SecretHash: Amazon Cognito - amazon-cognito

I'm using boto3 for connect Amazon Cognito. I'm trying to use sign_up method; however, there is a parameter called SecretHash which I cannot understand how it is generated.
The original documentation about SecretHash:
SecretHash (string) -- A keyed-hash message authentication code (HMAC)
calculated using the secret key of a user pool client and username
plus the client ID in the message.
I'm getting error below, if I don't put SecretHash parameter.
botocore.exceptions.ClientError: An error occurred
(NotAuthorizedException) when calling the SignUp operation: Unable to
verify secret hash for client xxx

Don't know if this helps, but if you're using the JavaScript SDK, in the Setup part 1, it states:
Create an app for your user pool. Note that the generate client secret box must be unchecked because the JavaScript SDK doesn't support apps that have a client secret.
I created another app without generating a client secret key and it worked.

I just got off support with AWS regarding this question. Here is what i was told:
AWS REP: let me start from the begnining
AWS REP: the secretHash is not 100% stable
AWS REP: in order to call the function
AWS REP: the secretHash has to be ommited
AWS REP: so, you need to call it without secretHash
AWS REP: but.....
AWS REP: if you have configured your app with secret/security setting
AWS REP: cognito will not be happy if there is no secret hash
AWS REP: so you need to use a different app key
AWS REP: that was confiured without that setting
The rep was not sure about the algorithm that should be used and has opened a case to get the documentation updated.

The following works for me :
import hmac
import hashlib
import base64
def signup( username, password, email, phone):
msg=username + CLIENTID
dig = hmac.new(str(CLIENTSECRET).encode('utf-8'),
msg=str(msg).encode('utf-8'), digestmod=hashlib.sha256).digest()
d2 = base64.b64encode(dig).decode()
dig = d2
resp = pool.sign_up(
ClientId=CLIENTID,
SecretHash=str(dig),
Username=username,
Password=password,
UserAttributes=[
{ 'Name': 'email', 'Value': email},
{ 'Name': 'phone_number', 'Value': phone}
])
This was after trying all permutations of inputs to the secret and message parts. The CLIENTID is from AWS User Pools 'App clients' -> 'App client id'. The CLIENTSECRET is from AWS User Pools 'App client' -> 'App client secret'.
Hope this helps

This example shows how you could generate HMAC with SHA256 hash algorithm in python using the hmac libraries.
Calculating a SHA hash with a string + secret key in python
In this case the secret for HMAC will be your 'client secret'. And the message will be utf8 bytes of (username+clientId).
You only need to provide secretHash if your client has been generated with the secret otherwise secret hash can be committed in the call.
Also I would recommended you tot explore Cognito UserPools with our client side SDKs :javascript, android or IOS. As the SDKs handle the generation of secret hash for you.

Related

Configure AWS Cognitp UserPool to send emails with SES using CDK

I have an UserPool provisioned with AWS CDK.
I now noticed that Cognito is sending emails from Cognitop itself which is restricted in rates and amount of emails. They recommend to switch to an SES configuration.
After updating AWS-CDK to version 1.147.0 I was able to configure the UserPool's email settings.
this.userPool = new cognito.UserPool(this, "auth-pool", {
userPoolName: `...`,
...
email: cognito.UserPoolEmail.withSES({
sesRegion: "eu-west-1",
fromEmail: "...",
}),
...
})
I added before calling cdk deploy a verified identity
for the domain
and also for the email no-reply#DOMAIN
I also clicked on the verify link on the received email.
Still I get the following error when I try to deploy the stack changes:
Cognito received the following error from Amazon SES when attempting to send email: Email address is not verified. The following identities failed the check in region
EU-WEST-1: arn:aws:ses:eu-west-1:ACCOUNT:identity/no-reply#DOMAIN
My Stack runs in a different region but as Cognito can also use eu-west-1 (and other regions) I use this for the CDK settings.
Anyone an idea? I configured this manually in the AWS console and it is working :-|
Did you try sending a test email from the domain/email you verified? See end of this tutorial video:
https://www.youtube.com/watch?v=nxXIpPZzMd0

