amplify-js icon indicating copy to clipboard operation
amplify-js copied to clipboard

id token doesn't contain ClientMetadata after refresh

Open rashidwiizb opened this issue 1 year ago • 18 comments

this way I m using signIn "@aws-amplify/react-native": "^1.1.1", "aws-amplify": "^6.3.1", "react-native": "0.73.2",

const userSignIn = await signIn({ username: userName, password: password, options: { clientMetadata: { roleType: "Student" } } });

I already attache pre token lambda trigger on Cognito for customise the id token . so I get the roleType in it on idTojeb when signing success . But when this idToken expires I get the new id token from amplify itself so in that idToken I didn't get this roleType

rashidwiizb avatar May 14 '24 13:05 rashidwiizb

Hi @rashidwiizb, react-native is actually part of the amplify-js project. I'll transfer this issue over to that repository.

mattcreaser avatar May 14 '24 13:05 mattcreaser

Hello, @rashidwiizb 👋. I think we may need to understand how your Auth flow is structured to better help here. How are you persisting the roleType field and attaching it to the iDToken? Can you share the lambda hook implementation and how to reproduce this? Thanks!

cwomack avatar May 16 '24 20:05 cwomack

We are facing the same issue. When providing the ClientMetadata to the signIn method, we are getting the right JWT token from Cognito (adding a claim, depending on the ClientMetadata, to the token via the PreTokenGeneration trigger in Cognito).

But when doing the fetchAuthSession({ forceRefresh: true }); via Amplify, the ClientMetadata is not provided to the PreTokenGeneration trigger and thus, this information is missing in the JWT token provided by the fetchAuthSession call.

We are using the latest "aws-amplify" version 6.3.2. Thanks...

TomMuehlegger avatar May 17 '24 09:05 TomMuehlegger

Hi @mattcreaser thanks for the reply.

@cwomack my pre-token lambda triggers is

const handler = async (event, context,callback) => {
  try {
        let roleType = '';
        if (event.triggerSource === 'TokenGeneration_Authentication') {
            roleType = event.request?.clientMetadata?.roleType;
            cachedRoleType = roleType;
        } else if (event.triggerSource === 'TokenGeneration_RefreshTokens') {
            roleType = cachedRoleType;
        }
        
        event.response = {
            claimsOverrideDetails: {
                claimsToAddOrOverride: {
                    'roleType': roleType
                },                
            },
        };

        return callback(null, event);
    } catch (error) {
        console.error('Error processing Pre Token Generation:', error);
        throw error;
    }
};

export { handler };

when I signin I pass the roleType in ClientMetadata and I get that roletype in idtoken,but when the idtoken is expires amplify itself refresh and get the new idtoken and access token but in that id token the roleType is empty strings "", that mean when refreshing the lambda doesn't get the ClientMetadata . Is there any way to fix this ?

rashidwiizb avatar May 17 '24 09:05 rashidwiizb

hello everyone. Passing clientMetadata while refreshing tokens is not supported at this time. However we are aware of this feature and will be working on it. I'll mark this issue as a feature request for now.

israx avatar May 17 '24 11:05 israx

Hi @israx

I am configuring AWS Amplify with different user pools and client IDs according to the role I pass to the amplify_Config function. This works correctly, and Amplify is configured with the appropriate pool ID and client ID based on the role.

When I first select the "Student" role, it configures for the student, and then the sign-in happens successfully, and the user is logged in. However, after the current user logs out, if I choose another role and call amplify_Config in useEffect again, it reconfigures correctly. Then, when I call sign-in, Amplify tries to sign in with the previously configured user pool and client ID, resulting in an error.

But when I reload the page after selecting the role and configuring Amplify, it works fine. Is there a way to achieve this without reloading the page?

my amplify_Config

export const amplify_Config = async (role) => { return new Promise((resolve, reject) => { let poolId = ''; let clientId = ''; role = role.charAt(0).toUpperCase() + role.slice(1);

    if (role === "Student") {
        poolId = config.AWS_CONFIG.STUDENT_POOL_ID;
        clientId = config.AWS_CONFIG.STUDENT_CLIENT;
    } else if (role === "Teacher") {
        poolId = config.AWS_CONFIG.TEACHER_POOL_ID;
        clientId = config.AWS_CONFIG.TEACHER_CLIENT;
    } else if (role === "Parent") {
        poolId = config.AWS_CONFIG.PARENT_POOL_ID;
        clientId = config.AWS_CONFIG.PARENT_CLIENT;
    }

    // console.log("amp config", { role }, { poolId }, { clientId });
    Amplify.configure({
        Auth: {
            Cognito: {
                userPoolId: poolId,
                userPoolClientId: clientId,
                loginWith: {
                    oauth: {
                        domain: '',
                        responseType: 'code',
                        scopes: config.AWS_CONFIG.SCOPE,
                        redirectSignIn: [''],
                        redirectSignOut: [''],
                    }
                },
            },
        },
    });
    resolve();
});

}

the useeffect and reload in signIn page after selecting role

