Technical: OpenID Connect 1.0 Nuances

May 10, 2025 in Technical, OpenID Connect 1.0 by James Elliott 17 minutes

This is a commentary on several troubling trends in the security world, as well as an explainer on some fundamental OpenID Connect 1.0 concepts.


The intended audience of this article is those curious about some of the design choices Authelia has recently made surrounding Claims1, as well as those trying to navigate implementation of the specifications. I suspect some who are just interested in the topic may also find this beneficial. I have seen some interest from parties within the Open Source Community as well as the more specific Authelia Community for information like this, so hopefully it helps satisfy some of this.

Because of the technical nature, I’ve intentionally not including it on the homepage, but we will probably publish more articles like this in the future in the same category.

Learning about OpenID Connect 1.0 and the associated specifications has been quite a journey. There is a lot to read and even more to understand. This journey has taken years even with preexisting knowledge; and the following article represents the elements of the specifications that were not only the most time-consuming to accurately grasp, but also what I now consider the most important elements, at least in the way I understand them today.

While writing this article it is becoming clearer and clearer to me that even though this article looks long it will only take most interested people about 20 minutes to read. I find this frustratingly ironic. I can only hope that these learnings help current and future OpenID Connect 1.0 Providers and Relying Parties save time in understanding what I now consider a wonderfully beautiful framework and technology.

The decision to write this article mainly comes from the fact that I have found myself recently and regularly discussing a few topics associated with OpenID Connect 1.0 and in particular some of the nuances that don’t seem well understood. I fail to see today why these concepts are hard to understand, they are very clearly detailed in the relevant specifications; I can only summarize at least from my anecdotal experience no one appears to discuss them in detail.

I suspect most people will find this topic rather boring. However, those of you who find technical articles interesting will probably really enjoy this, especially if your understanding of the OpenID Connect 1.0 specifications is non-existent or just starting to develop.

Several of these concepts are heavily linked together and in my opinion once you realize how each of these concepts interacts with the others, there is a sort of orchestral design behind each of them; it’s almost like the specification designers actually put a lot of thought into this.


Access Tokens and ID Tokens

When these specifications were first envisioned, there existed no specific format for the Access Token2. The format was entirely up to the implementer. Effectively an Access Token2 was completely opaque and meaningless to the party that was utilizing it. This concept is important, as it is the foundation of the intent of the Access Token2, in contrast the ID Token3 is strictly a JSON Web Token4.

Some confusion has developed over the years surrounding the purpose of these tokens. You see they are meant to have meaning and to be verified only by the Authorization Server5 or integrated Resource Server6. This is partially because they can be completely opaque, and a couple of other reasons we’ll get into. It’s true that these tokens can be Introspected regardless of if they are opaque or a JSON Web Token4, but this is typically the role of the Resource Server6.

However as the JSON Web Token Profile for OAuth 2.0 Access Tokens was ratified several providers started using the JWT4 for Access Tokens2, and people are increasingly using these to determine the identity of a user. The problem is this is not the intent behind these tokens. In fact, for other reasons that will be discussed in the next section of this article it is actually a harmful decision to make.

The intended purpose of OpenID Connect 1.0 was to solve this particular issue with OAuth 2.0 as well as a few others. How it solves this particular issue is via the ID Token3. These tokens are designed to carry information that will uniquely identify a user.

This becomes more visible when you look at the final audience (aud Claim1) of a ID Token3 vs an Access Token2 using the JSON Web Token4 format. According to RFC7519 Section 4.1.3 the audience identifies the recipients that the JWT4 is intended for.

The below examples are the example ID Token3 and Access Token2 which are effectively valid in content for an Authorization Code Flow7 with the following parameters:

  1. Client ID: K2LQE4XRC54N7C2F5ZLF
  2. Authorized Audience: https://auth.example.com/api/oidc/introspection
  3. Scopes: openid profile email
ID Token
{
  "jti": "91de5882-ff69-46b6-b13b-165199f3191f",
  "iss": "https://auth.example.com",
  "sub": "d2fdc83d-d7ad-4ced-81d8-0bb87db4a127",
  "aud": "K2LQE4XRC54N7C2F5ZLF",
  "exp": 1745755215,
  "iat": 1745755000
}
Access Token
{
  "jti": "f30450c1-a60c-43ab-b855-e670f84ba45a",
  "iss": "https://auth.example.com",
  "sub": "d2fdc83d-d7ad-4ced-81d8-0bb87db4a127",
  "aud": "https://auth.example.com/api/oidc/introspection",
  "exp": 1745755215,
  "iat": 1745755000,
  "client_id": "K2LQE4XRC54N7C2F5ZLF",
  "scope": "openid profile email"
}

