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.
Top comments (5)
thanks, really helpful
Have you tried this lately? i can't make it work.
Thank you for pointing out the issue with the "client_secret".
Thanks a lot. it is a life saver article :)
thanks ! Has been useful :)