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

Enabling Device Tracking from Cognito User Pool sets `token_use` on SSR auth call to `access` instead of `id`

Open asp3 opened this issue 2 years ago • 11 comments

Before opening, please confirm:

JavaScript Framework

React, Next.js

Amplify APIs

Authentication

Amplify Categories

auth

Environment information

    @aws-amplify/api: ^5.0.7 => 5.0.7 
    @aws-amplify/auth: ^5.1.1 => 5.1.1 
    @aws-amplify/core: ^5.0.7 => 5.0.7 
    @aws-amplify/storage: ^5.0.7 => 5.0.7 

Describe the bug

IDENTITY when called from ssr.

identity: {
    claims: {
      sub: '82328429-dbb9-46d9-b8eb-0ebd238e221c',
      device_key: 'us-east-1_e2605136-8c55-406b-914c-baaf706c935c',
      token_use: 'access',
      scope: 'aws.cognito.signin.user.admin',
      auth_time: 1672183360,
      iss: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pj3FdFG6C',
      exp: 1672187757,
      iat: 1672184157,
      jti: 'd00deefd-5149-44fe-9e84-923a2526d3fe',
      client_id: 'linov2orokihpnfsq2aqqugsc',
      username: 'kstudent12@[email protected]'
    },
    defaultAuthStrategy: 'ALLOW',
    groups: null,
    issuer: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pj3FdFG6C',
    sourceIp: [ '100.37.153.113' ],
    sub: '82328429-dbb9-46d9-b8eb-0ebd238e221c',
    username: 'kstudent12@[email protected]'
  },

IDENTITY from client side call.

identity: {
    claims: {
      birthday: '2002-9-13',
      sub: '82328429-dbb9-46d9-b8eb-0ebd238e221c',
      referralcode: '94FE1F98',
      timezone: 'america/los_angeles',
      rating: '5',
      accounttype: 'Student',
      iss: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pj3FdFG6C',
      cover: 'https://knowt-user-attachments.s3.amazonaws.com/847a780f1f494a56ab6be61628d60042.jpeg',
      auth_time: 1672183360,
      ID: '5f608122-f843-11ea-8356-4502370cf8a3',
      ...otherCustomClaimsFromPreToken
      'cognito:username': 'kstudent12@[email protected]',
      aud: 'linov2orokihpnfsq2aqqugsc',
      ratingcount: '1',
      token_use: 'id',
      lastnotified: '1649437274',
      grade: '2nd',
      name: 'Max',
      username: 'user12'
    },
    defaultAuthStrategy: 'ALLOW',
    groups: null,
    issuer: 'https://cognito-idp.us-east-1.amazonaws.com/us-east-1_pj3FdFG6C',
    sourceIp: [ '100.37.153.113' ],
    sub: '82328429-dbb9-46d9-b8eb-0ebd238e221c',
    username: 'kstudent12@[email protected]'
  },

The problem seems pretty straightforward, the token_use key is access instead of ID, which I also assume is the reason why the data from the PreTokenTrigger is not populated as well. We use custom fields (like a custom username field), which is why it is necessary to get the overridden fields from the PreToken generation.

Expected behavior

SSRContext should have the same auth information as the client side call.

Reproduction steps

set up amplify environment, and set up a pretoken trigger on the user pool that is used. Then, make client side and server side calls and see the differences in payload in appsync.

Code Snippet

The value of the identity is above, printed in lambda (called from AppSync)

Log output

// Put your logs below this line


aws-exports.js

No response

Manual configuration

No response

Additional configuration

No response

Mobile Device

No response

Mobile Operating System

No response

Mobile Browser

No response

Mobile Browser Version

No response

Additional information and screenshots

No response

asp3 avatar Dec 28 '22 00:12 asp3

Hi @asp3, thank you for opening this issue. I've taken a look at it and haven't yet reproduced this issue. However I have some follow up questions:

  • It looks like you may be importing each module separately? ( @aws-amplify/<module> vs aws-amplify )
  • Are you utilizing the getServerSideProps method here?
  • Which of the Amplify Auth methods are you using?

Here's is what is working for me as expected:

import { Amplify, Auth, withSSRContext } from 'aws-amplify';
import awsExports from "../src/aws-exports";
Amplify.configure({ ...awsExports, ssr: true });

...

export async function getServerSideProps(ctx){
    const SSR = withSSRContext(ctx);
    try {
        const session = await SSR.Auth.currentSession();
        return {
            props: { msg: session.idToken.payload }
        }
    } catch (e) {
        console.log("ERROR", e);
        return {
            props: { msg: e }
        }
    }
}

...

With the above, I am able to get my custom claims in my props including token_use: 'id'. Are you doing this differently from the above? Any additional context will be helpful here.

nadetastic avatar Dec 29 '22 03:12 nadetastic

Hi @nadetastic thank you for the response!

That code snippet works for me as well, and I am able to see the custom claims in my next app as well. However, the issue I have is with appsync graphql calls, the claims there seem to only include the access token

