Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Scope field is empty in Token Response #5753

Open
artalat opened this issue Apr 18, 2024 · 19 comments
Open

Scope field is empty in Token Response #5753

artalat opened this issue Apr 18, 2024 · 19 comments
Labels
pending-verification Something is still under investigation stale

Comments

@artalat
Copy link

artalat commented Apr 18, 2024

Describe the bug

While authenticating user, the token response has empty scope. I would like to get the JWT access token, and as per the docs I am sending API resource param (which is the default api resource as well) without openid scope.

If I send openid scope (along with the resource param), then the scope property has the required scopes but the access_token is an opaque token and not a JWT.

Expected behavior

Token response should have JWT access_token and scope field filled with required scopes.

How to reproduce?

Auth Request

/oidc/auth?code_challenge=cAN9fZ-QM4TuoFCun1kpBV2Fx2Y4RpMMGfW3YzWSnB4&resource=https%3A%2F%2Fapi.mevris.app&code_challenge_method=S256&prompt=consent&redirect_uri=http%3A%2F%2Flocalhost%3A19006&client_id=gthwi145jrqgqw7itehxz&response_type=code&state=oKKHhYjXQS&scope=profile%20email%20offline_access

Auth Response

{
  "type": "success",
  "error": null,
  "url": "http://localhost:19006/?code=UeFQ9NrME1f6L5d43HzLr9QZoZRa8wQQMnNJnqlMgj2&state=oKKHhYjXQS&iss=http%3A%2F%2Flocalhost%3A3001%2Foidc",
  "params": {
    "code": "UeFQ9NrME1f6L5d43HzLr9QZoZRa8wQQMnNJnqlMgj2",
    "state": "oKKHhYjXQS",
    "iss": "http://localhost:3001/oidc"
  },
  "authentication": null,
  "errorCode": null
}

Token Request

grant_type=authorization_code&client_id=gthwi145jrqgqw7itehxz&scope=profile%20email%20offline_access&code_verifier=Rg81au3yQ21ZnkHlEFf7q32bZ7aAdlWsfJfMITPTmdK8sxyuonKlz5XqdbinbOrrDhKB5flnd700pXPJl3dezsgXMFUA97joeCRuNTjSSEa1YTvA3QVdci6DYdY4Qnbv&redirect_uri=http%3A%2F%2Flocalhost%3A19006&code=UeFQ9NrME1f6L5d43HzLr9QZoZRa8wQQMnNJnqlMgj2

Token Response

{
  "accessToken": "eyJhbGciOiJFUzM4NCIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjFMNHVHcTQwWkV3R1VDb1M4RUp0dWZQNURaNDJXWG55UmRpTXNSMDl3eU0ifQ.eyJqdGkiOiJINXpETndkaEtVWHpGYURjbUd3OGoiLCJzdWIiOiJ4MWc4YXh2cDM0ZXgiLCJpYXQiOjE3MTM0NTAyNTEsImV4cCI6MTcxMzQ1Mzg1MSwic2NvcGUiOiIiLCJjbGllbnRfaWQiOiJndGh3aTE0NWpycWdxdzdpdGVoeHoiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEvb2lkYyIsImF1ZCI6Imh0dHBzOi8vYXBpLm1ldnJpcy5hcHAifQ.hGqFD8TrLY6yWDqkzIL-soVmtSFX7V7xk34lbk0Ufc1gE9WTnAFBB_Q0Z1c5fNfZINzCWPTB1YXSbcQjcE9Pjw-S0lLSCWYlxBV3mzigptmLxzNwTcsRdWFMdR-nDupa",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "refreshToken": "rdlCL94FSp-B19Difj_wxa_0VPfvMaafpW7DGRQTuYX",
  "scope": "",
  "issuedAt": 1713450251
}
@artalat artalat added the bug Something isn't working label Apr 18, 2024
@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

Did you add scopes to your resource https://api.mevris.app, add the scopes to a user role and assign the role to your end user? After that, you also need to declare scopes you want to ask for consent when init an auth request.

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

openid and email are scopes attached to ID Token not access token.

@darcyYe darcyYe added pending-verification Something is still under investigation and removed bug Something isn't working labels Apr 18, 2024
@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

BTW from what I can read from the context you've provided, this is not a bug, removed the "bug" tag. Feel free to bring it back with more context.

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Did you add scopes to your resource https://api.mevris.app, add the scopes to a user role and assign the role to your end user? After that, you also need to declare scopes you want to ask for consent when init an auth request.

