Beta

Create a tenant

A new tenant starts with its own users, OAuth settings, audit history and logs. You are its first Tenant Admin.

BTL Admin

Reading and validating aggregated claims

All domains, identifiers, and tokens in these examples are fictional.

The printer has your UserInfo response with Northfield's statement inside it. Its sign-in code has already checked the response the usual way: it came from the photo service over HTTPS, in answer to the printer's own access token, and its sub matches the ID token. The discount now depends on the embedded JWT.

Decoding that JWT takes one line of code, and the decoded claim says true. Applying the discount at that point would mean trusting a statement nobody has checked. The photo service did not sign it, and nothing so far shows that Northfield did either. To the printer, the JWT is a string inside the response, and a string can say anything, including a header with "alg": "none" and no signature at all.

Two signers, two decisions

The response combines statements from two parties, and the printer trusts each one for something different:

QuestionWho answers itHow the printer checks
Which photo account signed in?The photo serviceThe validated ID token, and a UserInfo response whose sub matches it.
Whose enrollment does the statement describe?The photo service, by placing the statement in this responseThe same checks. Northfield's JWT names no one.
Is that person enrolled, and until when?NorthfieldNorthfield's signature, issuer, and expiry, checked against the printer's own configuration.

The second row is easy to miss. Because Northfield's statement carries no subject, the printer relies on the photo service to have attached the right statement to the right person. It can check Northfield's signature, but not that the photo service picked your statement rather than a classmate's. That is acceptable only because the printer already trusts the photo service to identify you, and it is a reason to accept Northfield statements only from providers the printer has agreed to take them from. If the mail service, which the printer also supports for sign-in, began including Northfield statements, the printer would ignore them until it had made the same arrangement with the mail service.

The third row needs a trust relationship of its own. Trusting the photo service gives the printer no reason to trust Northfield, so the printer configures Northfield separately, as it would any issuer. In this example Northfield publishes provider metadata like an OpenID Provider. The printer fetched it from https://id.northfield.example/.well-known/openid-configuration, took the key set address from its jwks_uri, and recorded that Northfield signs with ES256 and may vouch for https://id.northfield.example/enrolled.

Checking the embedded JWT

For each aggregated claim it wants, the printer works through these steps:

  1. Follow the reference. Look up the claim name in _claim_names, find the source it points to in _claim_sources, and take that source's JWT member. A missing source, or one without a JWT, means the claim is absent.
  2. Check the issuer before using any key. Read iss without trusting it yet, and compare it exactly with the claims providers in the printer's configuration that may vouch for this claim name. Stop if it is not one of them. The printer never fetches keys from an issuer just because a JWT names it.
  3. Verify the signature with a key from that issuer's configured key set, selected by kid, using the algorithm configured for that issuer. none fails here, and so does any key the JWT tries to bring with it.
  4. Check the time. exp must not have passed, and iat must not be in the future, allowing a small tolerance for clock differences. The printer can add a limit of its own as well, such as refusing statements made before the current term began.
  5. Check the audience, if there is one. The specification does not say whom these JWTs are addressed to, so the arrangement with Northfield settles it. Northfield's statements carry no aud, and a Northfield JWT that names any audience is rejected. That rule stops a different kind of Northfield JWT being passed off as a statement. An ID token that Northfield issued to some other application is also signed with Northfield's key, may even contain the enrolled claim, and names that application in aud.
  6. Read only the claims that were named. The JWT must contain every claim that _claim_names points to it. The photo service may leave some of a claims provider's claims out of _claim_names, for example because you did not agree to share them, and the printer leaves those alone even if the JWT contains them.

As a sketch:

source = response._claim_sources.get(response._claim_names.get(ENROLLED))
if source is None or "JWT" not in source:
    return not_available()
statement = decode_without_trusting(source.JWT)

provider = claims_providers.get(statement.iss)        # the printer's own configuration
if provider is None or ENROLLED not in provider.claim_names:
    return rejected("unexpected_issuer")
key = provider.keys.find(statement.header.kid)         # refetches Northfield's jwks_uri, within a limit, for a new kid
if key is None or statement.header.alg not in provider.algorithms:
    return rejected("unknown_key_or_algorithm")
if not verify(key, source.JWT):
    return rejected("bad_signature")
if expired(statement.exp) or issued_in_future(statement.iat):
    return rejected("stale_statement")
if "aud" in statement:                                 # this arrangement expects none
    return rejected("unexpected_audience")
if ENROLLED not in statement:
    return not_available()
return statement[ENROLLED]

The helper names are illustrative. The order is the point: nothing reads the enrolled claim until the issuer, key, signature, and time have all passed.

Whose statement, about whom

The printer links your account to the pair https://auth.photos.example and user-2048, as Connecting a sign-in to an account described. Northfield's statement changes nothing about that. The discount belongs to the printer account the photo service signed you into.

Some claims providers include a sub after all. The specification says one should appear only when the value is the person's identifier at the claims provider. If Northfield sent "sub": "s-551207", that would be your identifier at Northfield, and it would not match user-2048. The mismatch is expected. Subjects are scoped by their issuer, as Supporting several providers explained, so the printer never compares a Northfield subject with a photo service subject, and never uses one to find or create an account.

A Northfield subject could still have a use. A printer worried that one enrollment might unlock discounts on many accounts could record it, under Northfield's issuer, to notice the same student record appearing again. It is also a good reason to leave the subject out: an identifier that is the same for every application you share it with lets those applications recognize you across all of them. Unless the arrangement calls for it, the printer does not store it.

When the statement fails

A failed check on Northfield's statement is not a failed sign-in. The ID token and the UserInfo response were valid, and you are still signed in to the same account. Only the discount is in question, and the printer handles each case on that basis:

SituationWhat the printer does
No Northfield statement in the responseNot an error. You may have declined to share it. No discount, and the card upload is offered instead.
Unknown kidNorthfield may have started signing with a new key. Fetch Northfield's key set again, with the usual limit on how often, and verify again.
Northfield's key set cannot be fetchedThe statement cannot be checked right now. No discount for the moment, with an offer to try again later.
Expired statementTreated as missing. You can renew your student status with the photo service.
Bad signature, unexpected issuer, or unexpected audienceThe JWT was altered or forged, or it is the wrong kind of JWT. Reject it and record a security event.

In every row, the discount waits for a statement that passes every check, and nothing falls back to the decoded value. The printer's log records the stage, the failed check, the claims provider's issuer, the kid, and a correlation ID, never the JWT.

Once a statement passes, the printer keeps only what the discount needs: that Northfield confirmed your enrollment, when the statement was made, and when it expires. After that expiry, the next discount means asking again.

Try it in the Lab

PUT IT INTO PRACTICE

Check your understanding

Try these questions before moving on. If an answer isn't right, use the feedback and try again.

0 of 2 answered correctly

Enable JavaScript to answer these questions and save progress in this browser.

QUESTION 1 OF 2The ID token and UserInfo response check out, but the Northfield JWT inside the response has "alg": "none". What should the printer do?

QUESTION 2 OF 2Northfield's JWT includes "sub": "s-551207", while the ID token's sub is user-2048. What does that mean?

We value your privacy

We use cookies and similar technologies to enhance your browsing experience, and analytics to understand our traffic. By clicking "Allow All", you consent to optional analytics. Cookie Policy

Learn identity