Cognito ID Pool always fail if the ID provider URL given with / on web identity federation of Auth0.

Summary

  • If the Provider URL of identity provider is ending with /, Issuer validation of Cognito ID pool always fail.
  • When the temporary security credentials are created by OpenID Connect (OIDC) ID provider, you need to add separate identity providers for Cognito ID Pool and STS. even if same provider.

Issuing temporary security credentials in IAM for authenticated by OIDC provider.

There are 2 ways of issuing temporary security credentials in IAM for authenticated user by Auth0, using Cognito ID Pool or STS (Security Token Service). When Cognito ID Pool is issuing, the process of credentials issue is almost same, because the Cognito uses STS inside. However, the verification of ID token has difference.

In case of Cognito ID Pool

This article uses a sample code that federated with OpenID Connect (Auth0). First you need to add an identity provider on IAM Identity provider page like below.

  • Provider type: OpenID Connect
  • Provider URL: Copy the domain of Auth0 application.
  • Audience: Copy of Client ID of Auth0.

Do not ended with / for Provider URL.

Next step is to enable the ID provider on the Cognito ID Provider page by creating id pool or using existed pool.

To obtain temporary security credentials from Cognito ID Pool, follow the Identity pools (federated identities) authentication flow. JavaScript sample code is like below.

const client = new CognitoIdentityClient({ region: "us-east-1" });
const getIdCommand = new GetIdCommand({
  IdentityPoolId: "us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  Logins: {
    "AUTH0_DOMAIN.auth0.com": idToken,
  },
});
const cognitoId = await client.send(getIdCommand);
const getCredentialsForIdentityCommand = new GetCredentialsForIdentityCommand(
  {
    IdentityId: cognitoId.IdentityId,
    Logins: {
      "AUTH0_DOMAIN.auth0.com": idToken,
    },
  }
);
const awsCredentials = await client.send(getCredentialsForIdentityCommand);

awsCredentials has a temporary security credentials to access the AWS resources. You can access the AWS resources by the assumed role of authenticated user.

In case of using STS directly

To obtain the temporary security credentials from STS directly, use the AssumeRoleWithWebIdentity.

Without using Cognito ID pool like this, there is no role to assume, you need to follow the Creating a role for web identity or OIDC to create a role.

JavaScript sample code is like below.

const client = new STSClient({ region: "us-east-1" });
const command = new AssumeRoleWithWebIdentityCommand({
  RoleArn: "arn:aws:iam::XXXXXXXXXXXX:role/Auth0SampleRole",
  RoleSessionName: "Auth0AssumeRoleSession",
  WebIdentityToken: idToken,
});

But the Identity provider settings are also required, you must be all right because Cognito ID Pool has been configured already in the previous section.

Please execute this API, you must get following error.

{
    "errorType": "InvalidIdentityTokenException",
    "errorMessage": "No OpenIDConnect provider found in your account for https://AUTH0_DOMAIN.auth0.com/",
    "name": "InvalidIdentityTokenException",
    "$fault": "client",
    "$metadata": {
        "httpStatusCode": 400,
        "requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "Error": {
        "Type": "Sender",
        "Code": "InvalidIdentityToken",
        "Message": "No OpenIDConnect provider found in your account for https://AUTH0_DOMAIN.auth0.com/",
        "message": "No OpenIDConnect provider found in your account for https://AUTH0_DOMAIN.auth0.com/"
    },
    ...snip...  
}

This error says the API couldn’t find the OpenID Connect provider. This is strange because the identity provider was configured despite working with Cognito ID Pool correctly.

Answer

In fact, you need to add separate identity provider from Cognito ID Pool. Please try to add identity provider as follows. Don’t forget to add / at the end of provider url.

The role is needed to edit because the identity provider is configured. If this does not work, you may recreate the role.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam:XXXXXXXXXXXX:oidc-provider/dev-AUTH0_DOMAIN.auth0.com/"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "AUTH0_DOMAIN.auth0.com/:aud": "AUTH0_CLIENT_ID"
                }
            }
        }
    ]
}

