UpveraTechnology
Explainer· For developersUpvera ID

What is a pairwise identifier in OpenID Connect?

Short answer

A pairwise identifier is a sub value that an OpenID Connect provider computes separately for each sector, usually each relying party's domain, so the same person gets a stable but different ID at every site. It is defined in OpenID Connect Core 1.0, section 8, and it stops two sites from linking their records on the ID alone. A provider lists it in subject_types_supported, and a client can ask for it with subject_type=pairwise when it registers.

Updated
Reading
4 min
The need
Understand what a pairwise sub is, why providers use it, and how to store and use it correctly in a relying party.
Plain text
/guides/pairwise-identifiers.md

#Two kinds of subject

Every OpenID Connect ID token carries a sub claim: the provider's identifier for the person. Section 8 of OpenID Connect Core 1.0 defines two types.

  • public: every client receives the same sub for the same person. It is the default when a provider's discovery document has no subject_types_supported entry.
  • pairwise: each client receives a different sub, so clients cannot correlate a person's activity across sites without that person's permission.

Pairwise does not strictly mean "one value per client". It means one value per sector identifier.

#What a sector identifier is

By default, the sector identifier is the host of the client's registered redirect_uri. A client that redirects to https://app.example.com/auth/callback has the sector app.example.com.

If a client registers redirect URIs on more than one host, it must also register a sector_identifier_uri. That is an https URL pointing to a JSON array of redirect URIs, and its host becomes the sector:

[
  "https://app.example.com/auth/callback",
  "https://www.example.com/auth/callback"
]

The provider checks at registration that every registered redirect URI appears in the array. Clients with the same sector receive the same sub for a person. That is how a group of sites under one owner can recognize the same user, and how a client can later move its redirect domain without every user turning into a stranger.

#How providers derive it

Section 8.1 sets three requirements and leaves the method open:

  1. Only the provider can reverse the value.
  2. Different sector identifiers give different values.
  3. The calculation is deterministic.

The spec gives three examples: hash the sector identifier, the local account ID and a secret salt together; encrypt the same three values; or create a random identifier for each pair of sector and account and store it. A keyed hash is a common choice:

import base64, hashlib, hmac

def pairwise_sub(key: bytes, sector: str, account_id: str) -> str:
    # length-prefix each part so "ab" + "c" never equals "a" + "bc"
    msg = b"".join(len(p).to_bytes(4, "big") + p
                   for p in (sector.encode(), account_id.encode()))
    digest = hmac.new(key, msg, hashlib.sha256).digest()
    return base64.urlsafe_b64encode(digest).rstrip(b"=").decode()

The derived methods need no extra table, but the key cannot change without giving every user a new sub. A stored random value per pair can survive key changes and migrations, at the cost of a lookup.

#Public and pairwise compared

Public Pairwise
Same person at two different sites Same sub Different sub
Sites can join records on sub Yes No
Value depends on The account The account and the sector
Default when discovery lists no type Yes No
Your web and mobile clients see The same value The same value only if they use the same sector

#Handling it as a relying party

  1. Read subject_types_supported from the provider's discovery document. If it lists only pairwise, that is what you will get.
  2. With dynamic registration, send subject_type and, if you use more than one redirect host, a sector_identifier_uri.
  3. Key accounts on the pair of iss and sub. Section 5.7 calls this the only guaranteed unique identifier for a person.
  4. Store sub as a case-sensitive string of up to 255 ASCII characters.
  5. If you call the UserInfo endpoint, check that its sub exactly matches the one in the ID token.

#Common mistakes

  • Keying accounts on email. The spec says email must not be used as a unique identifier. It can change and can even be reassigned.
  • Case-insensitive storage. A lowercased sub or a case-insensitive column collation can merge two different people.
  • Moving the redirect domain. Under the default rule, a new callback host is a new sector, and every returning user arrives with a new sub. Choose the sector before launch, or register a sector_identifier_uri from the start.
  • Expecting one ID across products. Products on different domains see different values unless they register the same sector.
  • Treating pairwise as anonymity. It only covers sub. If several sites receive the same email address, they can still link the person on it.

#Questions

Can a site turn a pairwise sub back into the person's account?

No. Section 8.1 requires that nobody except the provider can reverse it.

Does the sub change when the person changes their email?

No. A sub is locally unique within the provider and never reassigned, and it does not depend on email.

Can two of my apps see the same sub?

Yes, if they use the same sector identifier: the same redirect host, or the same sector_identifier_uri.

Is a pairwise sub a secret?

No. Treat it as an opaque, stable ID for your own records. Its protection comes from being different at every site, not from being hidden.

#How Upvera does it

Upvera ID is an OpenID Connect provider at https://id.upvera.co, and its discovery document lists only pairwise. Every site receives a stable sub of 22 characters. Web and mobile clients of the same startup on the same domain see the same value; another startup sees the same person under a different value.

Email is off by default and comes only if the person turns it on. Instead of more personal data, the upvera:human claim tells the site the person's proof-of-personhood tier and the month it was reached. Client registration opens in the Upvera app with its next release; endpoints and claims are documented on upvera.dev. The design is covered in the Upvera ID report.

#Sources