ory / hydra

The most scalable and customizable OpenID Certified™ OpenID Connect and OAuth Provider on the market. Become an OpenID Connect and OAuth2 Provider over night. Broad support for related RFCs. Written in Go, cloud native, headless, API-first. Available as a service on Ory Network and for self-hosters.
https://www.ory.sh/?utm_source=github&utm_medium=banner&utm_campaign=hydra
Apache License 2.0
15.56k stars 1.5k forks source link

Email claim not present on ID token after issuing with refresh #3852

Closed 3schwartz closed 5 days ago

3schwartz commented 2 weeks ago

Preflight checklist

Ory Network Project

No response

Describe the bug

Issue

Details

We are using the oidc-client-ts (v 3.0.1) client in our frontend with the authorization code flow and refresh tokens, https://www.ory.sh/docs/oauth2-oidc/refresh-token-grant.

We have enabled the openid, email and offline_access scope in the OAuth2 Client used.

Our client setup are using oidc-client-ts:

new UserManager({
      authority: 'http://localhost:4000’, // ory channel in this example
      redirect_uri: window.location.origin + '/auth/callback',
      client_id: "<SOME_ID_CLIENT>",
      response_type: 'code',
      scope: 'openid email offline_access',
      automaticSilentRenew: true,
      post_logout_redirect_uri: window.location.origin + '/auth/login',
      extraQueryParams: {
        audience: 'http://<SOME_DOMAIN>.com'
      }
})

The first request to our token endpoint after a successfully login is using our authorization code. We call https://<OUR_DOMAIN>.com/oauth2/token with the form data

grant_type: authorization_code
redirect_uri: https://<OUR_DOMAIN>.com/auth/callback
code: <SOME_CODE>
code_verifier: <SOME_VERIFIER>
client_id: <SOME_CLIENT_ID>

and we get response

{
    "access_token": “<ACCESS_TOKEN>",
    "expires_in": 86399,
    "id_token”: “<ID_TOKEN>",
    "refresh_token": “<REFRESH_TOKEN>",
    "scope": "openid email offline_access",
    "token_type": "bearer"
}

Now the email claim is present in the id token but not in the access token.

Access token

{
  "aud": [
    "http://<SOME_DOMAIN>.com"
  ],
  "client_id": "<SOME_CLIENT_ID>",
  "exp": 1727779067,
  "ext": {},
  "iat": <iat>,
  "iss": "https://<PROJECT_SLUG>.projects.oryapis.com",
  "jti": “<jti>",
  "nbf": <nbf>,
  "scope": "openid email offline_access",
  "sub": “<sub>"
}

ID token

{
  "amr": [
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password"
  ],
  "at_hash": “<AT_HASH>",
  "aud": [
    “<AUD>"
  ],
  "auth_time": 1727692664,
  "email": “<USER_EMAIL>",
  "email_verified": true,
  "exp": 1727779067,
  "iat": <IAT>,
  "iss": "https://<PROJECT_SLUG>.projects.oryapis.com",
  "jti": “<jit>",
  "rat": <rat>,
  "sid": “<SID>",
  "sub": “<sub>"
}

Now after we use our refresh token we call the token endpoint again https://<OUR_DOMAIN>.com/oauth2/token with body

grant_type: refresh_token
refresh_token: <REFRESH_TOKEN>
scope: openid email offline_access
client_id: <CLIENT_ID>

we get response

{
    "access_token”: “<ACCESS_TOKEN>",
    "expires_in": 30,
    "id_token”: “<ID_TOKEN>",
    "refresh_token": “<REFRESH_TOKEN>",
    "scope": "openid email offline_access",
    "token_type": "bearer"
}

where access token contains

{
  "aud": [
    "http://<SOME_DOMAIN>.com"
  ],
  "client_id": "<SOME_CLIENT_ID>",
  "exp": 1727779067,
  "ext": {},
  "iat": <iat>,
  "iss": "https://<PROJECT_SLUG>.projects.oryapis.com",
  "jti": “<jti>",
  "nbf": <nbf>,
  "scope": "openid email offline_access",
  "sub": “<sub>"
}

and ID token now doesn’t have the email and sid claim

{
  "amr": [
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password",
    "password"
  ],
  "at_hash": “<AT_HASH>",
  "aud": [
    “<AUD>"
  ],
  "auth_time": 1727692664,
  "exp": 1727779067,
  "iat": <IAT>,
  "iss": "https://<PROJECT_SLUG>.projects.oryapis.com",
  "jti": “<jit>",
  "rat": <rat>,
  "sub": “<sub>"
}

Reproducing the bug

Relevant log output

No response

Relevant configuration

No response

Version

Using Ory network

On which operating system are you observing this issue?

Ory Network

In which environment are you deploying?

None

Additional Context

No response

3schwartz commented 1 week ago

I was finally able to get email claims on ID tokens after renewing them using refresh tokens.

However, I'm still unsure why the configuration change to use_continue_with_transitions: true resolved the issue. The only relevant information I could find was in the documentation here and a few related references (detailed below).

Could someone provide a more detailed explanation of how this configuration change affects the token behavior?

Summary of Steps Taken

Earlier today, I created a new Ory project, an OAuth2 client (with the same configurations as previous clients that had issues), and a user using the default identity schema (preset://email).

I then initiated an OIDC flow with this new setup and observed that, unlike before, the email was successfully present on the ID token after renewal. However, when I used the same setup with my older project, client, and user(s), the issue persisted.

Upon comparing the oauth2-config and identity-config between the two projects, I noticed a difference in their identity configurations. Specifically:

Research Findings

Upon searching for use_continue_with_transitions, I found these relevant issues and discussions:

Additionally, I found documentation stating that this setting:

...
This setting is enabled by default for new projects.
...

(Reference: Ory Kratos Native Recovery Flows Documentation)

We have not made any changes to these configurations, so it appears that the default value for this setting has been updated since we originally started our project. This change in the default configuration is likely the reason the old setup did not include the email claim on ID tokens after renewal.

Resolution

After setting use_continue_with_transitions: true in the old project, we now successfully receive email claims on our ID tokens after token renewal.

aeneasr commented 5 days ago

I'm a bit confused, in the section

and ID token now doesn’t have the email and sid claim

you show a JWT token that does have the email claim. It is missing the sid claim though!

aeneasr commented 5 days ago

So, I have tried to reproduce this issue both with the flag set to true and to false, however, I always get the same claims in the ID token from the refresh_token grant as I do from the original grant (email and sid). Therefore, I assume that this is some other issue and am closing this as can't reproduce. Please feel free to provide more details/context in case you find this issue again.

3schwartz commented 4 days ago

I'm a bit confused, in the section

and ID token now doesn’t have the email and sid claim

you show a JWT token that does have the email claim. It is missing the sid claim though!

Sorry it was a copy paste error. Updated now.

3schwartz commented 4 days ago

So, I have tried to reproduce this issue both with the flag set to true and to false, however, I always get the same claims in the ID token from the refresh_token grant as I do from the original grant (email and sid). Therefore, I assume that this is some other issue and am closing this as can't reproduce. Please feel free to provide more details/context in case you find this issue again.

I have compared both identity and oauth2 configuration and this one change is the only difference between the project where I have issues, and the new project which works.

Could you maybe help provide some links to documentation or give some details what this flags does in the backend use_continue_with_transitions: true?