{"id":3714,"date":"2019-03-20T10:29:24","date_gmt":"2019-03-20T16:29:24","guid":{"rendered":"http:\/\/benincosa.com\/?p=3714"},"modified":"2019-03-20T10:29:24","modified_gmt":"2019-03-20T16:29:24","slug":"golang-aws-sdk-cognito-and-api-gateway","status":"publish","type":"post","link":"https:\/\/benincosa.com\/?p=3714","title":{"rendered":"Golang: AWS SDK, Cognito and API Gateway"},"content":{"rendered":"<p>The situation is as follows:<\/p>\n<ol>\n<li>Create an application with the <a href=\"https:\/\/serverless.com\">serverless<\/a> framework.\u00a0 This uses API Gateway, Lambda, and all kinds of cool stuff.<\/li>\n<li>Authenticate on the application using Cognito.<\/li>\n<li>Write a client that can call the API created by API gateway in Go.<\/li>\n<\/ol>\n<p>Steps 1-2 are covered everywhere on the internet.\u00a0 My favorite reference is this <a href=\"https:\/\/serverless-stack.com\/\">serverless stack tutorial<\/a>.\u00a0 It is gold and covers so much.<\/p>\n<p>But step 3 is pretty poorly documented.\u00a0 What I found are several old libraries that do v4 signing but are no longer maintained and shouldn&#8217;t be used.\u00a0 Below I document a solution that works using the <a href=\"https:\/\/docs.aws.amazon.com\/sdk-for-go\/api\/\">AWS Go SDK<\/a>.<\/p>\n<h2>1. Cognito Setup<\/h2>\n<p><strong>User Pool<\/strong>:\u00a0 I have a user pool and a corresponding App Client.\u00a0 The App Client for the command line Go code that I&#8217;m writing is separate from the App Client that is used by the web interface.\u00a0 You&#8217;ll need to note both of these:<\/p>\n<ul>\n<li>UserPoolId: us-east-1_123456789<\/li>\n<li>AppClientId: 123456789abcdefghijklmnopq<\/li>\n<\/ul>\n<p><strong>Federated Identity<\/strong>: In addition an identity pool which uses the user pool ID should be created.\u00a0 This will also give you an identification<\/p>\n<ul>\n<li><span id=\"txtPoolId\" class=\"cog-code-inline\">IdentityPoolId: us-east-1:abc13813-4444-4444-4444-123456789abc<\/span><\/li>\n<\/ul>\n<p>Make sure you can login and out and that this cognito stuff works.\u00a0 I used the serverless stack tutorial above and I could log in and out with the web interface.<\/p>\n<h2>2. API Gateway Setup<\/h2>\n<p>I&#8217;m using the serverless framework to create my stack. A few notes about it:<\/p>\n<ol>\n<li>The Method Request should specify that it requires Auth: AWS_IAM<\/li>\n<li>The Cognito Identity pool has an authenticated and unathenticated role.\u00a0 Make sure that role has the proper permissions to call the lambda functions.<\/li>\n<\/ol>\n<p>As an example, my cognito identity pool authenticated role has the following properties:<\/p>\n<pre class=\"lang:js decode:true \">{\r\n    \"Version\": \"2012-10-17\",\r\n    \"Statement\": [\r\n        {\r\n            \"Effect\": \"Allow\",\r\n            \"Action\": [\r\n                \"mobileanalytics:PutEvents\",\r\n                \"cognito-sync:*\",\r\n                \"cognito-identity:*\"\r\n            ],\r\n            \"Resource\": [\r\n                \"*\"\r\n            ]\r\n        },\r\n        {\r\n            \"Effect\": \"Allow\",\r\n            \"Action\": [\r\n                \"execute-api:Invoke\"\r\n            ],\r\n            \"Resource\": [\r\n                \"arn:aws:execute-api:us-east-1:*:123456789\/*\/*\/*\"\r\n            ]\r\n        }\r\n    ]\r\n}<\/pre>\n<p>You would make sure the execute-api is set to your correct API Gateway ID.\u00a0 You can find this in the stages portion where you invoke the API:<\/p>\n<ul>\n<li>InvokeURL: https:\/\/123456789.execute-api.us-east-1.amazonaws.com\/dev<\/li>\n<\/ul>\n<h2>3. Golang<\/h2>\n<p>By leaving out the error checking and structure I&#8217;ll make this as simple as possible to do.\u00a0 You&#8217;ll probably want to do some caching of some sorts and of course check for errors.\u00a0 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.<\/p>\n<h3>3.1 Create aws session<\/h3>\n<pre class=\"lang:go decode:true\">ses, _ := session.NewSession(&amp;aws.Config{Region: aws.String(\"us-east-1\")})<\/pre>\n<h3>3.2 Authenticate user from Cognito Identity Provider<\/h3>\n<p>Your cognito user has a username and password.\u00a0 (I&#8217;m using an email).\u00a0 Authenticate this:<\/p>\n<pre class=\"lang:go decode:true\">params := &amp;cognitoidentityprovider.InitiateAuthInput{\r\n    AuthFlow: aws.String(\"USER_PASSWORD_AUTH\"),\r\n    AuthParameters: map[string]*string{\r\n      \"USERNAME\": aws.String(\"maria@vontropps.com\"),\r\n      \"PASSWORD\": aws.String(\"doremefasolatido\"),\r\n    },\r\n    ClientId: aws.String(\"123456789abcdefghijklmnopq\"), \/\/ this is the app client ID\r\n  }\r\ncip := cognitoidentityprovider.New(ses)\r\nauthResp, _ := cip.InitiateAuth(params)<\/pre>\n<p>The AuthResp will contain the IdToken, AccessToken, RefreshToken, etc.\u00a0 What you need is an IAM user.\u00a0 Notice that you&#8217;re just using the AppClientID and not the UserPoolId.\u00a0 I thought this was a little strange but since the AppClientId belongs to the UserPoolId I guess it works.<\/p>\n<h3>3.3 Get ID from Cognito Identity<\/h3>\n<p>This section follows (to some extent) the documentation in the <a href=\"https:\/\/docs.aws.amazon.com\/sdk-for-go\/api\/service\/cognitoidentity\/\">Overview section of the cognito Identity API Reference<\/a>.\u00a0 (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:<\/p>\n<pre class=\"lang:go decode:true\">svc := cognitoidentity.New(ses)\r\nidRes, _ := svc.GetId(&amp;cognitoidentity.GetIdInput{\r\n    IdentityPoolId: aws.String(\"us-east-1:123456789-444-4444-123456789abc\"),\r\n    Logins: map[string]*string{\r\n      \"cognito-idp.us-east-1.amazonaws.com\/us-east-1_123456789\": authResp.AuthenticationResult.IdToken,\r\n    },\r\n  })<\/pre>\n<p>This result gives us a user id which we can now get credentials:<\/p>\n<pre class=\"lang:go decode:true \">credRes, _ := svc.GetCredentialsForIdentity(&amp;cognitoidentity.GetCredentialsForIdentityInput{\r\n    IdentityId: idRes.IdentityId,\r\n    Logins: map[string]*string{\r\n      \"cognito-idp.us-east-1.amazonaws.com\/us-east-1_123456789\": authResp.AuthenticationResult.IdToken,\r\n    },\r\n  })<\/pre>\n<p>Cool, now if you look at credRes you have IAM AccessKeyId, SecretKey, SessionToken!\u00a0 Everything we need to now call our API.<\/p>\n<h3>3.4 Invoke the API Gateway with a Signed Request<\/h3>\n<p>To invoke the API we create a new request like we would if it were unauthenticated:<\/p>\n<pre class=\"lang:go decode:true \">url := \"https:\/\/123456789.execute-api.us-east-1.amazonaws.com\/dev\/list\"\r\nclient := new(http.Client)\r\nreq, _ := http.NewRequest(\"GET\", url, nil)<\/pre>\n<p>In this case I&#8217;m calling the &#8220;\/list&#8221; resource and using the GET method.<\/p>\n<p>Now the trick here is that we need to sign the request.\u00a0 With the SDK we now have a library that does that for us:<\/p>\n<pre class=\"lang:go decode:true\">v := v4.NewSigner(credentials.NewStaticCredentials(\r\n    *credRes.Credentials.AccessKeyId,\r\n    *credRes.Credentials.SecretKey,\r\n    *credRes.Credentials.SessionToken,\r\n  ))\r\n\r\nv.Sign(req, nil, \"execute-api\", \"us-east-1\", time.Now())<\/pre>\n<p>Notice that we pass in the req variable.\u00a0 This step will add headers to the request that will authorize our request.\u00a0 Finally do the request:<\/p>\n<pre class=\"lang:go decode:true\">resp, _ := client.Do(req)<\/pre>\n<p>Check out the response:<\/p>\n<pre class=\"lang:go decode:true\">b, _ := ioutil.ReadAll(resp.Body)\r\nresp.Body.Close()\r\nfmt.Printf(\"%s\\n\", b)<\/pre>\n<p>If all went well you have just sent an authenticated call to your API using the AWS SDK for Go.<\/p>\n<h2>4. Parting Thoughts<\/h2>\n<p>This is very complicated, but hopefully very secure!\u00a0 I&#8217;m putting this here as I saw very little documentation.\u00a0 One example goes a long way to drive home understanding and I&#8217;m hoping I can save someone else some time.\u00a0 Having never done this it took me 2 days to figure this out! There seems to be other methods of accomplishing this as well.\u00a0 For example, in API Gateway you can configure an authorizer that can accept just the IdToken from the Cognito User.\u00a0 Using that method you could skip 3.3 and just add a header instead of using the v4 library in 3.4.\u00a0 However, I already had this API setup for the web interface and didn&#8217;t want to change what it had.<\/p>\n<p>&nbsp;<\/p>\n<p>&nbsp;<\/p>\n","protected":false},"excerpt":{"rendered":"<p>The situation is as follows: Create an application with the serverless framework.\u00a0 This uses API Gateway, Lambda, and all kinds of cool stuff. Authenticate on the application using Cognito. Write a client that can call the API created by API gateway in Go. Steps 1-2 are covered everywhere on the internet.\u00a0 My favorite reference is&#8230;<\/p>\n","protected":false},"author":1,"featured_media":0,"comment_status":"open","ping_status":"closed","sticky":false,"template":"","format":"standard","meta":[],"categories":[463,889],"tags":[891,1010,890,779,850,892],"jetpack_featured_media_url":"","_links":{"self":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/3714"}],"collection":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts"}],"about":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/types\/post"}],"author":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/users\/1"}],"replies":[{"embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcomments&post=3714"}],"version-history":[{"count":1,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/3714\/revisions"}],"predecessor-version":[{"id":3715,"href":"https:\/\/benincosa.com\/index.php?rest_route=\/wp\/v2\/posts\/3714\/revisions\/3715"}],"wp:attachment":[{"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fmedia&parent=3714"}],"wp:term":[{"taxonomy":"category","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Fcategories&post=3714"},{"taxonomy":"post_tag","embeddable":true,"href":"https:\/\/benincosa.com\/index.php?rest_route=%2Fwp%2Fv2%2Ftags&post=3714"}],"curies":[{"name":"wp","href":"https:\/\/api.w.org\/{rel}","templated":true}]}}