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