Do I need to do this for openid scopes as well?

openid and email are scopes attached to ID Token not access token.

The thing is, they are add to the token in case of opaque access_token, even if ID Token is present, but when jwt is generated, the field is an empty string like the example above

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Example Opaque Token

Here both resource & openid scope are sent.

Auth Request

/oidc/auth?code_challenge=DFXpT_qX8BbHx9ZFJ1g4x11XOAGpuVTDYQEnegethIs&resource=https%3A%2F%2Fapi.mevris.app&code_challenge_method=S256&prompt=consent&redirect_uri=http%3A%2F%2Flocalhost%3A19007&client_id=gthwi145jrqgqw7itehxz&response_type=code&state=q06pjx36MH&scope=openid%20profile%20email%20offline_access

Auth Response

{
  "type": "success",
  "error": null,
  "url": "http://localhost:19007/?code=KkGRGSXph2S7ra5PzEgAdjDql2c0NLeCUszioYJ7QB-&state=q06pjx36MH&iss=http%3A%2F%2Flocalhost%3A3001%2Foidc",
  "params": {
    "code": "KkGRGSXph2S7ra5PzEgAdjDql2c0NLeCUszioYJ7QB-",
    "state": "q06pjx36MH",
    "iss": "http://localhost:3001/oidc"
  },
  "authentication": null,
  "errorCode": null
}

Token Request

grant_type=authorization_code&client_id=gthwi145jrqgqw7itehxz&scope=openid%20profile%20email%20offline_access&code_verifier=SzzGHWVHXINPgeq5Rx96jsGwLqjlw4jcn6VhbknisE3xKIRyJxvts8ZHSkU8XuEZGB97GUh9zhjlqBz2FHZA3MIHOsqDVEqxJRmIK8PfgXxBT8IJtFxRNxb69EUWRoaK&redirect_uri=http%3A%2F%2Flocalhost%3A19007&code=KkGRGSXph2S7ra5PzEgAdjDql2c0NLeCUszioYJ7QB-

Token Response

{
  "accessToken": "NZeyax74oBf8JakMdXAn-IIz1uKlUKmky8Il_0GK88P",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "refreshToken": "fscKNjdco9-_-IfDyzsYeQkT-XptpoKRtuZNc-OzEHU",
  "scope": "openid profile email offline_access",
  "idToken": "eyJhbGciOiJFUzM4NCIsInR5cCI6IkpXVCIsImtpZCI6IjFMNHVHcTQwWkV3R1VDb1M4RUp0dWZQNURaNDJXWG55UmRpTXNSMDl3eU0ifQ.eyJzdWIiOiJibm1lbzduN3p3ZWIiLCJuYW1lIjoiQWJkdWwgUmVobWFuIFRhbGF0IiwicGljdHVyZSI6Imh0dHBzOi8vbWV2cmlzLWltYWdlLWF2YXRhcnMuczMuZGUuaW8uY2xvdWQub3ZoLm5ldC83YTdhMDM0YS1kZGQ3LTQ0ZTktYTJmNy1jZmQ5MmE2NjFiNTkucG5nIiwidXNlcm5hbWUiOiJhcnRhbGF0IiwiZW1haWwiOiJyZWhtYW4udGFsYXRAZ21haWwuY29tIiwiZW1haWxfdmVyaWZpZWQiOnRydWUsImF0X2hhc2giOiJIa1RDYk5vaFZVMGFldHBiTmlOaEVDSFJjXzlCaHVyMCIsImF1ZCI6Imd0aHdpMTQ1anJxZ3F3N2l0ZWh4eiIsImV4cCI6MTcxMzQ1NzE1NiwiaWF0IjoxNzEzNDUzNTU2LCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEvb2lkYyJ9.QSMfS4nfjUH439oLx4zE_LECOOL4CJwvv8Y30O01Cm98GmvGP9x-ijaJWFXn8cidoCChIhLKTvDcgRhdZrImwxIsihve1KPijt9jhuLQ_Wk7lUu74SEWXPoWGBcINB5E",
  "issuedAt": 1713453556
}

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

Could you please attach your SDK config? You need to add your resource and scope in the SDK config.

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Im not using any official SDK, developing in expo

import * as React from 'react';
import { Button, Text, View } from 'react-native';
import * as AuthSession from 'expo-auth-session';
import * as WebBrowser from 'expo-web-browser';

