Golang: AWS SDK, Cognito and API Gateway

The situation is as follows:

  1. Create an application with the serverless framework.  This uses API Gateway, Lambda, and all kinds of cool stuff.
  2. Authenticate on the application using Cognito.
  3. Write a client that can call the API created by API gateway in Go.

Steps 1-2 are covered everywhere on the internet.  My favorite reference is this serverless stack tutorial.  It is gold and covers so much.

But step 3 is pretty poorly documented.  What I found are several old libraries that do v4 signing but are no longer maintained and shouldn’t be used.  Below I document a solution that works using the AWS Go SDK.

1. Cognito Setup

User Pool:  I have a user pool and a corresponding App Client.  The App Client for the command line Go code that I’m writing is separate from the App Client that is used by the web interface.  You’ll need to note both of these:

  • UserPoolId: us-east-1_123456789
  • AppClientId: 123456789abcdefghijklmnopq

Federated Identity: In addition an identity pool which uses the user pool ID should be created.  This will also give you an identification

  • IdentityPoolId: us-east-1:abc13813-4444-4444-4444-123456789abc

Make sure you can login and out and that this cognito stuff works.  I used the serverless stack tutorial above and I could log in and out with the web interface.

2. API Gateway Setup

I’m using the serverless framework to create my stack. A few notes about it:

  1. The Method Request should specify that it requires Auth: AWS_IAM
  2. The Cognito Identity pool has an authenticated and unathenticated role.  Make sure that role has the proper permissions to call the lambda functions.

As an example, my cognito identity pool authenticated role has the following properties:

You would make sure the execute-api is set to your correct API Gateway ID.  You can find this in the stages portion where you invoke the API:

  • InvokeURL: https://123456789.execute-api.us-east-1.amazonaws.com/dev

3. Golang

By leaving out the error checking and structure I’ll make this as simple as possible to do.  You’ll probably want to do some caching of some sorts and of course check for errors.  In the steps below we will turn our Cognito username and password into IAM credentials that assume the role of executing the API, after which we will use them invoke the API.

3.1 Create aws session

3.2 Authenticate user from Cognito Identity Provider

Your cognito user has a username and password.  (I’m using an email).  Authenticate this:

The AuthResp will contain the IdToken, AccessToken, RefreshToken, etc.  What you need is an IAM user.  Notice that you’re just using the AppClientID and not the UserPoolId.  I thought this was a little strange but since the AppClientId belongs to the UserPoolId I guess it works.

3.3 Get ID from Cognito Identity

This section follows (to some extent) the documentation in the Overview section of the cognito Identity API Reference.  (Seriously, Amazon, a few examples would be nice) Now that we have the IdToken we can use that to get the ID of the user:

This result gives us a user id which we can now get credentials:

Cool, now if you look at credRes you have IAM AccessKeyId, SecretKey, SessionToken!  Everything we need to now call our API.

3.4 Invoke the API Gateway with a Signed Request

To invoke the API we create a new request like we would if it were unauthenticated:

In this case I’m calling the “/list” resource and using the GET method.

Now the trick here is that we need to sign the request.  With the SDK we now have a library that does that for us:

Notice that we pass in the req variable.  This step will add headers to the request that will authorize our request.  Finally do the request:

Check out the response:

If all went well you have just sent an authenticated call to your API using the AWS SDK for Go.

4. Parting Thoughts

This is very complicated, but hopefully very secure!  I’m putting this here as I saw very little documentation.  One example goes a long way to drive home understanding and I’m hoping I can save someone else some time.  Having never done this it took me 2 days to figure this out! There seems to be other methods of accomplishing this as well.  For example, in API Gateway you can configure an authorizer that can accept just the IdToken from the Cognito User.  Using that method you could skip 3.3 and just add a header instead of using the v4 library in 3.4.  However, I already had this API setup for the web interface and didn’t want to change what it had.