Boto3 - How to use Amazon Cognito to get temporary credentials from Identity Pool to access S3 bucket?

I am developing a python application whose purpose is to upload data to S3. Since it must be installed on different devices independently, I wouldn’t want store aws credentials on every platform but I want to create an authentication method based on Amazon Cognito.
It is necessary a login method based on username and password, so the user must be authenticated before being authorized to upload files.
I've created a Users Pool and Identity Pool and this is the pattern I want to follow:
This is the code I wrote to authenticate user:
import os
import boto3
username = "user1997"
password = "abcd1234!!"
client = boto3.client("cognito-idp", region_name="ap-south-1")
response = client.initiate_auth(
ClientId=os.getenv("COGNITO_USER_CLIENT_ID"),
AuthFlow="USER_PASSWORD_AUTH",
AuthParameters={"USERNAME": username, "PASSWORD": password},
)
access_token = response["AuthenticationResult"]["AccessToken"]
But I don't know how to use access_token to get temporary credentials from Identity Pool.
Access token isn't what you want here. You can use the identity token with get_id and get_credentials_for_identity calls to finally get temporary AWS credentials. For Example:
identityId = identity.get_id(
IdentityPoolId='us-east-1:xyxyxyxy-ab23-9989-7767-776x7676f5',
Logins={
'cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxx': id_tok
}
)['IdentityId']
aws_cred = identity.get_credentials_for_identity(
IdentityId=identityId,
Logins={
'cognito-idp.us-east-1.amazonaws.com/us-east-1_xxxxxxx': id_tok
}
)['Credentials']
aws_cred will have access key, secret key and session token. You can use these to sign AWS calls.

How to authenticate Python client app for access to restricted Google Cloud function?

I've created a Google Cloud function and I would like to access it from a Python application I'm developing. I am able to access the function when there is no authentication required, but can't access the functions when I enable authentication.
Here is the service account key I'm using with stripped out info. The only role it is configured for is invoking cloud functions.
{
"type": "service_account",
"project_id": "XYZ",
"private_key_id": "XYZ",
"private_key": "XYZ",
"client_email": "XYZ",
"client_id": "XYZ",
"auth_uri": "https://accounts.google.com/o/oauth2/auth",
"token_uri": "https://oauth2.googleapis.com/token",
"auth_provider_x509_cert_url": "https://www.googleapis.com/oauth2/v1/certs",
"client_x509_cert_url": "XYZ"
}
It seems that authenticated requests require a token that's included in the requests Authorization header, but I don't understand where to acquire this token.
I've tried using the approach outlined here with environment variables and the default auth method, but this doesn't work. I assume this is because the service account key is different from an OAuth token. (I have created a new service account the Cloud Functions invoker permission and am using that service account key). I receive the following error:
google.auth.exceptions.RefreshError: ('invalid_scope: Invalid OAuth scope or ID token audience provided.', '{"error":"invalid_scope","error_description":"Invalid OAuth scope or ID token audience provided."}')
How do I generate this token to authenticate the request from my Python script? Or is the approach with the service account recommended, but something else is going wrong?
I was able to get this working, though at time of writing there is a bug in the documentation that threw me off.
Access protected Cloud Functions is possible by using the IDTokenCredentials class within the google-auth library:
credentials = service_account.IDTokenCredentials.from_service_account_file(
SERVICE_ACCOUNT_JSON_FILE,
target_audience="https://function/url/here",
)
authed_session = AuthorizedSession(credentials)
response = authed_session.post(url)
I have my service account configured with the "Cloud Functions Invoker" role.
Your problem is almost certainly in the roles you gave that service account. Service accounts are finicky and the roles / permissions do not always act the way you think they will in my experience. Start by making a Service Account that has full permissions (project owner). Use that service account in your script then begin limiting the permissions from there. It sounds like you will need at a minimum cloud function "admin". If that works try another level down. Cloud function "developer" etc.
If you are using, for instance, App Engine or even other Cloud Functions to connect to your Cloud Function, you can use this: Function-to-function, the steps, basically are:
Grant the Cloud Functions Invoker.
In the calling function, you'll need to:
Create a Google-signed OAuth ID token with the audience (aud) set to the URL of the receiving function
Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the function.
import requests
//# TODO<developer>: set these values
REGION = 'us-central1'
PROJECT_ID = 'my-project'
RECEIVING_FUNCTION = 'my-function'
//# Constants for setting up metadata server request
//# See https://cloud.google.com/compute/docs/instances/verifying-instance-identity#request_signature
function_url = f'https://{REGION}-{PROJECT_ID}.cloudfunctions.net/{RECEIVING_FUNCTION}'
metadata_server_url = \
'http://metadata/computeMetadata/v1/instance/service-accounts/default/identity?audience='
token_full_url = metadata_server_url + function_url
token_headers = {'Metadata-Flavor': 'Google'}
def calling_function(request):
//# Fetch the token
token_response = requests.get(token_full_url, headers=token_headers)
jwt = token_response.text
//# Provide the token in the request to the receiving function
function_headers = {'Authorization': f'bearer {jwt}'}
function_response = requests.get(function_url, headers=function_headers)
return function_response.text
I have tested this solution and works as expected.
If you're invoking a function from a compute instance that doesn't have access to compute metadata (e.g. your own server), you'll have to manually generate the proper token:
Self-sign a service account JWT with the target_audience claim set to the URL of the receiving function.
Exchange the self-signed JWT for a Google-signed ID token, which should have the aud claim set to the above URL.
Include the ID token in an Authorization: Bearer ID_TOKEN header in the request to the function.
The Cloud IAP docs have sample code to demonstrate this functionality. The part you could be interested in should be this Authenticating from a service account

