Reputation: 6404
Right now, I'm struggling to understand AWS Cognito so maybe someone could help me out. I set a domain to serve Cognito's hosted UI for my User Pool like what's described here. So when I go to https://<my-domain>.auth.us-east-1.amazoncognito.com/login?response_type=code&client_id=<MY_POOL_CLIENT_ID>&redirect_uri=https://localhost:8080
I get a login page where my users can login to my app with Google. That part is working great.
I'm confused about what to do with the code that is returned from that page once my user logs in. So once I get redirected to Google and authorize the application to view my information, I get redirected back to one of my URLs with a code in the query params. Right now I'm redirecting to localhost, so the redirect URL look like this:
https://localhost:8080/?code=XXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXX
What exactly is this code? Also, how do I use it to get access to AWS resources for my user?
Upvotes: 76
Views: 45064
Reputation: 15912
When this is true
#1: https://<my-domain>.auth.us-east-1.amazoncognito.com/login?response_type=code&client_id=<MY_POOL_CLIENT_ID>&redirect_uri=https://localhost:8080
The AWS Cognito Redirect will be
https://localhost:8080?code=8023253d-1c76-4c70-be3d-c8c29cc18c95
Then from your web client, use the returned code in the following app request.
NOTE: Post Header Content-Type MUST BE 'application/x-www-form-urlencoded'; Code is a one time use; redirect_uri from #1 and #2 requests must match.
#2: POST https://<my-domain>.auth.us-east-1.amazoncognito.com/oauth2/token?grant_type=authorization_code&code=8023253d-1c76-4c70-be3d-c8c29cc18c95&client_id=<MY_POOL_CLIENT_ID>&redirect_uri=https://localhost:8080
And will then get a response payload as follows
{
"id_token": "...",
"access_token": "...",
"refresh_token": "...",
"expires_in": 3600,
"token_type": "Bearer"
}
NOTE: If you instead have a Custom Domain setup, then the two above calls would change to
#1: https://my.custom.auth.domain.com/login?...
#2: https://my.custom.auth.domain.com/oauth2/token?...
UPDATE
In regards to comment to extend answer.
I use this library: https://github.com/awslabs/cognito-at-edge on a cloudfront edge.
I actually opened the lib use it locally in a file so that I could change the names of each cookie.
Create a Lambda and add the library to the index.js like this. There may be a few more steps as I have a utilities folder with csrf.js and cookie.js libs locally. I don't recall why I had to do that. Probably something with using the main lib locally instead of npm installing it.
Then in your cloudfront add this lambda as the "Viewer Request" with the ARN from the above lambda like this.
This should solve all your cookie issues as I think the lib attempts a refresh when needed. It's been awhile since I worked on this so memory is a bit lost.
Then for your api gateway, I think there is a setting on any route your add verification, where you say to accept a boolean so that you can return false from your verification lamba.
So make another lambda for API Gateway and attach it. In it just add the following code.
Verify cookie token on API Gateway route.
import { CognitoJwtVerifier } from 'aws-jwt-verify';
import { parseCookies } from './utilities/parse-cookie.mjs';
const verifier = CognitoJwtVerifier.create({
userPoolId: process.env.COGNITO_POOL_ID,
tokenUse: "access",
clientId: process.env.COGNITO_CLIENT_ID
});
const TOKEN_NAME = process.env.COGNITO_TOKEN_NAME;
export const handler = async (event) =>
{
if (event.cookies === null)
{
return {
isAuthorized: false,
};
}
const TOKEN = parseCookies(event.cookies, TOKEN_NAME);
if (TOKEN === null)
{
return {
isAuthorized: false,
};
}
try
{
await verifier.verify(TOKEN);
return {
isAuthorized: true,
};
}
catch(e)
{
return {
isAuthorized: false,
};
}
};
This will still take you a bit of time as there are many things going on and I could not include it all, but this is the bulk. Working with Edge is cumbersome as the changes don't propagate quickly. I found using the lambda_@_edge lib to be very easy to use and exactly what I was looking for.
Have patience and keep trying a little of the flow at a time until it works, then move on to the API gateway lambda.
You can get the cognito and cloudfront working just using postman requests.
Upvotes: 4
Reputation: 1304
Here is a more simple/clear way of doing this with Axios:
axios.post(`https://${yourCognitoPull}.auth.us-east-1.amazoncognito.com/oauth2/token`, null, {
params: {
grant_type: 'authorization_code',
client_id: 'xxxxx replace with your client id xxxxxx',
client_secret: 'xxxxx replace with your secret xxxxxx',
code: 'xxxx code you received from cognito xxxx',
redirect_uri: 'xxxx replace with the uri that you configure as valid xxxx'
}
}).then(response => {
const newAccessToken = response.data.access_token;
}).catch(error => {
console.log('Error al renovar el token de acceso', error);
});
Very important: Add the ClientSecret AND the RedirectURI (even if your are not redirecting, just getting the token, because it seems that Cognito checks if you know the valid URI as an additional security check)
(I strongly recommend doing this in a backend environment to protect the ClientSecret... or encrypting the value someway at least)
Upvotes: 3
Reputation: 4279
First off, screw authentication a thousand times. No one deserves to spend half a day looking at this shit.
Authentication for API Gateway Authorized with Cognito
Ingredients
client_id
and client_secret
: In Cognito > General Settings > App clients you can find the App client id, then click on Show Details to find the App client secret
For the header Authorization: Basic YWJjZGVmZzpvMWZjb28zc...
you need to encode those two with: Base64Encode(client_id:client_secret)
, for example in Python:
import base64
base64.b64encode('qcbstohg3o:alksjdlkjdasdksd'.encode()).decode()
side note: Postman also has an option to generate this in Authorization > Basic Auth
redirect_uri
: passed in the body, it is the callback url that you configured in App integration > App client settings.
This MUST match with what you configured or you will get a totally
unhelpful message { "error": "invalid_grant" }
Example of a request to get a token from the code:
curl --location --request POST 'https://mycognitodomain.auth.us-east-1.amazoncognito.com/oauth2/token' \
--header 'Content-Type: application/x-www-form-urlencoded' \
--header 'Authorization: Basic <base64 encoded client_id:client_secret>' \
--data-urlencode 'grant_type=authorization_code' \
--data-urlencode 'code=<use the code you received post login>' \
--data-urlencode 'redirect_uri=https://myapp.com'
This will return your tokens:
{
"access_token":"eyJz9sdfsdfsdfsd",
"refresh_token":"dn43ud8uj32nk2je",
"id_token":"dmcxd329ujdmkemkd349r",
"token_type":"Bearer",
"expires_in":3600
}
Then take the id_token
and plug into your API call:
curl --location --request GET 'https://myapigateway.execute-api.us-east-1.amazonaws.com/' \
--header 'Authorization: <id_token>'
Ok, this is tagged as JavaScript but since we also suffer in Python
Friendly reminder: this is an example, please don't hardcode your secrets.
import requests
# In: General Settings > App clients > Show details
client_id = "ksjahdskaLAJS ..."
client_secret = "dssaKJHSAKJHDSsjdhksjHSKJDskdjhsa..."
# URL in your application that receives the code post-authentication
# (Cognito lets you use localhost for testing purposes)
callback_uri = "http://localhost:8001/accounts/amazon-cognito/login/callback/"
# Find this in: App Integration > Domain
cognito_app_url = "https://my-application-name.auth.us-west-2.amazoncognito.com"
# this is the response code you received - you can get a code to test by going to
# going to App Integration > App client settings > Lunch Hosted UI
# and doing the login steps, even if it redirects you to an invalid URL after login
# you can see the code in the querystring, for example:
# http://localhost:8001/accounts/amazon-cognito/login/callback/?code=b2ca649e-b34a-44a7-be1a-121882e27fe6
code="b2ca649e-b34a-44a7-be1a-121882e27fe6"
token_url = f"{cognito_app_url}/oauth2/token"
auth = requests.auth.HTTPBasicAuth(client_id, client_secret)
params = {
"grant_type": "authorization_code",
"client_id": client_id,
"code": code,
"redirect_uri": callback_uri
}
response = requests.post(token_url, auth=auth, data=params)
print(response.json()) # don't judge me, this is an example
Upvotes: 89
Reputation: 160
Thank you for sharing @bubbassauro. Below my PHP version :
try {
$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, $_ENV['URL_COGNITO'].'/oauth2/token');
curl_setopt($ch, CURLOPT_RETURNTRANSFER, 1);
curl_setopt($ch, CURLOPT_POST, 1);
$headers = [];
$headers[] = 'Content-Type: application/x-www-form-urlencoded';
$headers[] = 'Authorization: Basic '.base64_encode($_ENV['CLIENTID'].':'.$_ENV['CLIENTSECRET']);
curl_setopt($ch, CURLOPT_HTTPHEADER, $headers);
curl_setopt($ch,CURLOPT_POSTFIELDS, http_build_query([
'grant_type' => "authorization_code",
'client_id' => $_ENV['CLIENTID'],
'code' => $code,
'redirect_uri' => $_ENV['URL_AUTH0']
]));
$result = curl_exec($ch);
curl_close($ch);
$result= json_decode($result);
return $result->id_token;
} catch (Exception $e) {
return $e->getMessage();
}
Upvotes: 1
Reputation: 1
Simply, You can request the id/access/refresh tokens using the code and the Cognito clientId+hostname, then use the id and access token to identify the user in your API calls.
Upvotes: -2
Reputation: 2768
Not sure if this is going to be useful 10 months from since it was asked but might be helpful to others.
I have used response_type=token
(Oauth flow=implicit grant
& scope=openid
) & provider as Cognito
. After you login using the default login page, you will get an id_token
& access_token
. You can get a temporary identity for your user using this id_token
. You also need to have an Federated identity pool setup for this, with roles assigned for authenticated & unauthenticated users, and linked to the user pool you are authenticating with. Once you have that (assuming you are using javascript), you can follow example at Cognito user identity pools javascript examples. My sample code derived from the same -
function getAccessToken(idToken, identityPoolId, userPool) {
let provider = "cognito-idp.us-east-2.amazonaws.com/" + userPool;
let login = {};
login[provider] = idToken;
// Add the User's Id Token to the Cognito credentials login map.
let credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
Logins: login
});
//call refresh method in order to authenticate user and get new temp credentials
credentials.get((error) => {
if (error) {
console.error(error);
let response = {
statusCode: 500,
body: JSON.stringify(error)
};
return response;
} else {
console.log('Successfully logged!');
console.log('AKI:'+ credentials.accessKeyId);
console.log('AKS:'+ credentials.secretAccessKey);
console.log('token:' + credentials.sessionToken);
let response = {
statusCode: 200,
body: JSON.stringify({
'AKI': credentials.accessKeyId,
'AKS': credentials.secretAccessKey,
'token': credentials.sessionToken
})
};
return response;
}
});
}
Hope this helps.
Upvotes: 7
Reputation: 161
To fetch AWS credentials (id_token, access_token and refresh_token) from the code request parameter returned by the authorisation code oath2 flow, you should use your Cognito User Pool web domain /oauth2/token
endpoint, following https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html instructions.
Pay attention to the HTTP Basic Authorisation user and password instructions, it should be your Cognito App client_id
and client_secret
, otherwise, you get a invalid_client
error.
The code flow is supposed to be used server side, as you avoid tokens floating around on URLs. If you're planning to do that client side, you should use the response_type=token
, as it gives this result directly on the login redirect.
Upvotes: 16
Reputation: 279
you can find the "Authorization code grant" in the doc :http://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-app-idp-settings.html
Upvotes: 2