5

I have created a resolver that uses the email address ($context.identity.claims.email). I tested my query in the AWS Console "Queries" section and all worked fine as $context.identity.claims looked as expected;

{
    sub: 'xxx-xxx-xxx-xxx-xxx',
    aud: 'xxxxxxxxx',
    email_verified: true,
    sub: 'xxx-xxx-xxx-xxx-xxx',
    token_use: 'id',
    auth_time: 1563643503,
    iss: 'https://cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_xxxxx',
    'cognito:username': 'xxxx',
    exp: 1563647103,
    iat: 1563643503,
    email: 'xxx@xxx.xxx'
}

All looks good so lets use it in my React App that uses the AWS Amplify code for authentication. Its not working now and that is because there is no "email" in the claim section! It looks like this;

{
    sub: 'xxx-xxx-xxx-xxx-xxx',
    event_id: 'xxx-xxx-xxx-xxx-xxx',
    token_use: 'access',
    scope: 'aws.cognito.signin.user.admin',
    auth_time: 1563643209,
    iss: 'https://cognito-idp.ap-southeast-1.amazonaws.com/ap-southeast-1_xxxx',
    exp: 1563646809,
    iat: 1563643209,
    jti: 'xxx-xxx-xxx-xxx-xxx',
    client_id: 'xxxx',
    username: 'xxxx'
}

Can anyone help me out as to why the email shows in the AWS Console Query but not when I call it from my own client?

devfubar
  • 436
  • 2
  • 14

4 Answers4

7

Amplify can be configured to include the current ID Token for each graphql request by passing in a function. Two configuration options are shown in the following:

import { Auth } from 'aws-amplify';

const getIdToken = async () => ({
  Authorization: (await Auth.currentSession()).getIdToken().getJwtToken()
});

const aws_exports = {
  aws_appsync_graphqlEndpoint: 'https://****.appsync-api.us-east-2.amazonaws.com/graphql',
  aws_appsync_region: 'us-east-2',
  aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS',

  // OPTION 1
  graphql_headers: getIdToken,

  // OPTION 2
  // API: {
  //   graphql_headers: getIdToken
  // },

  Auth: {
    identityPoolId: 'us-east-2:********-****-****-****-************',
    region: 'us-east-2',
    userPoolId: 'us-east-2_*********',
    userPoolWebClientId: '*************************',
    type: 'AMAZON_COGNITO_USER_POOLS'
  }
};

export default aws_exports;
Amplify.configure(awsconfig);

Note the different claims available to the resolver between Access & ID tokens.

Access tokens will provide claims such as client_id, jti, and scope, while ID token claims provide email, phone_number, etc., along with others like aud, cognito:roles and cognito:username.

Access Token

{
  "claims": {
    "auth_time": 1581438574,
    "client_id": "*************************",
    "cognito:groups": [
      "Admin"
    ],
    "event_id": "ec70594c-b02b-4015-ad0b-3c207a18a362",
    "exp": 1581442175,
    "iat": 1581438575,
    "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_*********",
    "jti": "351d2d5f-13c3-4de8-ba7c-b3c5a9e46ca6",
    "scope": "aws.cognito.signin.user.admin",
    "sub": "********-****-****-****-************",
    "token_use": "access",
    "username": "********-****-****-****-************"
  },
  ...
}

ID Token

{
  "claims": {
    "address": {
      "formatted": "1984 Newspeak Dr"
    },
    "aud": "....",
    "auth_time": 1581438671,
    "birthdate": "1984-04-04",
    "cognito:groups": [
      "Admin"
    ],
    "cognito:roles": [
      "arn:aws:iam::012345678901:role/us-east-2-ConsumerRole"
    ],
    "cognito:username": "********-****-****-****-************",
    "email": "winston.smith@oceania.gov",
    "email_verified": true,
    "event_id": "e3087488-bfc8-4d08-a44c-089c4ae7d8ec",
    "exp": 1581442271,
    "gender": "Male",
    "iat": 1581438672,
    "iss": "https://cognito-idp.us-east-2.amazonaws.com/us-east-2_*********",
    "name": "WINSTON SMITH",
    "phone_number": "+15551111984",
    "phone_number_verified": false,
    "sub": "********-****-****-****-************",
    "token_use": "id"
  },
  ...
}

Tested with amplify-js@2.2.4

Source: https://github.com/aws-amplify/amplify-js/blob/aws-amplify%402.2.4/packages/api/src/API.ts#L86-L107