As opposed to the Authorization Code Flow7 where the sub should normally be the Resource Owners8 subject identifier, the Client Credentials Flow has an interesting effect on the Access Token2 where the sub should normally be the Client ID. While you could technically try to use this to validate the identity of the End-User9, this is not the intended purpose behind this Access Token2 format. In fact, it’s not even guaranteed by any normal area of the specification that this will be the case.

If you take a look the audience of the Access Token2 is not the Client ID, this is because it is not the intended recipient and should not use it to validate the identity of a user. You will also note the audience in the ID Token3 is the Client ID, it is required to be the Client ID, though it optionally can have additional values. This clearly expresses that the Client is the intended recipient.

The short version of this is that the Access Token2 is meant to be understood by the Authorization Server for uses at the User Information Endpoint10, Introspection Endpoint11, Revocation Endpoint12, and various other endpoints it decides to implement; or the endpoints of a Resource Server with deep understanding over how to validate the token. The ID Token3 is intended to be used by the Relying Party13. It’s used as a means of a verifiable proof the user is a unique individual, or at the very least has granted access to their account (under normal circumstances).

Another interesting difference between the ID Token3 and Access Token2 above is that the Access Token2 has a scope Claim1, but the ID Token3 does not. This may seem like a mistake but I assure you that it isn’t. There is no need for the ID Token3 to have a scope Claim1, as the token has a very clear intention; sharing user identity, and it’s only used for this purpose. The only clearly expressed intention behind an Access Token2 is in the name of the token type; it’s used to access things. This is why the concepts of Scope14 and Audience15 exist; rather than the token having blanket access, they limit the access to very specific endpoints and actions in a fairly explicitly expressed way.

This concept of what kinds of things these tokens can access specifically in OpenID Connect 1.0 is going to be touched on later, specifically when discussing Claims1 availability.


Claim Stability and Uniqueness: The effect on Identity Binding

There are various Claims1 available to implementers in most OpenID Connect 1.0 Provider implementations. These Claims1 vary from email addresses, to usernames, to readable names. A troubling trend that I have seen in both Enterprise and Open Source projects is that they use whatever Claim1 they feel like to bind identities together. In fact many don’t even use them to perform any kind of binding at all, they just decide because the username or email matches that the user is signed in.

This is not how the specification indicates this should be done. In fact rather than just leaving it up to everyone to decide how to handle this OpenID Connect 1.0 clearly spells out that these Claims1 must not be used for this purpose. Instead it directs our attention the sub and iss Claims1 being the only Stable and Unique Claims1 that an End-User9 can cleanly be identified by.

The Claim Stability and Uniqueness Section of OpenID Connect 1.0 reads:

The sub (subject) and iss (issuer) Claims from the ID Token, used together, are the only Claims that an RP can rely upon as a stable identifier for the End-User, since the sub Claim MUST be locally unique and never reassigned within the Issuer for a particular End-User, as described in Section 2. Therefore, the only guaranteed unique identifier for a given End-User is the combination of the iss Claim and the sub Claim.

All other Claims carry no such guarantees across different issuers in terms of stability over time or uniqueness across users, and Issuers are permitted to apply local restrictions and policies. For instance, an Issuer MAY re-use an email Claim Value across different End-Users at different points in time, and the claimed email address for a given End-User MAY change over time. Therefore, other Claims such as email, phone_number, preferred_username, and name MUST NOT be used as unique identifiers for the End-User, whether obtained from the ID Token or the UserInfo Endpoint.

The intent and gravity of neglecting this element is very clear as soon as you realize that providers may allow users to change their email address or username. TThese values are intended to be persistent identifiers for a user, never changing, regardless of what other values change. Not only for security, but for the user experience to be seamless. Just because they change their email they should not be prevented from signing into an application; and this would be the case if you do not use the intended Claims1.

This is linked heavily to the first concept because the Access Token2 may in some way identify the End-User9, but it’s not required to. In fact the Access Token2 may not even be a JWT4, it’s up to the OpenID Connect 1.0 Provider how they deliver it, as long as they understand it.

So some astute readers may be thinking. Why do these Claims1 exist in the first place then? Well it comes down to three primary functions. Obviously these functions are not an exhaustive list, but it should be enough to explain the principles.

The first function of these Claims1 is to provide helpful information or hints to the Relying Party during a Registration Flow, or in some instances an Identity Binding Flow (i.e. binding the sub and iss Claims1 to an identity that already exists) when they’re not already logged in. For example they may prefill a form.

