tanksta.github.io

View on GitHub
6 February 2020

Multi-Tenant/ Multi- User Pool Authorization with Amplify API

by tanksta

Multi- Tenant/User Pool Amplify API

The topic of a multi tenant api, that allows access from different separated Cognito User Pools or identity provider, is shrouded in confusion. First of all we need to separated between authentication and authorization. Authentication ensures that a user is, who he claims to be. Authorization ensures that an authenticated user only retrieves the data or information, he is allowed to see.

AWS Amplify claims to allow multiple authorization types (Cognito User Pools, IAM, API key and OIDC). The authorization works well if you combine one User Pool and an API key authorization or API key and OIDC authorization. However, if you allow authorization via Cognito User Pools and try to add a second User Pool in the AppSync Console the default User Pool works, but users from the second configured User Pool are not recognized as authorized users. I can not say if this was misconfiguration or if it is just not supported to add more than one User Pool, but I could not get it to run.

But there is another solution, that seems to be even more elegant, and allows to authorize users from several user pools and/or federate identity providers as well.

Overview

This architecture approach allows the client to authenticate at one of multiple User Pools and gets an access token and an id token in return. With the id token, the client fetches credentials from the Cognito Identity Pool. With these credentials the GraphQL API is called to access the data for authorized users with the role Authenticated Role. Furthermore, this solution allows unauthenticated users to access the public part of the API, by fetching temporarily credentials to call the public API with the role Unauthenticated Role.

Architecture Overview

Backend

Therefore we setup the API to use IAM as default authorization method.

	
	$ amplify add api   // or: $ amplify update api
	
	? Please select from one of the below mentioned services (Use arrow 	keys)
	 	GraphQL
  		REST

	? Please select from one of the below mentioned services GraphQL
	? Provide API name: multitenantAPI
	? Choose an authorization type for the API (Use arrow keys)
	 	API key
  		Amazon Cognito User Pool
		iam
	
	

In the script above, IAM is specified as the default authorization method, which allows you to distinguish between “UnAuthenticated Role” and “Authenticated Role”. When used in conjunction with amplify add auth the amplify CLI generates scoped down IAM policies for the “Un/Authenticated” role automatically.

To assume these roles for our authenticated user we need to setup an Cognito Identity Pool, that holds all authentication providers (e.g. Cognito User Pools or federate identity providers).

If we use Amplify Authentication, Amplify CLI will setup a terraform stack, that includes a Cognito User Pool, a Cognito Identity Pool, and the two IAM roles.

	$ amplify add auth // Select cognito default or with federate identities or manual configuration <-- recommended to understand the magic

Note: If you are using standard Cognito authentication, the allowUnauthenticatedIdentities parameter (see amplify/backend/auth/{terraformFolder})/parameters.json) in terraform is set to false. You can either change the parameter in the parameter.json file or by enabling the checkbox on the created Identity Pool in the AWS Console.

amplify push your changes on the stack.

Now we have setup a GraphQL API with IAM authorization, IAM roles, a Cognito User Pool and an Cognito Identity Pool. To add another User Pool or federate identity provider, go to the AWS Console to Cognito -> Manage identity pools -> Select the created identity pool -> Edit identity pool and add a new user pool in the section Authentication providers.

AWS Console: Identity Pool Configuration

Frontend

App Flow:

  1. Select User Pool to authenticate at. Provide login for each User Pool and update Auth of Amplify SDK.
  2. Authenticate with Auth.signIn(username,password) and obtain access token and id token.
  3. Get credentials from the Cognito Identity Pool (request with id token from 2.)
  4. Call GraphQL API with credentials
	 Auth.configure({
            Auth: {
                identityPoolId: '**-****-*:********',
                region: 'eu-central-1',
                userPoolWebClientId: tenant.userPoolWebClientId, // <-- HERE: set user pools web client identity before sign in
                userPoolId: tenant.userPoolId  // <-- HERE: set user pool before sign in
            }
        });
	
	Auth.signIn(username, password);
	

The aws-amplify SDK (see: Node Package Manger) provides/ handles functionality to retrieve credentials from the Identity Pool, by calling

	
	await Auth.currentCredentials();
	

However, if you do not use aws-amplify.Auth you can retrieve credentials via aws-sdk (see: Node Package Manager) as well

	AWS.config.region = 'us-east-1';

	// Configure the credentials provider to use your identity pool
	AWS.config.credentials = new AWS.CognitoIdentityCredentials({
   	IdentityPoolId: 'IDENTITY_POOL_ID',
		// skip Logins if you want credentials for unauthenticated user
    	Logins: { // optional access/id jwt-tokens, used for authenticated login
        'graph.facebook.com': 'FBTOKEN',     // or
        'www.amazon.com': 'AMAZONTOKEN',     // or
        'accounts.google.com': 'GOOGLETOKEN' 
    	}
	});

	// Make the call to obtain credentials
	await AWS.config.credentials.getPromise();

   // Credentials will be available after this function is called.
   return {
		AWS.config.credentials.accessKeyId;
   		AWS.config.credentials.secretAccessKey;
   		AWS.config.credentials.sessionToken;
	}

});

Now that we have the credentials for either an unauthenticated or authenticated user, it is time to query our API.

	const client = new AWSAppSyncClient({
  		url: awsconfig.aws_appsync_graphqlEndpoint,
  		region: awsconfig.aws_appsync_region,
  		auth: {
    		type: AUTH_TYPE.AWS_IAM,
    		credentials: async() => await Auth.currentCredentials(), // <— HERE: set credentials
  		},
	});

Note: Auth.currentCredentials() will provide credentials for authenticated user, but also credential for unauthenticated user, if the user did not sign in with Auth.signIn().

Sum it up

We created a backend stack with Amplify containing a Cognito User Pool, Cognito Identity Pool and a GraphQL API with IAM authorization. We furthermore can add another User Pool or federate identity provider (e.g. from other tenants) to our Identity Pool.

On client side we select the User Pool to authenticate against and get the credentials to query our GraphQL API. That’s all, now your app is ready to authenticate and authorize users from multiple identity providers/ User Pools.

tags: aws - cloud - development - Amazon Web Services - Amplify - Cognito - GraphQL