I'm dealing with the issue of users not explicitly logging out of a web application after use, which is not secure enough for the use case. It is a React app with AWS Amplify and Cognito.
I plan to do this by tracking sessions in a database (I can capture the start or refresh of a session using a Cognito Lambda trigger written in Go on PostAuthentication_Authentication or TokenGeneration_RefreshTokens events), and expiring sessions using GlobalSignOut after a period of inactivity, but in order to invalidate the user refresh tokens on session abandonment, I need the Access Token, which appears to only be available to the client.
I can get this explicitly on login from the web client, and post it back to the database using GraphQL to record it, but I was surprised to see that it's not available from the Cognito payload sent to the Lambda event triggers. I'm also not sure of how to grab the refreshed token on the client if it refreshes after an hour of continued application use, without adding overhead to every change in the application.
Is there a way to request the current access token for a Cognito user from a server side process like a Lambda function if you're using Amplify on the client for the authentication flows? I cannot see anything in cognitoidentityprovider that allows me to retrieve the access token, but it's clearly needed to use GlobalSignOut.
You could use the Authorisation Code Flow with PKCE instead so the client is only exposed to the code which is then exchanged server-side using the token endpoint for cognito user pool / id token, access token and refresh token. You can return the user pool token to the client as that will expire after an hour while keeping the refresh token in your session manager on the server-side allowing you to fetch fresh tokens as needed or invalidate the session based on your requirements.
https://docs.aws.amazon.com/cognito/latest/developerguide/token-endpoint.html
I've found a workable solution that does not involve fetching the Access Token server side. We use AppSync for GraphQL - the access token is passed with each GraphQL request as an authorization header, and it can be accessed in the request template as follows:
#set( $myMap = {
"field": "${field}",
"user_id": $context.identity.sub,
"source_ip": $context.identity.sourceIp,
"arguments": $context.arguments,
"access_token": $context.request.headers.authorization
} )
{
"version" : "2017-02-28",
"operation": "Invoke",
"payload": $util.toJson($myMap)
}
In each Lambda resolver, I simply cache the access token when encountered, so that I always have the latest access token for the current user. My scheduled session manager function then retrieves the access tokens from the cache for any users it detects with an abandoned session and can use it as an input to GlobalSignOut.
Related
Execute compute engine
api(GET https://compute.googleapis.com/compute/v1/projects/{project}/zones/{zone}/instances/{resourceId}) with oauth 2.0 client id.
I created an OAuth2.0 client ID and got access_token and refresh_token based on the steps on this site.
Obtaining OAuth 2.0 access tokens
Refreshing an access token (offline access)
I can execute api with access_token which was refreshed.
after 3days, run this step again,
https://developers.google.com/identity/protocols/oauth2/web-server#offline
response was
json
{ "error": "invalid_grant", "error_description": "Token has been expired or revoked." }
why expired refresh_token?
refresh_token
A token that you can use to obtain a new access token. Refresh tokens are valid until the user revokes access. Again, this field is only present in this response if you set the access_type parameter to offline in the initial request to Google's authorization server.
There are a lot of things which can cause a refresh token to expire.
you are using a gmail scope and the user changed their password.
it has not been used in six months.
the user has revoked your access in their google account.
If the user runs your app you get a refresh token, if they run it again you get a different refresh token, you can do this up to 50 times and get new refresh tokens and they will all work after number 50 the first one will expire. Make sure you are always saving the most resent refresh token.
your app is currently in testing and has not been set to published and has not been though the verification process.
Documentation link for expiration
I'm providing an external-facing REST GET API service in a kubernetes pod on AWS EKS. I had configured an ALB Ingress for this service which enforces Cognito user pool authentication. Cognito is configured with Authorization code grant with the openid OAuth scope enabled.
If I invoke my REST API from the browser, I get redirected to the Cognito login page. After a sucessful authentication on the form here, I can access my REST GET API just fine. This works, but this is not what I'd like to achieve.
Instead of this, I would need to use a Bearer token, after getting successfully authenticated. So first I invoke https://cognito-idp.ap-southeast-1.amazonaws.com using Postman with the request:
"AuthParameters" : {
"USERNAME" : "<email>",
"PASSWORD" : "<mypass>",
"SECRET_HASH" : "<correctly calculated hash>"
},
"AuthFlow" : "USER_PASSWORD_AUTH",
"ClientId" : "<cognito user pool id>"
}
and I get a successful response like:
"AuthenticationResult": {
"AccessToken": "...",
"ExpiresIn": 3600,
"IdToken": "...",
"RefreshToken": "...",
"TokenType": "Bearer"
},
"ChallengeParameters": {}
}
In the last step I'm trying to invoke my REST API service passing the Authorization HTTP header with the value Bearer <AccessToken> but I still get a HTML response with the login page.
How can I configure Cognito to accept my Bearer token for this call as an authenticated identity?
Quoting AWS support on this topic: "the Bearer token can not be used instead of the session cookie because in a flow involving bearer token would lead to generating the session cookie".
So unfortunately this usecase is not possible to implemented as of today.
STANDARD BEHAVIOUR
I would aim for a standard solution, which works like this:
API returns data when it receives a valid access token, or a 401 if the token is missing, invalid or expired - the API never redirects the caller
UIs do their own redirects to the Authorization Server when there is no token yet or when a 401 is received from the API
If it helps, my OAuth Message Workflow blog post demonstrates the 3 legged behaviour between UI, API and Authorization Server.
API GATEWAY PATTERN
It is perfectly fine to use an API Gateway Design Pattern, where token validation is done via middleware before hitting your API.
However that middleware must return a 401 when tokens are rejected rather than redirecting the API client.
IMPACT OF APIs REDIRECTING THE CLIENT
This may just about work for web UIs, though user experience will be limited since the UI will have no opportunity to save the user's data or location before redirecting.
For mobile / desktop apps it is more problematic, since the UI must redirect using the system browser rather than a normal UI view - see the screenshots on my Quick Start Page.
CHOICES
Any of these solutions would be fine:
Possibly the middleware you are using can be configured differently to behave like a proper API Gateway?
Or perhaps you could look for alternative middleware that does token validation, such as an AWS Lambda custom authorizer?
Or do the OAuth work in the API's code, as in this Sample API of mine
MY PREFERENCE
Sometimes I prefer to write code to do the OAuth work, since it can provide better extensibility when dealing with custom claims. My API Authorization blog post has some further info on this.
I'm trying to create a personal blog using s3 and lambda. I already have the API setup but I'm trying to figure out how to make the blog post requests more secure by requiring an authorization token in order to access the API Gateway.
I believe this can be done with cognito user pools but is usually used with many users not a single admin user. However, if there's another way I should go about this then I'm all ears.
You can implement this by:
Creating a User Pool in Cognito
If you are using the Hosted UI login pages, I recommend having the pages send a code response rather than a token response because you can call the token endpoint to get all the appropriate tokens.
Call your token endpoint with the code you receive in Step 2 (it'll be in the URL when you are redirected back to your site) to retrieve the ID, Access, and Refresh Tokens.
Once you have your cognitoUser tokens, you can wrap your blog publish function with a token check function to ensure that your token is up-to-date and send the updated token to your publish blog callback.
Send the user token in your headers: { Authorization: token } API Call.
In API Gateway, choose the Method Request in your Blog Post API and select your Cognito User Pool name under authorizers.
As long as the token you send is valid, the Method Request is all you need to update in order to secure the ability to post.
I am developing an application that uses AWS Cognito as the Identity Provider. So the user authenticate on AWS Cognito Pool and get the Access Token, Access ID and Refresh token.
Then the user can make backend requests to my app. I get the Access Token validate it, get the user profile on Cognito AWS and authorize the request.
The problem is that after the Access token has expired, and the client send the expired token to the backend, the backend app get an error (token experied or not authorized).
How can I make this workflow works?
I was thinking in send to the client a message that the token has expired, and the the cliente refresh it against the Cognito Pool. Is it the correct approach?
When you get the Access Token, ID and Refresh token from Cognito User Pools, you must cache it locally. The Access and the ID token are valid for 1 hour and should be reused as much as possible within that time period.
These tokens are JWT tokens and hold the expiry time within themselves. You can decode the JWT token and also cache this expiry along with the token. Every time the cache for the tokens is accessed, also check the current time against the cached expiry time. If expired, use the Refresh token to obtain the latest Access and ID token and cache the tokens and expiry again.
If you use one of our high level SDKs for Android, iOS or JavaScript, the SDK manages all of this for you.
you can find more information How-to use them on this link.http://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html.
To use the refresh token to get new tokens, use
the AdminInitiateAuth API, passing REFRESH_TOKEN_AUTH for
theAuthFlow parameter and the refresh token for
the AuthParametersparameter with key "REFRESH_TOKEN". This initiates
the token refresh process with the Amazon Cognito server and returns
new ID and access tokens.
In short, call the AdminInitiateAuth action with the refresh token. Take a look at the SDK of your development language you prefer.
In my projects I use AWS Amplify library and I found this approach to work:
Configuration:
import Amplify, { Auth } from "aws-amplify";
Amplify.configure({
Auth: {
userPoolId: <USER_POOL_ID>,
userPoolWebClientId: <USER_POOL_WEB_CLIENT_ID>
}
});
Refresh tokens
try {
const currentUser = await Auth.currentAuthenticatedUser();
const currentSession = currentUser.signInUserSession;
currentUser.refreshSession(currentSession.refreshToken, (err, session) => {
// do something with the new session
});
} catch (e) {
// whatever
}
};
More discussion here: https://github.com/aws-amplify/amplify-js/issues/2560.
The Twitter OAuth 1.0a flow requires authenticated request token to be exchanged with access token at consumer or client side after user has authenticated.
The problem that I'm facing is that generating access token needs authenticated request token, request token secret and verifier but the response from the oauth/authentication api doesn't have request token secret. So how do I temporarily save request token secret from oauth/request_token api call so that I can use it in oauth/access_token api call.
I found some solutions from my explorations like Running a Cache server (Memcached, Redis) or using django session feature. But they all seem to be overkill for this task.
I hope to find a simpler solution.
I'm sure you long ago figured this out, but just for future goolers: I decided to a go a more low tech route and create an OAuth token class which includes fields for the fetched and access token. Basically I take the fetched token, store it, then recall it when accessing (as it's in a different view) and then save the access token. Once (if) that's successful than I delete the fetched token.
There's likely a more glamorous way to do this, but if you're clever with your naming convention you can easily keep them straight (i.e. add a CharField for provider and just save the fetched token as twitter_fetched, and the access token as just twitter).
This has the added benefit of allowing you to create an OAuth1 or OAuth1Session from the stored access token.