Skip to main content

Solutions for common OAuth2-related problems

Spec-compliant OAuth 2.0 and OpenID Connect is hard. Let's take a look how to resolve certain issues.

OpenID Connect ID Token missing

If you expect an OAuth 2.0 ID Token but aren't receiving one, this can have multiple reasons:

  1. You are using the client_credentials grant which can't return an ID token.
  2. You forgot to request the openid scope when calling /oauth2/auth?response_type=code (Authorize Code Flow - correct would be /oauth2/auth?response_type=code&scope=openid) or the id_token response type when calling /oauth2/auth?response_type=token (Implicit/Hybrid flow - correct would be /oauth2/auth?response_type=token+id_token&scope=openid or any other combination such as response_type=id_token, response_type=token+code+id_token).
  3. You forgot to include nonce when calling /oauth2/auth?response_type=id_token or any combination of id_token (token+id_token, token+code+id_token etc.) nonce is a value sent by the client application and included as a claim in the returned id_token. The nonce claim can then be verified by the client application to mitigate any replay attacks. Optional for Authoize Code Flow, but mandatory for Implicit/Hybrid flow. Correct request would be - /oauth2/auth?response_type=id_token&nonce=some-value. You can read more about nonce here OpenId Connect Core.
  4. Your consent app didn't send granted_scope: ["openid"] or when accepting the consent request.
  5. The OAuth 2.0 Client making the request isn't allowed to request the scope openid.

OAuth 2.0 Refresh Token is missing

If you expect an OAuth 2.0 Refresh Token but aren't receiving one, this can have multiple reasons:

  1. You are using an implicit or hybrid flow. These flows never return a refresh token!
  2. You are using the client_credentials grant which can't return a refresh token.
  3. You forgot to request the offline or offline_access scope when calling /oauth2/auth.
  4. You consent app didn't send granted_scope: ["offline"] or granted_scope: ["offline_access"] when accepting the consent request.
  5. The OAuth 2.0 Client making the request isn't allowed to grant type refresh_token.

OAuth 2.0 authorize code flow fails

The most likely cause is misconfiguration, summarized in the next sections.

Refresh Token flow fails

Refresh tokens can become invalid if abuse is detected, but coding issues may also trigger this scenario, for example if a client makes multiple requests.

Some common examples:

  1. Replay of authorization code grant.
  2. Replay of refresh token grant.

Wrong or misconfigured OAuth 2.0 client

You are using the wrong OAuth 2.0 Client or the OAuth 2.0 Client has a broken configuration. To check that you're using the right client, run:

ory get oauth2-client {client.id}}

The result shows you the whole client (excluding its secret). Check that the values are correct. Example:

{
"client_id": "my-client",
"grant_types": [
"authorization_code"
],
"jwks": {},
"redirect_uris": [
"http://127.0.0.1:5556/callback"
],
"response_types": [
"code"
],
"scope": "openid offline",
"subject_type": "pairwise",
"token_endpoint_auth_method": "client_secret_basic",
"userinfo_signed_response_alg": "none"
}

Redirect URL isn't whitelisted

A likely cause of your request failing is that you are using the wrong redirect URL. Assuming your OAuth 2.0 URL looks like https://{project.slug}.projects.oryapis.com/oauth2/auth?client_id=my-client&...&redirect_uri=http://my-url/callback

The redirect URL http://my-url/callback must be whitelisted in your client configuration. The URLs must match. URL http://my-url/callback and http://my-url/callback?foo=bar are different URLs!

To see the whitelisted redirect_uris, check the client:

ory get oauth2-client {client.id}

{
// ...
"redirect_uris": [
"http://127.0.0.1:5556/callback"
],
// ...
}

Here you see that http://my-url/callback isn't in the list, which is why the request fails.

/oauth2/token endpoint fails for JWKS based client

When trying to get an access token for a client registered with "token_endpoint_auth_method": "private_key_jwt" it's possible that the provided jwt has expired.

The response body that's sent to the client is like below

{
"error": "error",
"error_description": "The error is unrecognizable: Token is expired"
}

The debug error messages isn't provided to avoid divulging more information to a malicious client.

If you need more information in response for debugging, switch the project to development mode. Now you will be able to see the error_debug field in the response like below:

{
"error": "error",
"error_description": "The error is unrecognizable",
"status_code": 500,
"error_debug": "Token is expired"
}