The second function of these Claims1 is to provide the Relying Party with information about a Resource Owner8 with an already bound identity that they may not want to store themselves; such as they may perform a Authorization Flow to temporarily obtain the Resource Owners8 address information for a purchase.

The third function of these Claims1 is to provide the Relying Party a way to obtain updated details about a Resource Owner8 who already has a bound identity. For example updating their contact details. This leads naturally into the next concept.


Claims Availability

There are various ways to request and grant Claims1 in the OpenID Connect 1.0 specification. Many assume that the Claims1 are either exclusively available in the ID Token3, will always be in the ID Token3, or even worse as we’ve previously found out in the Access Token2. The assumption stems from the fact certain scopes grant the Relying Party access to certain sets of Claims1. But what does the specification really intend?

Well if we dive into the Claims1 that are standard in the ID Token3, it has a very minimal set of Claims1 by default. It can contain more, and in some scenarios it should contain a lot of Claims1.

Scope Parameter

Scopes seem to be mostly be assumed to always mint an ID Token3 with all the scopes relevant Claims1. Why is this assumption flawed?

The section on Requesting Claims using Scope Values has a crucial passage regarding this:

The Claims requested by the profile, email, address, and phone scope values are returned from the UserInfo Endpoint, as described in Section 5.3.2, when a response_type value is used that results in an Access Token being issued.

These scopes are very clearly not intended to include the Claims1 in the ID Token3. In fact the User Information Endpoint10 is meant to return them. The specification does not specifically prevent a OpenID Connect 1.0 Provider from returning them in the ID Token3, but it strongly suggests you shouldn’t do this normally.

There’s another crucial sentence directly after the first one.

However, when no Access Token is issued (which is the case for the response_type value id_token), the resulting Claims are returned in the ID Token.

This seems to solidify this point. When the Implicit Flow16 is used, as the Implicit Flow16 is the only flow that potentially will not result in an Access Token2; the OpenID Connect 1.0 Provider should mint an ID Token3 that’s populated with the Claims1 normally accessible at the User Information Endpoint10. In fact it’s only in one variation of the Implicit Flow16, when the response_type parameter is only id_token. In this instance the ID Token3 is categorically required to be populated with every one of the Claims1 the Access Token2 would normally be able to access at the User Information Endpoint10.

It’s amazing how this neatly ties back into the use case for the Access Token2 at the very start of this article. It’s intended for use cases like accessing specific API’s at the Authorization Server. If you then consider the fact the ID Token3 is a static snapshot of the unique identity of a Resource Owner8 as described in the above concept surrounding Claim Stability and Uniqueness, and then extrapolate that the request to the User Information Endpoint10 could realistically have the most up-to-date information about the user; I feel like it all just makes complete sense.

The question is if a Relying Party is not intending on using the Access Token2 to access the User Information Endpoint10 why are they not using the Implicit Flow16 with the Response Mode17 form_post since this flow is intended specifically to identify the user. Since they will not be issued an Access Token2, and only the ID Token3, and it’s performed over form_post, and the ID Token3 is signed and potentially encrypted multiple times; with the exception of the lack of client authentication most of the issues surrounding this flow mitigated by only returning the ID Token3 via form_post. That being said I think the security concerns are definitely a reasonable explanation; but the privacy and security benefits of using the User Information Endpoint10 are too strong an argument to not implement this properly.

What do I mean by privacy and security benefits? Well the User Information Endpoint10 requires an active Access Token2 to obtain the Claims1. This means even if the ID Token3 does not need to contain privacy sensitive information. An added benefit of this approach is that the Access Token2 can be revoked, and security conscious Authorization Servers5 will automatically mint a new Refresh Token18 at the same time it mints a new Access Token2 during the Refresh Token Flow19 which effectively rotates both of these tokens as the old ones should be intentioanlly revoked.

Claims Parameter

To cement the above point there is actually another means by which clients using any flow other than the Implicit Flow16 which only returns an ID Token3 can obtain additional Claims1 in the ID Token3.

This is done via the Claims Parameter. The Claims Parameter allows requesting specific Claims1 be present in the ID Token3. In addition it has the added benefit of allowing granular requests for specific Claims1 rather than granting an entire Scope14 which may have many useless Claims1 to the Relying Party.

This clearly has several impactful elements to security, privacy, and usability. This is also the parameter that is used to indicate elements which are optional to consent to. I suspect most users have seen these dialogs which ask the user what properties they want to allow the Relying Party to be able to access, even if they were not fully conscious of it.