WebBrowser.maybeCompleteAuthSession();

const redirectUri = AuthSession.makeRedirectUri();

const CLIENT_ID = 'gthwi145jrqgqw7itehxz';
const SCOPES: string[] = [
  'openid', 
  'profile', 
  'email', 
  'offline_access',
];

export default function Auth() {
  const [token, setToken] = React.useState<any>();
  const discovery = AuthSession.useAutoDiscovery('http://localhost:3001/oidc');
 
	// Create and load an auth request
  const [request, result, promptAsync] = AuthSession.useAuthRequest(
    {
      usePKCE: true,
      codeChallengeMethod: AuthSession.CodeChallengeMethod.S256,
      clientId: CLIENT_ID,
      redirectUri,
      scopes: SCOPES,
      prompt: AuthSession.Prompt.Consent,
      extraParams: {
        resource: 'https://api.mevris.app'
      }
  },
    discovery
  );

  const handleResponse = React.useCallback(
    async () => {

      if (result?.type !== 'success' || result.params.error) {
        console.log('Something went wrong');
        return;
      }

      const tokenResult = await AuthSession.exchangeCodeAsync(
        {
          scopes: SCOPES,
          code: result.params.code,
          clientId: CLIENT_ID,
          redirectUri,
          extraParams: {
            code_verifier: request?.codeVerifier ? request.codeVerifier : ''
          }
        },
        discovery as any
      );

      setToken(JSON.parse(JSON.stringify(tokenResult)));
    },
    [request, result, discovery]
  );



  return (
    <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center' }}>
      <Text>AuthSession Example</Text>
      <Text>Redirect URI: {redirectUri}</Text>
      <Text>Client ID: {CLIENT_ID}</Text>
      <Text>Scopes: {SCOPES.join(', ')}</Text>
      <View style={{ height: 20 }} />
      <Button title="Login!" disabled={!request} onPress={() => promptAsync()} />
      {result && <Text style={{width: '90%'}}>{JSON.stringify(result, null, 2)}</Text>}
      <Button title="Get Token" disabled={result?.type !== 'success'} onPress={() => handleResponse()} />
      {token && <Text style={{width: '90%'}}>{JSON.stringify(token, null, 2)}</Text>}
    </View>
  );
}

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

You should add your scopes to SCOPES.

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Im not using any custom scopes, intend to use only standard openid scopes for now. Problem is, event they're missing in the token response

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

openid means you can get id_token field with your token request response; email and profile brings corresponding information in your id_token JWT payload (such as email, email_verified, name etc.); offline_access means refresh token comes as well in token request response. They will not show up in access token scopes but takes effect in different way like I said.

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Should they also not show here (response from first example):

{
  "accessToken": "eyJhbGciOiJFUzM4NCIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjFMNHVHcTQwWkV3R1VDb1M4RUp0dWZQNURaNDJXWG55UmRpTXNSMDl3eU0ifQ.eyJqdGkiOiJINXpETndkaEtVWHpGYURjbUd3OGoiLCJzdWIiOiJ4MWc4YXh2cDM0ZXgiLCJpYXQiOjE3MTM0NTAyNTEsImV4cCI6MTcxMzQ1Mzg1MSwic2NvcGUiOiIiLCJjbGllbnRfaWQiOiJndGh3aTE0NWpycWdxdzdpdGVoeHoiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEvb2lkYyIsImF1ZCI6Imh0dHBzOi8vYXBpLm1ldnJpcy5hcHAifQ.hGqFD8TrLY6yWDqkzIL-soVmtSFX7V7xk34lbk0Ufc1gE9WTnAFBB_Q0Z1c5fNfZINzCWPTB1YXSbcQjcE9Pjw-S0lLSCWYlxBV3mzigptmLxzNwTcsRdWFMdR-nDupa",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "refreshToken": "rdlCL94FSp-B19Difj_wxa_0VPfvMaafpW7DGRQTuYX",
  "scope": "",
  "issuedAt": 1713450251
}

Notice "scope": ""

Also, no ID Token issued here.

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

id_token will not appear when you call token endpoint with refresh_token grant type. If you do not have custom scope and do not specify when init auth requests, what do you expect from access token scopes?

@darcyYe
Copy link
Contributor

darcyYe commented Apr 18, 2024

token request with authorization_code grant type and the code brought to callback URL with openid in the previous auth request ensure the appearance of id_token in the token response.

@artalat
Copy link
Author

