Missing Custom Claim in token

I also posted this issue on GitHub for the React Native library: Refreshed Token does not contain requested scopes · Issue #786 · auth0/react-native-auth0 · GitHub

Problem: Our app is using a custom scope OURAPP_ID_SCOPE inside the Auth0 token. Specifically this is used to get the userId. The token correctly contains a value for the OURAPP_ID_SCOPE property when we do an initial login. However, when we do a refresh token or when we re-launch the app, we notice that the userId property is missing on the user object

What we are seeing is that the user property from the Auth0 hook is behaving different depending on whether or not the user logged in during the current app session, versus a previous one (eg. existing auth). It seems that the user property from the Auth0 hook does not contain the requested scope on subsequent app launches.

In the following example, the value of user will not contain the custom scope properties if we re-launch the app. Our custom scope is https://dev.fleet.ourapp.team/userId.

NOTE: user data and our team URLs are anonymized (using ChatGPT)

/**
 * Auth0 authorization parameters
 */
const options = {};
const BASE_SCOPE = 'openid profile email';
const OURAPP_ID_SCOPE = 'https://dev.ourapp.team/userId';
const REFRESH_TOKEN_SCOPE = 'offline_access';
const scope = [BASE_SCOPE, OURAPP_ID_SCOPE, REFRESH_TOKEN_SCOPE].join(' ');

const parameters = {
  audience: 'https://api.dev.ourapp.team/',
  scope: scope
};

const AuthContextProvider = ({ children }: Props) => {
  const { authorize, user, getCredentials, hasValidCredentials } = useAuth0();

  // Login function
  const login = useCallback(async () => {
    try {
      const { accessToken, expiresAt } = await authorize(parameters, options);
      if (accessToken && expiresAt) {
        setApiAccessToken(accessToken);
      }
    } catch (error) {
      console.log(`Auth0 Error on login:`, error);
    }
  }
  , [authorize]);

  // On app launch
  useEffect(() => {
    const loadApiAccessToken = async () => {
      try {
        const isLoggedIn = await hasValidCredentials(0);
        if ( !isLoggedIn ) {
          return
        }
        const { accessToken, expiresAt } = await getCredentials(scope, undefined, undefined, true)
        if (accessToken && expiresAt) {
          setApiAccessToken(accessToken);
        }
      } catch (error) {
        console.log('Auth0: Error loading accessToken', error)
      }
    }
    if ( !apiAccessToken ) {
      loadApiAccessToken()
    }
  }, [apiAccessToken, getCredentials, hasValidCredentials])

  const userData = useMemo(() => {
    if ( !user ) {
      return undefined
    }

    const { name, email, picture } = user;
    const userId = user[OURAPP_ID_SCOPE];

    // Manually check properties that we assume are required
    // TODO: Verify w/ server that these properties are correct and required vs optional
    if ( !name || !email || !userId ) {
      console.log(`Auth0: Error: missing required user data`, user)
      return undefined
    }
    return {
      name,
      email,
      picture,
      id: userId
    }
  }, [user]);

  return { ..., userData, ... }

}

User value if this session started with login then the custom claim https://dev.fleet.ourapp.team/userId is defined

{
  "https://dev.fleet.ourapp.team/userId": "random123456",  <--- We need this!
  "https://dev.fleet.ourapp.team/roles": [],
  "givenName": "UserFirstName",
  "familyName": "UserLastName",
  "nickname": "usernickname",
  "name": "UserFirstName UserLastName",
  "picture": "https://example.com/anon-profile-pic.png",
  "locale": "en",
  "updatedAt": "2023-11-06T19:17:34.389Z",
  "email": "useremail@ourapp.team",
  "emailVerified": true,
  "sub": "anon-oauth2|randomizedUserId"
}

User value on subsequent app launches. Notice that the custom scope variable is missing.

{
  "givenName": "UserFirstName",
  "familyName": "UserLastName",
  "nickname": "usernickname",
  "name": "UserFirstName UserLastName",
  "picture": "https://example.com/anon-profile-pic.png",
  "locale": "en",
  "updatedAt": "2023-11-06T19:17:34.389Z",
  "email": "useremail@ourapp.team",
  "emailVerified": true,
  "sub": "anon-oauth2|randomizedUserId"
}

Hi @objc,

Welcome to the Auth0 Community!

I have just tested this on my end and could not reproduce the issue. In my tests, I was able to get my custom claims in the ID token both during the initial login and refresh token flow.

When checking your logs, I did notice that during the successful refresh token exchange there was a "wrong client" message in the response.

Just to rule out whether your current Post-Login Action script has issues, could you please try the following code which I used during my tests in a new script, and see how it behaves during both the login and refresh token flow?

exports.onExecutePostLogin = async (event, api) => {
  const namespace = 'https://my-app.example.com';
  if (event.authorization) {
    api.accessToken.setCustomClaim(`${namespace}/email`, event.user.email);
    api.accessToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
    api.idToken.setCustomClaim(`${namespace}/email`, event.user.email);
    api.idToken.setCustomClaim(`${namespace}/roles`, event.authorization.roles);
  }
};

Please let me know how this goes for you.

Thanks,
Rueben

1 Like

Hi @rueben.tiow! Thanks for your response. We deployed that change to our server and it’s working now! Appreciate all the help from everyone at Auth0. We’re using the React Native client and excited to release our app in the coming months. :sun_with_face:

Hi @objc,

Thanks for the reply and I’m glad that everything is working now!

Please feel free to reach out again if you have any additional questions.

Thanks,
Rueben

This topic was automatically closed 14 days after the last reply. New replies are no longer allowed.