ggriffin
  • 322
  • 3
  • 9
  • 1
    fantastic, not knowing the difference between access token and ID token had me tripped up, this allowed me to get email, thank you!! – andy mccullough Feb 12 '21 at 19:38
1

Guessing that inside your React App, you are retrieving the user attributes with something to the effect of

    import { Auth } from 'aws-amplify';

    async componentDidMount() {
           const currentUser = await Auth.currentUserInfo();
           const claims = currentUser.attributes; 
           // verification logic here, and here you cannot find claims['email']
    }

One thing to check is whether the specific React App client can access the 'email' attribute. The client may have been disallowed to specific attributes.

Inside the AWS Cognito Console > User Pools > General Settings > App Clients you should see something like the screen shot below.

Find the specific app client (match the Id). Click on 'Set attribute read and write permissions' - underlined red. There you should be able to select the email attribute as Readable by this client.

Cognito Screen Shot

cy6581
  • 143
  • 1
  • 7
  • Thanks for your suggestion. I did suspect this and already tried. Additionally, when using the AWS Console Appsync Queries UI I supplied the same User Pool web client id to the one I use in the front end. – devfubar Jul 21 '19 at 06:27
  • Also I use the React HOC "withAuthenticator" to authenticate. – devfubar Jul 21 '19 at 06:28
1

I have had a request for a feature in Amplify and I got the following brilliant solution suggestion

TLDR : Update your Auth Provider to create a "pre-token generation" Lambad, and inside your lambda you add another 'fake' group to the claim, as the groups are part of the token passed to AppSync

More details on the solution in this repo

https://github.com/dantasfiles/AmplifyMultiTenant

codaddict
  • 307
  • 2
  • 5
0

Ok, so I think they is in the "token_use" element. My original code used this function;

import {API, graphqlOperation} from 'aws-amplify';
import * as queries from '../../graphql/queries';

async function makeCall() {
    let resp = await API.graphql(graphqlOperation(queries.getMeta));
    return resp.data.getMeta;
}

That code produces the observed above. If I use the following (very dirty but works) code I get the above expected result;

import {Auth, API, graphqlOperation} from 'aws-amplify';
import axios from 'axios';
import * as queries from '../../graphql/queries';

async function makeCall() {
    const curSesh = await Auth.currentSession();
    const token = curSesh.idToken.jwtToken;
    const resp = await axios({
        method: 'post',
        url: API._options.aws_appsync_graphqlEndpoint,
        data: graphqlOperation(queries.getMeta),
        headers: {
            authorization: token
        }
    });
    return resp.data.data.getMeta;
}

I am not going mark this as solved quite yet as I am sure there is a far cleaner way to get this working. If anyone can shed light on it I would love to learn.

devfubar
  • 436
  • 2
  • 14
  • Technically, since you are using withAuthenticator HOC, it sets and stores the user credentials within Amplify itself. Which means that using `API.graphql()` will automatically sign the HTTP request with the correct Authorization header and you don't have to do it yourself. Can you share how you are using the withAuthenticator() in your App.js? Possibly check that you have initialized Amplify with correct config using `Amplify.configure()` in your App.js as well? – cy6581 Jul 22 '19 at 10:24
  • I dont do anything strange, just as is in the documentation ie; `Amplify.configure({"aws_appsync_authenticationType": "AMAZON_COGNITO_USER_POOLS",....)` and `withAuthenticator(App)` – devfubar Jul 23 '19 at 11:44
  • As I mentioned it looks like Amplify uses the access token and not the id token. Hence why I had to do the hacky code to do a request using the id token which carries the email address. – devfubar Jul 23 '19 at 11:46
  • 1
    You are right - seeing the source code the [API object indeed uses the accessToken for the Authorization header](https://github.com/aws-amplify/amplify-js/blob/799a1cfe59f7244f1cc51bdc0220a6c4ebf81455/packages/api/src/API.ts#L301). It seems like the only other option is to query Cognito via a Lambda with the "sub" field (aka User Pool User Id), to return the full User object, which is pretty long-winded process. There's a related workaround [here](https://stackoverflow.com/questions/42386180/aws-lambda-api-gateway-with-cognito-how-to-use-identityid-to-access-and-update/42405528#42405528) – cy6581 Jul 24 '19 at 09:16
  • I did think about doing the lambda option however there are other factors in our project that made that a difficult option for us. Besides which I am not sure it is any more elegant solution than the code I used above. Is this a bug in amplify, not sure why it would use the acces rather than id token? – devfubar Jul 24 '19 at 13:06