You can get a success response.

What do you guess If the provider url ended with / using Cognito ID Pool?

{
    "errorType": "NotAuthorizedException",
    "errorMessage": "Token is not from a supported provider of this identity pool.",
    "name": "NotAuthorizedException",
    "$fault": "client",
    "$metadata": {
        "httpStatusCode": 400,
        "requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "__type": "NotAuthorizedException",
    ...snip...
}

“Token is not from a supported provider of this identity pool.” seems to say that the provider’s name of Authentication providers and Logins option of GetId are different. Please change the code to add / as follows and run it.

const getIdCommand = new GetIdCommand({
  IdentityPoolId: "us-east-1:XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
  Logins: {
-    "AUTH0_DOMAIN.auth0.com": idToken,
+    "AUTH0_DOMAIN.auth0.com/": idToken,
  },
});

const getCredentialsForIclient. Sendand = new GetCredentialsForIdentityCommand(
  {
    IdentityId: cognitoId.IdentityId,
    Logins: {
-    "AUTH0_DOMAIN.auth0.com": idToken,
+    "AUTH0_DOMAIN.auth0.com/": idToken,
    },
  }
);

You’ll get this error.

{
    "errorType": "NotAuthorizedException",
    "errorMessage": "Invalid login token. Issuer doesn't match providerName",
    "name": "NotAuthorizedException",
    "$fault": "client",
    "$metadata": {
        "httpStatusCode": 400,
        "requestId": "XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX",
        "attempts": 1,
        "totalRetryDelay": 0
    },
    "__type": "NotAuthorizedException",
    ...snip...
}

Now the Cognito ID Pool could find the identity provider, but it mismatches with issuer of ID token due to add /. ID token issued by Auth0 contains iss claim as follows. The iss claim contains /.

{
  "iss": "https://AUTH0_DOMAIN.auth0.com/",
}

I think the Cognito ID Pool compare with https://${Identity provider domain}/ and iss claim of ID token. When you add / at the end of provider url, duplicated / prevent the API from verification. Maybe this behavior will change again for ID providers that do not have / in the iss claim.

If you meet those errors, you can resolve the problems with this article’s information.

Advertisement

AWS STS is NOT able to accept the access token issued by Auth0 to create temporary security credentials.

Summary

When AssumeRoleWithWebIdentity on AWS STS (Security Token Service), STS can NOT validate the aud claim of access token issued by Auth0.

The solutions are, using ID token or Lambda Authorizer.

How to Reproduce

This post shows a sample API that responds to the result using the AWS resources with Auth0 authentication.

Building a frontend

You need to build a frontend app and configure Auth0 by reading the Auth0 Quickstarts.

Please refer to Auth0 React SDK Quickstarts: Call an API to call an API from the frontend app.

Configure Auth0 API

To open the API for an authenticated user of Auth0, create the API on the Auth0 dashboard.

API settings on Auth0 dashboard

You can use Identifier as you want, the Auth0 recommends API endpoint is preferred to use it. This article uses https://example.com/ as Identifier.

Configure the AWS

Setting Identity providers

After this step, this app will pass the access token issued by Auth0 to the AWS STS. Therefore, you need to add an Auth0 as an identity provider on AWS refer to Creating IAM identity providers.

Go to the “Identity providers” on IAM console, add provider as below.

IAM identity provider settings
  • Provider type: Open ID Connect
  • Provider URL: Copy the domain from Auth0 dashboard.
  • Audience: Copy the API Audience from Auth0 dashboard.

Creating a role to delegate permissions to an AWS service

On the identity providers page, push “Assign role” to create a new role. You need to assign the policies for the AWS resources you want to use. See Creating a role for web identity or OIDC.

Sample trust relationships is as below.

{
    "Version": "2012-10-17",
    "Statement": [
        {
            "Effect": "Allow",
            "Principal": {
                "Federated": "arn:aws:iam::XXXXXXXXXXXX:oidc-provider/YOUR_AUTH0_DOMAIN.auth0.com"
            },
            "Action": "sts:AssumeRoleWithWebIdentity",
            "Condition": {
                "StringEquals": {
                    "YOUR_AUTH0_DOMAIN.auth0.com:aud": "https://example.com/"
                }
            }
        }
    ]
}

Creating API

When creating API on lambda, you can access the AWS resources by attaching the policy.

But on your server or GCP cloud functions, you can’t do it.

In this case, there is a way to access the AWS resources via the access key issued by STS.

This is an example of accessing AWS resources. When requested, this API receives access token, API requests to exchange the access token for AWS temporary credentials.

const jose = require("jose");
const {
  STSClient,
  AssumeRoleWithWebIdentityCommand,
} = require("@aws-sdk/client-sts");

exports.handler = async (event, context) => {
  const accessToken = event.headers.authorization.split(" ")[1];
  console.log("[Access Token] ", accessToken);

  const JWKS = jose.createRemoteJWKSet(
    new URL("https://YOUR_AUTH0_DOMAIN.auth0.com/.well-known/jwks.json")
  );

  // Verifying access token
  const { payload, protectedHeader } = await jose.jwtVerify(accessToken, JWKS, {
    issuer: "https://YOUR_AUTH0_DOMAIN.auth0.com/",
    audience: "https://example.com/",
  });
  console.log("[Protected Header] ", protectedHeader);
  console.log("[Payload] ", payload);

  // Requesting temporary security credentials from STS
  const client = new STSClient({ region: "us-east-1" });
  const command = new AssumeRoleWithWebIdentityCommand({
    RoleArn: "arn:aws:iam::XXXXXXXXXXXX:role/Auth0SampleRole",
    RoleSessionName: "Auth0AssumeRoleSession",
    WebIdentityToken: accessToken,
  });

  try {
    const awsCredentials = await client.send(command);
    console.log("[STS Credentials] ", awsCredentials);

    // DO Something with AWS Resource
  } catch (error) {
    // error handling.
    console.log("[STS Error] ", error);
  }

  return {};
};

Error will occur

When this API is executed, the error like below will occur.

InvalidIdentityTokenException: Incorrect token audience
{
  '$fault': 'client',
  '$metadata': {
    httpStatusCode: 400,
    requestId: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
    extendedRequestId: undefined,
    cfId: undefined,
    attempts: 1,
    totalRetryDelay: 0
  },
  Error: {
    Type: 'Sender',
    Code: 'InvalidIdentityToken',
    Message: 'Incorrect token audience',
    message: 'Incorrect token audience'
  },
  RequestId: 'XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX',
  xmlns: 'https://sts.amazonaws.com/doc/2011-06-15/'
}

“Incorrect token audience” means aud claim of access token and audience of identity provider does not match.

Why does this error occur?

You can see the data what the access token contains in jwt.io. The access token issued by Auth0 has aud claim like that.

"aud": [
  "https://example.com/",
  "https://YOUR_AUTH0_DOMAIN.auth0.com/userinfo"
],

The aud claim identifies the recipients that the JWT is intended for. When AssumeRoleWithWebIdentity, STS verifies that matching between “aud claim of access token” and “audience of identity provider”.

In general case, when the API responses “Incorrect token audience” message, you need to check the 2 parameters are same. But this sample’s audience parameter is contained in the aud claims correctly.

In fact, STS will always fail validation if the aud token is specified as an array.

The aud claim can contain an array of strings or a single string value defined in RFC 7519. However, STS accepts single string only. When using ID token instead of access token, you can see that STS accepts its.

I contacted AWS support and was told that STS cannot accept an array of aud claims. This behavior is also same for Cognito ID pools.

Alternative Solutions

However, you may not face this situation normally, I show you alternative solutions.

Using ID Token

If your API is public within same domain of frontend app, using ID token is suitable to use. Because the ID token issued by Auth0 contains single string aud claim, STS can accept the token.

Using Lambda Authorizer

In other ways, you can use lambda authorizer to verify the access token yourself.

Secure AWS API Gateway Endpoints Using Custom Authorizers