AWS Cognito refresh token fails on secret hash - go

I have a problem refreshing an AWS Cognito token using server side authentication in Go. I am able to get the id_token, access_token and refresh_token with the cognitoidentityprovider.AdminInitiateAuth method. I have create a User Pools Client with secrets so I have to provide the SECRET_HASH in the AuthParameters.
This all works fine when logging in, but the same secret hash doesn't work when refreshing the tokens. I have tripple checked the code and verified the secret hashes I send are the same when logging in and when refreshing the token (it should be the same since it uses the username, clientID and clientSecret which don't change).
The AWS API returns the following error:
{
"error": "NotAuthorizedException: Unable to verify secret hash for client myClientIdHere\n\tstatus code: 400, request id: c186ecf2-57a7-11e8-a01e-f97ed64650c9"
}
I have checked that device tracking is off as the documentation mentions that this is a problem when refreshing tokens on the server-side (note under "Admin authentication flow", https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html#amazon-cognito-user-pools-server-side-authentication-flow).
My refresh code is:
AWSRefreshToken := aws.String(refreshToken)
secretHash := secretHash(email, auth.Config.ClientID, auth.Config.ClientSecret)
AWSUserPoolID := aws.String(auth.Config.UserPoolID)
input := cognitoidentityprovider.AdminInitiateAuthInput{
AuthFlow: aws.String("REFRESH_TOKEN_AUTH"),
AuthParameters: map[string]*string{
"REFRESH_TOKEN": AWSRefreshToken,
"SECRET_HASH": &secretHash,
},
ClientId: &auth.Config.ClientID,
UserPoolId: AWSUserPoolID,
}
output, err := auth.AWSCognitoIdentityProvider.AdminInitiateAuth(&input)
The secret hash code (from https://stackoverflow.com/a/46163403/3515197):
func secretHash(username, clientID, clientSecret string) string {
mac := hmac.New(sha256.New, []byte(clientSecret))
mac.Write([]byte(username + clientID))
return base64.StdEncoding.EncodeToString(mac.Sum(nil))
}
I have checked other Stack Overflow questions but they only mention the device tracking problem and that the secret hash is needed. What am I missing here?

Use user id (sub) instead of email while generating secret hash.
secretHash := secretHash(sub, auth.Config.ClientID, auth.Config.ClientSecret)

I got the answer for this after smashing my head for a few hours. You gotta use the SUB as the username when getting the refresh token during the REFRESH_TOKEN_AUTH/REFRESH_TOKEN flow. I do not see this documented anywhere, and it's not returned when logging in via email as username, so it's super confusing. Do this: After you log a user in, do a GetUser call serverside with the newly minted accessToken to get that user's USERNAME which will be the value of that user's SUB in the cognito dashboard... WHY IS IT NOT JUST THE EMAIL ADDRESS!? GREAT QUESTION. Store the username (aka: the sub) in the user's cookie, so when they need the refresh token, you can use the sub as the username here. sheesh... this was super confusing.

This is the case as well in Javascript. At work we have a Cognito User Pool with the Username attribute set to email, see Screenshot.
In my setup, some APIs can use the email to compute the Secret Hash but NOT initiateAuth(REFRESH_TOKEN_AUTH/REFRESH_TOKEN).
Here is a list of APIs that did work with the email as my username to compute:
initiateAuth(USER_PASSWORD_AUTH)
signUp
forgotPassword
confirmForgotPassword
confirmSignUp
resendConfirmationCode

Related

Golang - spotify : This request requires user authentication

I'm writing a bot that takes the name of the music and the artist and adds it to the selected playlist with the zmb3 Spotify library. Everything works except the section add music to the playlist ;
the bot gives me this error: this request requires user authentication.
but Spotify token and Spotify secret are valid.
I searched, but I didn't get anything.
Is there anything more needed than Spotify Token and Spotify Secret?
What should I do?
Code :
// create spotify client connection and context to connect spotify
client, ctx := api.SpotifyConnection(SPOTIFYTOKEN, SPOTIFYSECRET)
// add playlist
playlist, err := client.GetPlaylist(ctx, PLAYLISTID)
if err != nil {
log.Fatal(err.Error())
}
/* SOME CODES */
if results.Tracks != nil {
items := results.Tracks.Tracks
musicID := items[0].ID.String()
cmd.AddMusic(client, ctx, playlist.ID.String(), musicID)
ERROR:
2021/12/26 11:06:25 This request requires user authentication.
Look at it this way - can you add a track to your playlist while not logged into your account? The same goes for your code. It's not an issue of authenticating to the API with TOKEN and SECRET_KEY, but you need to introduce user authentication, so that when a user opens your app, they log in and can add tracks to their own playlists.
There is an Authentication section in the zmb3 README that should help:
You can authenticate using a client credentials flow, but this does not provide any authorization to access a user's private data. For most use cases, you'll want to use the authorization code flow. This package includes an Authenticator type to handle the details for you.

Gmail API - Oauth2/google: no credentials found (Golang)

​Hello All,
Wanted to do server-side integration for Gmail API. The basic need is using enabling Gmail API I want to read my Gmail inbox for some analytics purpose.
Golang Package - "google.golang.org/api/gmail/v1"
As per documentation, I have followed the below steps.
New Signed up with Gmail
Added billing details to use GCP services
Created test project
Enabled Gmail API
Created Service Account and key inside it. Got credentials.json file
On the backend side, I am using the Golang package to extract the
Gmail inbox.
After successful integration, I am trying to run my code but getting
the below error
{"level":"error","msg":"Error while creating gmail service : oauth2/google: no credentials found","time":"2021-07-25T15:11:23+05:30"}
Can anyone help me to figure out what is missing?
I currently use OAuth with YouTube and the device flow [1], so maybe this can be helpful. First you need to make a one time request like this:
data := url.Values{
"client_id": {"something.apps.googleusercontent.com"},
"scope": {"https://www.googleapis.com/auth/youtube"},
}
res, err := http.PostForm("https://oauth2.googleapis.com/device/code", data)
You'll get a response like this:
type OAuth struct {
Device_Code string
User_Code string
Verification_URL string
}
which you can do a one time prompt to user like this:
1. Go to
https://www.google.com/device
2. Enter this code
HNDN-ZWBL
3. Sign in to your Google Account
Then, after user log in, you can do an exchange request like this:
data := url.Values{
"client_id": {"something.apps.googleusercontent.com"},
"client_secret": {"super secret"},
"device_code": {"device code from above"},
"grant_type": {"urn:ietf:params:oauth:grant-type:device_code"},
}
res, err := http.PostForm("https://oauth2.googleapis.com/token", data)
This will give you an access_token and refresh_token, that you can save locally for reuse. In addition to "device flow", you also have "native app" flow [2], but I found the former to be a simpler process.
https://developers.google.com/identity/sign-in/devices
https://developers.google.com/identity/protocols/oauth2/native-app

AWS Cognito Pre-Token Generation not adding custom claims to ID Token (with ALB setup + Auth Code flow)

I'm adding custom claims to Cognito's ID token using the "Pre Token Generation" trigger.
Problem
The lambda is triggered, but the issued ID Token doesn't include the claims I added. Am I missing something?
My setup
Using OAuth 2.0 with authorization code flow
My client app sits behind a load balancer (alb). The alb interacts with Cognito to get the Access + ID Tokens in the form of a ALBSessionCookie. Very similar to [0]
To get the ID Token, the client calls a custom endpoint to my backend with the ALBSessionCookie. The backend uses that cookie to return a decoded ID Token to the user. This is the ID Token that I expect should have the custom claims included.
[0] https://www.exampleloadbalancer.com/auth_detail.html
Lambda function (pre-token generation trigger)
Format taken from https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html#aws-lambda-triggers-pre-token-generation-example-1
exports.handler = (event, context, callback) => {
event.response = {
"claimsOverrideDetails": {
"claimsToAddOrOverride": {
"my-custom-claims-namespace": JSON.stringify({
"custom-claim-1": "hello",
"custom-claim-2": "hello",
})
}
}
}
callback(null, event)
}
If I can't make this work with ALB, what are some workarounds? Some ideas:
Call Cognito directly for an ID Token (somehow), hoping that will trigger the lambda to issue a JWT with the custom claims
Call Cognito via AmplifyJS
I have a feeling this is expected behavior, though seems like a limitation. Looking here:
https://www.exampleloadbalancer.com/auth_detail.html
We can see that the following steps occur:
ALB receives JWT (ID token, Access Token)
ALB to send access token
ALB receives user info(claims)
I believe the ALB is then not sending the contents of the Decoded ID token (That were manipulated by the Lambda trigger) back to the backend but instead sends the 'user info(claims)' (returned from the UserInfo endpoint) which are not effected by the Cognito trigger.
Yeah the ALB doesn't work that way, the ID Token that Lambda trigger customizes is the one you get when a user Authenticates. There are a couple of options.
Custom User Attributes
The least invasive IMO if instead of adding these attributes in the Lambda trigger, you could have them as custom attributes in Cognito, these I do believe will be in this token. You can sync these attributes at each successful Authorization. That may meet your requirements.
API GW
You could put an API GW either between your LB and your APP or infront of your LB. The API GW does give you a layer in which you can do all this stuff and more with customizing headers, tokens etc. For example you could have a Lambda Authorizer which reads this access token, and returns a context which you can reference in your integration requests back to your backend. It's a bit more involved and will add at least some latency to your app, although you can safely have a large TTL on your auth response because your LB is already doing Auth and you only want some extra attributes. You could also do a re-design and put this all in API GW and get all the bells and whistles it has but you might not need them.
But yeah probably easiest to use the first option if possible as that won't require you to do a redesign and you will just need to change your attribute names to custom:....

AWS Cognito: Add custom claim/attribute to JWT access token

My app creates a custom attribute "userType" for each new signed-up user. Now I would like this "userType" claim/attribute to be added to the JWT access token whenever the user signs in or the token gets refreshed.
Is there an option to tell cognito to add my custom claim/attribute to the JWT access token? (Without a pre token generation Lambda)
Custom attributes are not available in Cognito access token. Currently it is not possible to inject additional claims in Access Token using Pre Token Generation Lambda Trigger as well. PreToken Generation Lambda Trigger allows you to customize identity token(Id Token) claims only.
You can use ID token to get the token with custom attributes.
Access tokens are not intended to carry information about the user. They simply allow access to certain defined server resources.
You can pass an ID Token around different components of your client, and these components can use the ID Token to confirm that the user is authenticated and also to retrieve information about them.
How to retrieve Id token using amazon cognito identity js
cognitoUser.authenticateUser(authenticationDetails,{
onSuccess: function(result) {
var accessToken = result.getIdToken().getJwtToken();
console.log('accessToken is: ' + accessToken);
},
onFailure: function(err) {
alert(err.message || JSON.stringify(err));
},
});
I have the same problem when I want to create several microservice. There isn't a way I can customize an access token, but only an identity token. However, I use client credentials in the machine-to-machine which needs access token. So, in no way I can customize my token. At last, I decide to add such info(like user type) in the event header. It's not a very secure way compared to customize a token, but there isn't any other easy way to do it right now. Otherwise, I have to rewrite the authorizer in Cognito. Like rewriting a customize authorizer and it's very painful.
I have the same issue with Cognito; exist other tools like "PingFederate"Auth-server of Ping identity and Auth0 Auth-server; I know that the requirement isn't part of the standard, but these applications were my alternatives to fix this issue
The responses suggesting to use the ID Token for authorization in your backend systems are bad security practice. ID Tokens are for determining that the user is in fact logged in and the identity of that user. This is something that should be performed in your frontend. Access Tokens on the other hand are for determining that a request (to your backend) is authorized. ID Tokens do not have the same security controls against spoofing that Access Tokens have (see this blog from Auth0: https://auth0.com/blog/id-token-access-token-what-is-the-difference/).
Instead, I recommend that your backend accept an Access Token as a Bearer token via the Authorization HTTP header. Your backend then calls the corresponding /userinfo endpoint (see: https://openid.net/specs/openid-connect-core-1_0.html#UserInfo) on the authorization server that issued the Access Token, passing such said Access Token to that endpoint. This endpoint will return all of the ID Token information and claims, which you can then use to make authorization decisions in your code.

Trying to understand Laravel's Passport need for 5 tables in DB and why not in .env

I am building a REST API, no views, just routing for a front written in Angular.
For auth purposes, I need to create tokens, send them in the payload and verify them.
My question is why do I need to have 5 tables for this, would it be possible to store the secret on the ENV and just operate without calling the DB each time? I've been googling for answers on this without avail, hope to get clarity here. Thanks.
You require these tables. oauth_clients table stores your client_id and client_secret.
There are different type of clients available, in your case its better to use password client (where password_client set to 1 in auth_clients table).
{
"username": "your-laravel-username",
"password": "your-laravel-password",
"client_id": id-from-oauth-clients-table,
"client_secret": secret-from-oauth-cleints-table,
"grant_type": "password", //this is to use password client, search for password_client - 1 in oauth_client table
"scope": "conference"
}
This client_id and client_secret you can use in the login request to get the bearer token. This bearer token you can use to get the access to all the apis.
Once you have the bearer token ready you can use it for further request, now need to use the login api for each request.

Resources