artalat commented Apr 18, 2024

Im expecting, "scope": "openid profile email offline_access".

Please guide, when I have the following response, which I am getting in my use case, how do I check scopes. Moreover in the JWT there are no profile or email claims.

{
  "accessToken": "eyJhbGciOiJFUzM4NCIsInR5cCI6ImF0K2p3dCIsImtpZCI6IjFMNHVHcTQwWkV3R1VDb1M4RUp0dWZQNURaNDJXWG55UmRpTXNSMDl3eU0ifQ.eyJqdGkiOiJINXpETndkaEtVWHpGYURjbUd3OGoiLCJzdWIiOiJ4MWc4YXh2cDM0ZXgiLCJpYXQiOjE3MTM0NTAyNTEsImV4cCI6MTcxMzQ1Mzg1MSwic2NvcGUiOiIiLCJjbGllbnRfaWQiOiJndGh3aTE0NWpycWdxdzdpdGVoeHoiLCJpc3MiOiJodHRwOi8vbG9jYWxob3N0OjMwMDEvb2lkYyIsImF1ZCI6Imh0dHBzOi8vYXBpLm1ldnJpcy5hcHAifQ.hGqFD8TrLY6yWDqkzIL-soVmtSFX7V7xk34lbk0Ufc1gE9WTnAFBB_Q0Z1c5fNfZINzCWPTB1YXSbcQjcE9Pjw-S0lLSCWYlxBV3mzigptmLxzNwTcsRdWFMdR-nDupa",
  "tokenType": "Bearer",
  "expiresIn": 3600,
  "refreshToken": "rdlCL94FSp-B19Difj_wxa_0VPfvMaafpW7DGRQTuYX",
  "scope": "",
  "issuedAt": 1713450251
}

My requirement is very simple, I want to generate a JWT token (not an opaque token), that has the required claims (profile, email, etc). Our system was desinged on keycloak, and we had claims in the access token. We are trying to replicate the same here, to migrate to Logto

@charIeszhao
Copy link
Member

charIeszhao commented Apr 22, 2024

I think you're mixing up the concepts of OIDC scopes and API resource scopes.

The scopes you mentioned (openid, profile, offline_access, etc.) are OIDC scopes, which are primarily focused on user authentication and providing identity-related information. The consumer of these scopes is the OIDC auth server, not your own API server.

API resource scopes are focused on controlling access to specific functionalities or data within an API, and this is what you really want in your access tokens. You can only define these scopes in an "API resource". Simply go to "Logto console -> API resources" and create an API resource first, then create your scopes under the context of your API resource. Your backend service should check these scopes rather than the OIDC scopes.

Moreover, The "opaque" token you obtained earlier is the one that used to fetch user information from the /userinfo endpoint. You can not use this access token for your own API services. Please refer to the documentations and learn how to protect your own APIs with API resources. https://docs.logto.io/docs/recipes/protect-your-api/

@artalat
Copy link
Author

artalat commented Apr 26, 2024

My requirement is very simple, I want to generate a JWT token (not an opaque token), that has the required claims (profile, email, etc). Our system was desinged on keycloak, and we had claims in the access token. We are trying to replicate the same here, to migrate to Logto

Anyway I can achieve this?

@charIeszhao
Copy link
Member

Why do you need these claims in a JWT token when fetching userinfo, though? These information is returned from the /userinfo endpoind, which is hosted by Logto itself, not a third party. An opaque token is enough in this case since Logto can always check the DB if the received opaque token is valid. And the scopes you mentioned are pre-configured through LogtoConfigs in SDK.

JWT is for "offline verification" purpose, usually used when requesting from client to server, or a 3rd-party API service. In these scenarios, you'll have to provide a JWT format token, so that the receiver can check if it is valid and you have proper scopes for the requesting resource even if the token is not issued by themselves.

@charIeszhao
Copy link
Member

I'm not sure what SDK you are using, but let me explain it with the React SDK.

You need to provide these scopes through LogtoConfigs and pass it to Logto client
image

Then whenever you call fetchUserInfo or decode ID token claims, you will find you always get "tailored" userinfo based on the predefined scopes in LogtoConfig.

Copy link

This issue is stale because it has been open for 30 days with no activity. Remove stale label or comment or this will be closed in 5 days.

@github-actions github-actions bot added the stale label May 13, 2024
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
pending-verification Something is still under investigation stale
Development

No branches or pull requests

3 participants