const reload = () => { var refresh = localStorage.getItem('reload'); setTimeout(function () { if (refresh === null) { window.location.reload(); window.localStorage.setItem('reload', "1"); } });

setTimeout(function () { localStorage.removeItem('reload') }, 1000); } } useEffect(() => { const config = async() =>{ return await amplify_Config(role); } if(role){ dispatch(selecteRole(role)); config(); reload() // page reloading },[role])

rashidwiizb avatar May 21 '24 12:05 rashidwiizb

Hello @rashidwiizb . Can you try the following ?

  1. call Amplify.getConfigure before calling the signIn API and see if the configure was updated.
  2. listening for the signOut hub event and calling Amplify.configure after that.

If that doesn't work. Can you open a different GH issue regarding the problem with Amplify.configure , so we can assist you in a better way?

israx avatar May 21 '24 12:05 israx

Hi @israx first I call signOut for current logged user and then I reconfigured the amplify according with passed role after configuring I call Amplify.getConfigure and this gives the new configuration , but after that I call signIn it uses the previous configured client id and userpool id for signing;

Amplify.configure({ Auth: { Cognito: { userPoolId: poolId, userPoolClientId: clientId, loginWith: { oauth: { domain: '', responseType: 'code', scopes: config.AWS_CONFIG.SCOPE, redirectSignIn: [''], redirectSignOut: [''], } }, }, }, });

console.log("new configuration",Amplify.getConfigure());

rashidwiizb avatar May 22 '24 05:05 rashidwiizb

hello everyone. Passing clientMetadata while refreshing tokens is not supported at this time. However we are aware of this feature and will be working on it. I'll mark this issue as a feature request for now.

@israx People are waiting for this feature for years: https://github.com/aws-amplify/amplify-js/issues/6731 Is there any prospect of when this will be worked on?

leo-hsk avatar Jun 19 '24 06:06 leo-hsk

@israx Is this issue addressed in the roadmap? Please let me know if there are any updates.

kashee337 avatar Dec 12 '24 00:12 kashee337

@leo-hsk and @kashee337, we don't have any updates for this feature at this point. However, I'll bring it to our product team and review this internally again.

cwomack avatar Dec 12 '24 17:12 cwomack

+1

enchorb avatar Feb 11 '25 02:02 enchorb

Is there any update on this or workarounds? Is there a recipe to add a custom auth challenge to refresh tokens? I see that's the way to get clientMetadata through

henriwoodcock avatar Feb 18 '25 15:02 henriwoodcock

Hey @henriwoodcock unfortunately passing clientMetadata in refreshing auth token is currently not supported due to service endpoint's limitation. If your intended use case is for customizing idToken like OP, have you considered using userAttributes instead?

joon-won avatar Feb 21 '25 01:02 joon-won

@joon-won I have considered this, but the issue is the attribute is dynamic. So when the user first logs, they select an ID, we then pass is through a custom auth flow and then add it to the access token. For subsequent refreshes we want the access token to contain the exact same claims as the previous one. I worry I'd be doing a lot of admin update user calls at run-time just before refresh in our endpoint

henriwoodcock avatar Feb 21 '25 12:02 henriwoodcock

Hmm, can we make the user id not change? If it's an id, I would expect it does not change much, in that case we might be able to set up some attribute custom:user-id and set up pre token generation lambda to pick that up from userAttributes on refresh which will also contain user-id in this case, and have your lambda form the same access token. In pre token generation lambda, you can capture your refresh logic by checking triggerSource be TokenGeneration_RefreshTokens. It might not require many calls to be made before refresh in that way. Could you share your token refresh logic to dive deeper into this?

joon-won avatar Feb 22 '25 02:02 joon-won

@joon-won yes that makes sense, however there is the possibility for it to change when a user first logs in.

I have a setup where a user could have 2 different accounts on my platform but using the same cognito login. This is setup by having a login platform (auth.website.com) and then app sites (app1.website.com). I have built a custom oauth2 flow so that a user logs into auth.website.com and then a custom auth flow completes login to the app (exchanging a auth.website.com cognito jwt for another - app specific jwt).

Part of this flow also now includes the user on auth.website.com select which user they would like to log in as on the app1.website.com. Initially this works as we can pass the select user-id in clientMetadata in the respond to custom challenge. However on refreshes this doesn't work - we know which user the refresh is for, because in redis we store the hash of the refresh token and the user's id but there is no way to get this info into the token generation handler.

This is why in my setup I mentioned many update admin calls because we'd have to do it just before we call the initiate auth with the refresh token.

If you do have any ideas on how I could improve this to allow for refreshes properly though, please do let me know

henriwoodcock avatar Feb 23 '25 20:02 henriwoodcock

@henriwoodcock Thanks for sharing your use case. Unfortunately, passing user-id in clientMetadata is not currently supported by the Cognito service API as the underlying initiateAuth will not pass clientMetadata to Pre token generation lambda trigger. The only way currently is using userAttributes as only limited request params are accepted with the lambda trigger. For the full information on params they accept, please refer to this documentation. Also regarding use of userAttributes, hope this documentation helps.

I will keep this issue open and update it once the use case become available.

joon-won avatar Feb 25 '25 18:02 joon-won