How to forbid unauthenticated requests from public API

We have a web public api service used for our website. How to limit only our website can visit our api?
Website and API use the same domain, so CORS doesn't help. API is built by NodeJS & Express.
You can use something called as JWT- JSON web token. The modern and easiest tool today for authenticating web services. Its really really simple to use JWT with nodeJs.
Step 1: Install JWT for node.
npm install jwt
Step 2: Create a JWT token. Example
token = jwt.sign({
url:"www.abc.com",
param2: value2,
param3: value3,
accesstoken: casjzhiy15t4e78y8bnvkds98u2e3098nc1bxu
}, "HASHKEY");
Here, the params can be anything you wish to add to the token and HASHKEY should be a string value. Thats it. Now you can copy the generated token pass the same token to the API call's from web.
Step 3: Verifying the token in API
var adminToken = req.headers.admintoken;
var decoded = jwt.verify(adminToken, 'HASHKEY');
if(decoded.PARAM1 == "VALUE1"){
//Request is coming from an authenticated source
}
You can refer to official JWT documentation for more detailed information.

What is the REST (or CLI) API for logging in to Amazon Cognito user pools

How do i make logins happen via Amazon Cognito REST APIs (for user pools) on platforms for which there is no official SDK? - Note that i am asking for user pools - not identity pools.
Synopsis
Amazon cognito provides 3 kinds of logins:
federated logins (creates identity pools) - using social connects like FB, Twitter, G+ etc
AWS managed logins (creates user pools) - using Amazon's own managed signup, signin, forgot password, reset password services
developer provided logins (my custom designed authentication service managed by myself)
I am using the second one (with User Pools)
Amazon cognito has several SDKs for android, iOS, javascript, Xamarin etc. Cognito also provides REST APIs for building on platforms other than those supported by official SDKs. I am building an app for a different platform and, hence, REST API is my only way as there is no official SDK for my platform.
The Cognito REST API provides various endpoints for 'sign up', 'forgot password', 'confirm verification' etc, but surprisingly, the REST API does not have any endpoint for simple signin / login.
From Cognito CLI API docs I have all the OFFICIAL CLI APIs necessary to "signup users", "confirm signups", "change passwords", "verify phone numbers", "forgot passwords" etc. Surprisingly there is no CLI API mentioned for LOGINs. I was hoping there should be some CLI API like "$ aws cognito-idp log-in" just like there is for "$ aws cognito-idp sign-up" or for "$ aws cognito-idp forgot-password" etc.
Also from this getting started tutorial it talks about "*what should be done with tokens received AFTER successful authentication of a user*". However, it doesn't talk about HOW TO make the successful authentication happen on the first place with Cognito User Pool APIs. Examples are available only for Android, iOS, javascript SDKs. There are no authentication examples available for platforms which do not have SDKs.
Hence, How do i make logins happen via Amazon Cognito REST APIs (for user pools) on platforms for which there is no official SDK?
This curl command works for me:
curl -X POST --data #aws-auth-data.json \
-H 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
-H 'Content-Type: application/x-amz-json-1.1' \
https://cognito-idp.us-east-1.amazonaws.com/
Where aws-auth-data.json is:
{
"AuthParameters" : {
"USERNAME" : "yourusername#example.com",
"PASSWORD" : "yourpassword"
},
"AuthFlow" : "USER_PASSWORD_AUTH",
"ClientId" : "75........................"
}
The user pool client must allow USER_PASSWORD_AUTH for this to work - that's an AWS-side setting.
Update:
As you pointed out in the comments below, the authentication flow is documented here: http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-authentication-flow.html. This might help to clarify the authentication flow
It is somewhat counter-intuitive, but it does make sense for mobile apps where you don't want to have the user explicitly sign in, but instead carry tokens around for the user. Note that there is an explicit signin (login) API in the AWS Userpools SDK for iOS. I have not used it, but I suppose it is just an alternate client side API to get through the same InitiateAuth() followed by a RespondToAuthChallenge() flow. The iOS signin example is documented here - IOS SDK Example: Sign in a User
Original Post:
The Cognito User Pools API documentation for initiating auth is available here
The way it works becomes clearer if you implement a user pools application in one of the SDK's (I did one in Swift for iOS, it is clarified because the logging of the JSON responses is verbose and you can kind of see what is going on if you look through the log).
But assuming I understand your question: In summary you should InitiateAuth() and the response to that (from the Cognito User Pools server) is a challenge. Then you do RespondToAuthChallenge() (also documented in that API doc) and the response to that is an authentication result - assuming that the password / session / token were accepted.
The combination of those two things is, I believe, what you are calling LOGIN, and it works like a login. In the API's, the way it is set up is that attempts to get user information when the user is unauthenticated kicks off that InitiateAuth() and (in iOS anyway) the API does a callback to the code you write to ask for passwords, and send a RespondToAuthChallenge() request etc.
Just to add to #andrewjj's answer. You might get back a challenge (NEW_PASSWORD_REQUIRED) as InitiateAuth response. It is when you are being asked to change passport on initial signin.
You can use Postman or curl command. This example expects Postman being used.
InitiateAuth - This step is same as #andrewjj
Add this to Body as raw values
{
"AuthParameters": {
"USERNAME": "youremail#example.com",
"PASSWORD": "temporary-password",
},
"AuthFlow": "USER_PASSWORD_AUTH",
"ClientId": "2s........................"
}
Set headers
X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth
Content-Type: application/x-amz-json-1.1
Send a request to https://cognito-idp.us-east-1.amazonaws.com/ You might have to change region.
If you receive this response then your are ok, otherwise continue with step 2.
{
"AuthenticationResult": {
"AccessToken": "eyJra........",
"ExpiresIn": 3600,
"IdToken": "eyJra........",
"RefreshToken": "eyJjd........",
"TokenType": "Bearer"
},
"ChallengeParameters": {}
}
RespondToAuthChallenge - this is new step
In case you receive Challenge back like this one:
{
"ChallengeName": "NEW_PASSWORD_REQUIRED",
"ChallengeParameters": {
"USER_ID_FOR_SRP": "1231-......",
"requiredAttributes": "[]",
"userAttributes": "{\"email_verified\":\"true\",\"email\":\"youremail#example.com\"}"
},
"Session": "Sfas......"
}
You need to set new password. Add this to Body as raw values
{
"ChallengeName": "NEW_PASSWORD_REQUIRED",
"ChallengeResponses": {
"USERNAME": "youremail#example.com",
"NEW_PASSWORD": "newpassword"
},
"ClientId": "2s........................",
"Session": "Sfas......(use one from the InitiateAuth response)"
}
Set headers
X-Amz-Target: AWSCognitoIdentityProviderService.RespondToAuthChallenge
Content-Type: application/x-amz-json-1.1
Send a request to https://cognito-idp.us-east-1.amazonaws.com/ You might have to change region.
Do step 1 again to receive tokens.
Sharing curl direct may help to anyone
curl -X POST --data #user-data.json \
-H 'X-Amz-Target: AWSCognitoIdentityProviderService.InitiateAuth' \
-H 'Content-Type: application/x-amz-json-1.1' \
https://cognito-idp.<just-replace-region>.amazonaws.com/
file json user-data.json
{"AuthParameters" : {"USERNAME" : "sadfsf", "PASSWORD" : "password"}, "AuthFlow" : "USER_PASSWORD_AUTH", "ClientId" : "csdfhripnv7sq027kktf75"}
make sure your app client does not contain app-secret or create new app without secret. also inside app enable USER_PASSWORD_AUTH
One of the developers from AWS Cognito team here.
To add to #md-abdul-munim's answer, we recommend using one of the client side SDKs. If you are building a REST API and then a front end which talks to those APIs, it is better to just integrate Cognito from your front end.
If you absolutely need to use Cognito from a back end, the authentication APIs will be available with our GA release. In our Cognito User Pools beta release authentication is only available through client SDKs.
From what you have discussed, I consider you are trying to do that from a web frontend. Cause, cognito is providing you the necessary backend support and it expects you to communicate(e.g. authenticate, sign up etc.) from a presentation layer- that's why you found SDK's for different mobile platforms. They also have SDK for web app- the access is available via their Javascript SDK.
Here's a detailed tutorial to achieve what you have asked from a web frontend using their JS SDK-
Accessing Your User Pools using the Amazon Cognito Identity SDK for JavaScript
I have a similar problem and was wondering how to integrate Cognito within an Elixir backend and found this library: https://github.com/aws-beam/aws-elixir
From what I can understand by reading its source code, they ultimately make a POST request that contains the header "X-Amz-Target": "AWSCognitoIdentityProviderService.#{name_of_api_action}" (this is here: https://github.com/aws-beam/aws-elixir/blob/master/lib/aws/cognito_identity_provider.ex#L564). That's without the authorization headers, they are added elsewhere, but I found it interesting. The functions that construct the request URL are following, so you should be able to get an idea of the endpoint that gets called.
I must say I tried following this article written in Japanese - https://qiita.com/yujikawa/items/e79929ed14277102f4b8, and couldn't manage to make it work, maybe because I was not sure what the proper AWS_ENDPOINT environment variable should be. I am currently thinking of trying out the Ruby SDK, from the looks of the documentation it seems fine. But, nonetheless, this information may still help someone.
Thank #andrewjj, your answer is a big help.
Here is additional info for someone who has trouble with client secret. You don't need to turn it off.
You need to generate a secret hash from username, clientId, client secret, as following:
message = bytes(username+app_client_id,'utf-8')
key= bytes(clientSecret,'utf-8')
secret_hash = base64.b64encode(hmac.new(key, message, digestmod=hashlib.sha256).digest()).decode()
src: https://aws.amazon.com/premiumsupport/knowledge-center/cognito-unable-to-verify-secret-hash/
Then add the secret hash to your AuthParameters, as following:
{
"AuthParameters" : {
"USERNAME" : "...",
"PASSWORD" : "...",
"SECRET_HASH" : "..."
},
"AuthFlow" : "USER_PASSWORD_AUTH",
"ClientId" : "..."
}