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"
}