Using AWS Cognito App Client Secret Hash with Go

Mateusz Charytoniuk - Sep 16 '20 - - Dev Community

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
Enter fullscreen mode Exit fullscreen mode

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.
Enter fullscreen mode Exit fullscreen mode

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 ):

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)
    }
}

Enter fullscreen mode Exit fullscreen mode

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.

. . . . . . . . . . . . . . . . . . . . . . . .