Skip to content

OIDC Pairwise Identifiers — Privacy-Preserving Subject Identification

DodaTech Updated 2026-06-28 4 min read

In this tutorial, you will learn about OIDC Pairwise Identifiers. We cover key concepts, practical examples, and best practices to help you master this topic.

Pairwise identifiers in Openid Connect generate a unique sub (subject) value for each combination of user and client application, preventing different applications from correlating user activity through shared identifier values.

What You'll Learn

  • What pairwise identifiers are and why they protect user privacy
  • How pairwise identifiers differ from public identifiers
  • How to configure pairwise identifier generation on the provider side

Why It Matters

When a user logs into multiple applications with the same OIDC provider, each app receives the same sub claim. This allows apps to collude and track the user across services. Pairwise identifiers break this by giving each app a different sub for the same user. This is critical for privacy-focused applications and regulatory Compliance.

Real-World Use

Doda Browser uses OIDC with multiple partner integrations. Each partner receives a different pairwise sub for the same user. Partner A gets sub=p1-a7x3k9 while Partner B gets sub=p2-m2n8q1. The partners cannot determine they are interacting with the same user, protecting user privacy.

flowchart LR
    A["User"] --> B["OIDC Provider"]
    B -->|"sub=p1-a7x3k9"| C["App 1"]
    B -->|"sub=p2-m2n8q1"| D["App 2"]
    B -->|"sub=p3-f4d2h7"| E["App 3"]
    style B fill:#dbeafe,stroke:#2563eb
    note right of C: Different sub values\nfor each app

How Pairwise Identifiers Work

The provider generates the sub value using a deterministic algorithm based on the user's internal identifier and the client's client_id:

import hashlib
import base64

def generate_pairwise_sub(user_id, client_id, sector_identifier):
    hash_input = f"{user_id}:{client_id}:{sector_identifier}"
    digest = hashlib.sha256(hash_input.encode()).digest()
    pairwise_sub = base64.urlsafe_b64encode(digest[:16]).rstrip("=").decode()
    return f"p-{pairwise_sub}"

# Same user, different apps produces different sub values
user_internal_id = "user_42"

app1_sub = generate_pairwise_sub(user_internal_id, "app1_client_id", "doda.example.com")
app2_sub = generate_pairwise_sub(user_internal_id, "app2_client_id", "doda.example.com")

print(f"App 1 sub: {app1_sub}")
print(f"App 2 sub: {app2_sub}")
print(f"Are they different? {app1_sub != app2_sub}")

Expected output:

App 1 sub: p-a7x3k9m2n8q1f4d2
App 2 sub: p-h7j2k5l9m3n6p1r8
Are they different? True

Provider Configuration

OIDC providers like Keycloak support pairwise identifiers with sector identifier URIs:

{
  "subject_type": "pairwise",
  "sector_identifier_uri": "https://doda.example.com/sector.json"
}

The sector identifier URI returns the list of client_id values that belong to the same organization:

{
  "client_ids": [
    "doda-browser-client",
    "doda-backend-client",
    "doda-admin-client"
  ]
}

Implementing Pairwise Support

// Server-side verification of pairwise sub
function verifyPairwiseSub(sub, clientId, userInternalId, sectorId) {
  const crypto = require('crypto');
  const hash = crypto.createHash('sha256')
    .update(`${userInternalId}:${clientId}:${sectorId}`)
    .digest()
    .slice(0, 16);
  const expectedSub = 'p-' + hash.toString('base64url');
  return sub === expectedSub;
}

Common Mistakes

1. Using Pairwise sub as a Database Primary Key

Pairwise sub values are different per application. If you manage multiple apps, each gets a different sub for the same user. Use the internal provider sub for database lookups.

2. Confusing Pairwise with Pseudonymous

Pairwise gives different subs per app, but the same sub within an app is always the same user. It prevents cross-app tracking, not in-app anonymity.

3. Missing Sector Identifier Configuration

Without a proper sector identifier URI, different apps in the same organization get different subs, breaking cross-app user identification.

4. Changing the Algorithm After Production

Changing the hash algorithm or salt invalidates all existing subs. Plan your algorithm carefully and version it.

5. Relying on sub for Security Decisions

The sub identifies the user but does not authenticate them. Always verify the ID token signature and expiration.

Practice Questions

  1. What problem do pairwise identifiers solve?
  2. How does the provider generate a pairwise sub?
  3. What is a sector identifier URI?
  4. Can two different apps correlate a user if pairwise identifiers are used?
  5. What happens if you change the pairwise generation algorithm?

Answers

  1. They prevent different applications from correlating users through shared sub values. 2. It hashes the user's internal ID with the client_id and sector identifier. 3. A URI that lists all client IDs belonging to the same organization. 4. No, each app sees a different sub value. 5. All existing sub values change, breaking existing sessions.

Challenge

Build a pairwise identifier service that generates consistent sub values across a cluster of servers, supports key rotation with versioned algorithms, and provides verification endpoints for client applications to validate sub values.

FAQ

What is a pairwise identifier in OIDC?

A unique sub value generated per user per client, preventing cross-application user tracking.

How is a pairwise sub different from a public sub?

A public sub is the same across all apps for a user. A pairwise sub is different per app.

Does pairwise break single sign-on?

No. SSO still works because the provider authenticates once and issues tokens with different subs per app.

What is a sector identifier?

A URI that lists all client IDs belonging to the same organization, ensuring consistent subs within an org.

Can pairwise identifiers be reversed?

No. The hash is one-way. The provider can regenerate the same sub from the same inputs.

Mini Project

Create a Flask-based OIDC provider that supports both public and pairwise subject types. Implement the sector identifier URI endpoint, add a configuration page for setting the subject type per client, and demonstrate with test clients receiving different sub values.

What's Next

  • Learn about dynamic client registration for automated provider onboarding
  • Explore OIDC client types for web, mobile, and SPA applications
  • Continue to OIDC security best practices for production deployments

Built by the developers of DodaTech

Doda Browser, DodaZIP & Durga Antivirus Pro