const { API, Auth } = withSSRContext(context);
    const session = await Auth.currentSession();
    console.log("session", session.idToken.payload); // Works as expected
    const userId = await fetchUserId(Auth);
    const { textbookId, code } = context.query;

    let textbook = null;
    let serverUser = null;

    if (!userId && !code) {
        return {
            props: {
                textbook: null,
                serverUser: null,
            },
        };
    } else {
        // This call only has the access token payload
        serverUser = API.graphql({
                query: gql(getCurrentUser),
                variables: {
                    input: {
                        userId,
                    },
                },
            })
            .then(res => res?.data?.getCurrentUser)
            .catch(console.error);
    }

Thanks for the help!

asp3 avatar Dec 29 '22 17:12 asp3

Ok thanks you @asp3 for the additional context - to confirm, are you passing code and textbook in the query parameters of the URL?

nadetastic avatar Dec 29 '22 20:12 nadetastic

@nadetastic Yeah, the call hits our lambda code, which gets called from Appsync.

The appsync resolver,

getCurrentUser(input: GetCurrentUserInput): UserDetails!

which is connected to the lambda code that just prints the event passed in to the lambda handler, which is pasted above as event.identity.

So I assume that the withSSRContext API library works differently than the other one, somehow?

asp3 avatar Dec 29 '22 20:12 asp3

@asp3 I'd like to clarify my understanding of the flow, which I am assuming is the following:

  1. Client (Visit Next APP with Params in URL)
  2. getSSProps (Invoked and calls WithSSRContext().API.graphql)
  3. AppSync (Backend with Lambda resolver)

If this is correct, what part seems to have the issue?

I am also curious what const userId = await fetchUserId(Auth) does under the hood and if it successfully returns userId

nadetastic avatar Jan 04 '23 19:01 nadetastic

So fetchUserId is just a simple wrapper around the Auth library

export const fetchUserId = async (Auth): Promise<string> => {
    try {
        const userId = (await Auth.currentAuthenticatedUser()).signInUserSession.idToken.payload.ID;
        refreshToken(Auth);
        return userId;
    } catch {
        return null;
    }
};

It does successfully return, which means from SSRContext, we are able to get the idToken payload. However, the issue is that when we make the graphql call and the appsync is invoked, the payload is that of the accessToken, not the idToken. (printing the event from the lambda handler of the appsync query/mutation)

@nadetastic

asp3 avatar Jan 05 '23 20:01 asp3

After doing some digging, I can see that the error happens here:

https://github.com/aws-amplify/amplify-js/blob/736918491e74d9aefb543fa4e531efe867195521/packages/api-graphql/src/GraphQLAPI.ts#L285

https://github.com/aws-amplify/amplify-js/blob/736918491e74d9aefb543fa4e531efe867195521/packages/api-graphql/src/GraphQLAPI.ts#L170-L177

However, when running the same function in my getServerSideProps call, Auth.currentSession().getAccessToken().getJwtToken(), it return the token successfully.

@nadetastic

asp3 avatar Jan 08 '23 17:01 asp3

Also some other info that we found out:

The error that we were getting, No Current User, has been fixed! We figured out the issue happened since we had been destructuring the return from withSSRContext.

const {Auth, API} = withSSRContext(context)

...
API.graphql(....)
// FAILS - No Current User

Changed to

const SSR = withSSRContext(context)

SSR.API.graphql(...)
// NO error thrown

However, the issue stated at the beginning of the issue still stands, where the event in the parameters that is received in our lambda (called through appsync) does not contain the info of the authenticated user, since it only has the accessToken info, instead of the idToken info.

The only way I have found that fixes this issue so far is by changing https://github.com/aws-amplify/amplify-js/blob/main/packages/api-graphql/src/GraphQLAPI.ts#L174 to

- Authorization: session.getAccessToken().getJwtToken(),
+ Authorization: session.getIdToken().getJwtToken(),

After this patch, I can see the proper information in my lambda! I wonder if this is configurable through some parameter, and if this is a safe change to make. @nadetastic

asp3 avatar Jan 08 '23 18:01 asp3

A bit more into this,

looks like turning off Device Tracking from Cognito User Pool Settings fixes this!

https://stackoverflow.com/questions/46879876/aws-cognito-invalid-refresh-token

Not sure why, but I turned on debug logging, and it seems that the Refresh Session did not work because of this, and works as expected after turning this off. Very weird! @nadetastic

asp3 avatar Jan 08 '23 20:01 asp3

Hi @asp3 a fix for SSR with API as discussed in https://github.com/aws-amplify/amplify-js/issues/10786 was released with https://github.com/aws-amplify/amplify-js/pull/10831

Could you test with 5.0.10 ?

nadetastic avatar Jan 14 '23 06:01 nadetastic

Following up on this issue, I was able to reproduce with [email protected]. Updating the title to better reflect the problem

nadetastic avatar Mar 21 '23 16:03 nadetastic