Authentication and authorization with AWS Amplify under the hood

One problem that we engineers who worked in traditional environments constantly stumble upon (or at least get a bit overwhelmed) “another framework? how does the system work under these frameworks?” For a lot of engineers, understanding how the system works under the hood is very important for us. In this article, let me draw a diagram to explain “a life of authentication and authorization” when we use the amplify-js framework.

A hypothetical SPA app setup:

(This section is to setup a context, as a foundation to discuss in the next section — skip it if you don’t have much time)

We assume that a company wanted to build a single page application i.e, SPA (native app follows the same idea of SPA discussed in this article) and the backend services are hosted on AWS.

When we use amplify-cli to add the auth backend,

amplify add auth

we basically architected the Cognito User Pool and Cognito Identity Pool in our stack.

Note: Amplify-cli is a very opinionated here because your application could work without having Cognito Identity Pool at all (for example, you don’t need to use signed request to access AWS resource directly). Therefore, in our previous article, I mentioned that you most likely will not use Amplify-cli a large team project to maintain your architecture choice — also, that opinion seems not alone.

When we use amplify-cli to add an API backend (selecting Rest),

amplify add api

? Please select from one of the below mentioned services
  GraphQL
❯ REST

we basically added an API-Gateway (with Lambda Proxy Integration as its backend — another opinionated architecture choice).

Next, we can use amplify-cli to add an S3 storage backend — maybe we can build a photo sharing app 😉

amplify add storage

? Please select from one of the below mentioned services (Use arrow keys)
❯ Content (Images, audio, video, etc.)
  NoSQL Database

A life of authentication and authorization under the hood

(The previous section was simply to set up a backend architecture for an SPA so that we can discuss how authentication and authorization works). And let’s walk through the under the hood workflow step by step.

  1. When we type www.myspa.com, we got our front-end on to our device;

  2. Then we (e.g., type in user name / password to) authenticate ourselves to the Cognito User Pool:

    • if we use the amplify library, this can be achieved by Auth.signIn()

    • at the end of this approach we get three tokens, the details of those tokens are described here (tokens from a typical OpenID Connect provider — Cognito User Pool can be used as an OIDC IdP provider): id_token, access_token, refresh_token;

      • once the user authenticated, these tokens can be obtained (be renewed if needed) through the SDK’s API call of currentSession()

  3. After that, the amplify automatically gets the AWS temporary credentials from Cognito Identity Pool under the hood for you — Auth.signIn() will do this automatically for us:

    • AWS temporary credentials contains accessKeyId, secretAccessKey, sessionToken;

    • with valid session information, through the SDK’s API call of currentCredentials(), we can obtain (renew if needed) the AWS temporary credentials;

  4. For API-Gateway call authorization, there are two choices of authorizers, and three choices of mechanisms:

    • AWS_IAM based authorizer, and this is the default option in Amplify setup

    • Cognito authorizer

      • either based using id_token as header payload (see below)

      • either based using access_token as header payload (see below)

    Amplify.configure({
      API: {
        endpoints: [
          {
            name: "sampleCloudApi",
            endpoint: "https://xyz.execute-api.us-east-1.amazonaws.com/Development",
            custom_header: async () => { 
              return { Authorization : 'token' } 
              // Alternatively, with Cognito User Pools use this:
              // return { Authorization: `Bearer ${(await Auth.currentSession()).getAccessToken().getJwtToken()}` }
              // return { Authorization: `Bearer ${(await Auth.currentSession()).getIdToken().getJwtToken()}` }
            }
          }
        ]
      }
    });
  5. For directly access other AWS services (other than Appsync and API-Gateway), the authorization is the signed API call approach, in which the IAM based authorization play the role under the hood. In our use case, the Amplify Storage uses the signed API approach under the hood.

Summary

  • Cognito User Pool is the service for users authenticate themselves

    • it hands out tokens to clients so that the client can show their identity to the down stream services (in the case we presented here, Cognito Identity Pool and API-Gateway)

  • Cognito Identity Pool is the service for enabling AWS service authorization

    • it hands out AWS temporary credentials to the client, with which the client can sign the API calls for authorization purpose

  • If you use Amplify-cli to setup your project, it has opinionated architecture design under the hood for you if you use the out-of-box setup

    • and hopefully this article helped you to understand what it chose for you;

    • how the authentication and authorization process works under the hood.

Sign up now so you don’t miss further issue.

In the meantime, tell your friends!