The advantages of using the Claims1 parameter makes it quite a desirable feature. You can specifically request the openid scope, and request the specific Claims1 you need access to, and you can request that these Claims1 are made available either at the User Information Endpoint10 which is better for security and privacy, or in the ID Token3. It’s just too powerful not to use it.

Footnotes


  1. A Claim is some information that has been asserted about an Entity such as a End-User9 or Resource Owner8. This is also specifically defined in the OpenID Connect 1.0 Core Terminology as well as the Claims section which describes them in detail. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  2. An Access Token is traditionally an opaque token which is described in Section 1.4 of RFC6749 or sometimes a JSON Web Token4 which is either completely proprietary or described in RFC9068. The Access Token is used to access resources. ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  3. An ID Token is a JSON Web Token4 which is described ing ID Token Section of OpenID Connect 1.0 Core. The ID Token is intentionally designed to identify a unique user and has a strictly defined format and contents. This is also specifically defined in the OpenID Connect 1.0 Core Terminology↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  4. A JSON Web Token or JWT has multiple serialization forms, in our usages of them currently we use the compact serialization and this is what we’re referring to. The JSON Web Token (when signed, not encrypted) itself consists of three distinct parts, the header which contains important metadata about the token format, the body which contains the Claims1, and the signature which is a cryptographic hash of the other two parts. Both the header and the body are minimized JSON objects which are Base64 URL encoded. JSON Web Tokens are described in detail in RFC7519↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  5. The Authorization Server has the role of authorizing access to resources held on the Resource Server6↩︎ ↩︎ ↩︎

  6. The Resource Server has the role of holding resources which must be granted access to by the Resource Owner8 which is validated by the Authorization Server5↩︎ ↩︎ ↩︎

  7. The Authorization Code Flow is a flow which returns an Authorization Code in the Authorization Response. This Authorization Code is short lived, and is exchanged at the Token Endpoint along with any client authentication requirements for the minted tokens. ↩︎ ↩︎

  8. The Resource Owner is the owner of the information being requested, this is typically the user, but also can be the Relying Party13↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  9. The End-User is the human participant. This is a type of Resource Owner8. This is also specifically defined in the OpenID Connect 1.0 Core Terminology↩︎ ↩︎ ↩︎ ↩︎

  10. The User Information Endpoint is an OAuth 2.0 secured endpoint that has information about a Resource Owner8 commonly referred to as a user. You can read more about the User Information Endpoint by reading the UserInfo Endpoint Section of OpenID Connect 1.0 Core↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  11. The Introspection Endpoint is used to obtain information about an Access Token regardless of the format. You can read more about Token Introspection in RFC7662↩︎

  12. The Revocation Endpoint is used to revoke an Access Token and/or Refresh Token18. You can read more about Token Introspection in RFC7009↩︎

  13. The Relying Party is the party which relies on the OpenID Connect 1.0 Provider to process the Authorization Flow and the Resource Owner8 to grant access to the information. ↩︎ ↩︎

  14. The Scope defines specific actions a token is permitted to do or information they have access to. In OpenID Connect 1.0 the most common use for them is to define as set of Claims1 the token can access. You can read more about Scopes in the OAuth Defining Scopes Explainer↩︎ ↩︎

  15. The Audience describes the intended recipient that the token (especially a JSON Web Token4). This is represented as an array of strings which either contain URI’s or some other uniquely identifiable name for the recipient. This is stored in the aud Claim1 of JSON Web Token4 if applicable. ↩︎

  16. The Implicit Flow is a flow which directly returns the requested tokens within the Authorization Response. It does not exchange any short-lived code for the requested tokens. This flow is traditionally less secure due to the fact no client authentication occurs in order to obtain the tokens. The Implicit Flow is described in detail in the Implicit Flow Section of OpenID Connect 1.0 Core. This is also specifically defined in the OpenID Connect 1.0 Core Terminology↩︎ ↩︎ ↩︎ ↩︎ ↩︎

  17. The Response Mode describes how the Authorization Endpoint responds to Authorization Requests. There are three primary modes; query which redirects the Resource Owner8 with the response in the URI query parameters, fragment which redirects the Resource Owner8 with the response in the URI fragment parameters, and form_post which performs a POST request with the response in the request body. ↩︎

  18. The Refresh Token is typically a completely opaque token which can be used to reissue other tokens. ↩︎ ↩︎ ↩︎

  19. The Refresh Token Flow is a flow which exchanges a Refresh Token18 at the Token Endpoint for new tokens with the same security characteristics or narrowed security characteristics. ↩︎