Using AWS Cognito App Client Secret Hash with Go
Tags: aws, go, cognito
Published: 2020-09-16 14:34:10

If you ever tried to use Cognito with Go (or maybe other languages) you might have encountered this error:

```
NotAuthorizedException: Unable to verify secret hash for client XXXXXXXXXXXX
```

I did too. When I searched for answers the typical recommendation is to disable the app client secret, which I think is less than perfect. Every bit of additional security helps. 

I am using username / password authentication. According to the documentation here we need to send secret hash:

https://docs.aws.amazon.com/sdk-for-go/api/service/cognitoidentityprovider/#InitiateAuthInput

```
//    * For USER_SRP_AUTH: USERNAME (required), SRP_A (required), SECRET_HASH
//    (required if the app client is configured with a client secret), DEVICE_KEY.
```

The problem is, documentation could be better I think (it does not mention how the hash should be computed - at least in that place), because the first thing I tried to do is just to use the app client secret itself while sending requests to AWS Cognito. In fact, we need base64 encoded HMAC-SHA-256 hash. The same applies not only to Go, but also other languages.

The actual documentation on how to compute secret hash is here:
https://docs.aws.amazon.com/cognito/latest/developerguide/signing-up-users-in-your-app.html#cognito-user-pools-computing-secret-hash

Simple Go implementation might look like this (I used this code as a base: https://github.com/br4in3x/golang-cognito-example/blob/master/app/login.go ):

```go
package main

import (
	"crypto/hmac"
	"crypto/sha256"
	"encoding/base64"
	"fmt"

	"github.com/aws/aws-sdk-go/aws"
	"github.com/aws/aws-sdk-go/aws/session"
	"github.com/aws/aws-sdk-go/service/cognitoidentityprovider"
)

// I hard-coded those here just to demonstrate how to make 
// Cognito API calls. You should store this information 
// in other way, because this way it would be possible 
// to extract those strings from your compiled app if it 
// leaks somewhere.
const clientId = "XXXXXXXXXXXX"
const clientSecret = "XXXXXXXXXXXX"
// This is the username and password of a user from your
// Cognito user pool.
const username = "XXXXXXXXXXXX"
const password = "XXXXXXXXXXXX"

func main() {
	conf := &aws.Config{Region: aws.String("XXXXXXXXXXXX")}
	sess := session.Must(session.NewSession(conf))

	// This is the part where we generate the hash.
	mac := hmac.New(sha256.New, []byte(clientSecret))
	mac.Write([]byte(username + clientId))

	secretHash := base64.StdEncoding.EncodeToString(mac.Sum(nil))

	cognitoClient := cognitoidentityprovider.New(sess)

	authTry := &cognitoidentityprovider.InitiateAuthInput{
		AuthFlow: aws.String("USER_PASSWORD_AUTH"),
		AuthParameters: map[string]*string{
			"USERNAME": aws.String(username),
			"PASSWORD": aws.String(password),
			"SECRET_HASH": aws.String(secretHash),
		},
		ClientId: aws.String(clientId),
	}

	res, err := cognitoClient.InitiateAuth(authTry)
	if err != nil {
		fmt.Println(err)
	} else {
		fmt.Println("authenticated")
		fmt.Println(res.AuthenticationResult)
	}
}

```

To sum up, please do not disable secret in your app client. Just generate the hash. If you have any questions, let me know in the comments section.