How can I get my Auth0 permissions into the scope claim of the access token for an AWS HTTP API Jwt Authorizer? - amazon-web-services

I am trying to get an AWS HTTP API JWT Authorizer with scopes on an endpoint to work happily with my Auth0 access tokens.
The JWT Authorizer looks for the necessary scopes in the access token's "scope". I am thinking that this is used for fine-grained authorization. But, Auth0 returns permissions in a "permissions" array rather than in the token's "scope".
Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions? Or will I need to have my lambda function dissect the authenticated JWT after its gone past the JWT Authorizer?

There is a way to add your user permissions into the scope claim of your token. This thread details two ways to achieve this, thread:
Adding a rule that reads these permissions and copies them into your access_token scope claims.
function (user, context, callback) {
var ManagementClient = require('auth0#2.17.0').ManagementClient;
var management = new ManagementClient({
token: auth0.accessToken,
domain: auth0.domain
});
var params = { id: user.user_id};
management.getUserPermissions(params, function (err, permissions) {
var permissionNames = [];
permissions.forEach(function(obj) { permissionNames.push(obj.permission_name); });
if (err) {
// Handle error.
}
context.accessToken.scope = permissionNames;
callback(null, user, context);
});
}
Using the RBAC feature with TOKEN_DIALECT. Note that information around this is extremely scarce, this post linked below it the only piece of information I have found about it. Also I could not get this to work consistently so personally I use the method #1 listed here. TOKEN_DIALECT
In an ideal world option #2 is really the best and has the least configuration, but I have had issues with this.

Is there a way to get my permissions to show up in the "scope" of the access token so that I can use the JWT Authorizer to handle fine-grained permissions?
This is a bad idea. A JWT token is small (<8 kb according to this answer). What will happen when you have a million resources? Will your "array of permissions" have a million items, too?
https://auth0.com/blog/on-the-nature-of-oauth2-scopes/
scopes are used to express what an application can do on behalf of a given user. (...) scopes are used for handling delegation scenarios (...) overloading scopes to represent actual privileges assigned to the app (as opposed to the delegated permissions mentioned above) is problematic
I work for Auth0 and we're building a solution to handle fine-grained authorization. See https://zanzibar.academy/

Related

AWS Cognito: Why do you need an ID Token? [duplicate]

Just reading the docs, they seem very similar to me so I can't really discern why to use one over the other. Although identity token seems better since it has custom attributes on it from the user pool (eg: custom:blah and the default ones like name and email).
Right now, I am working with an app that passes the access token back down to the browser so it can use it for making ajax REST calls (there is an auth filter that expects this access token and validates it). Could I just switch out the access token with the id token? The current validation logic is to just get the sub field (the uuid) from the access token, but this sub field is also present in the identity token (as well as practically every other attribute except the aud which I don't need). I just want to make sure I am understanding this right as it is confusing to me why both tokens exist and seem so similar.
The id_token is for your application to process, so you can get all the personal details for your user, like their name, age, email address etc. Generally speaking you shouldn't send this token anywhere else as it contains sensitive user data.
The access_token is used to call other 'external' services (and by external I include other AWS services - these are often called over http). It provides service access authorisation for your user without having to include their personal details.
On the face of it this appears slightly confusing as you can actually use the id_token to access services in the same way as the access_token. However, good practise is to use the access_token in this circumstance and if backend services need user data, they should look it up themselves in Cognito.
EDIT: If you need to authenticate an api call based on claims in the identity token, there are circumstances when this is perfectly valid. But be aware of what details are in the identity token, and whether those claims are suitable to send to the particular API. If you don't need to use any claims from the id_token, use the access_token as this reduces the amount of potentially sensitive data you are sending.
The thing that wasn't obvious from documentation for me about the difference:
If you are using pretoken trigger function and want to add additional information to the claims with claimsToAddOrOverride , you need to use an id token because this information doesn't exist in the access token.
For ex:
event.response = {
claimsOverrideDetails: {
claimsToAddOrOverride: {
'userProfileID': id,
}
},
}
I've expected it in the AppSync resolver with lambda function as source
Speaking about AWS User Pool tokens:
Identity token is used to authenticate users to your resource servers or server applications. For example, if you use Cognito as authorizer in AWS API Gateway you need to use Identity token to call API.
The purpose of the access token is to authorize API operations in the context of the user in the user pool. For example, you can use the access token to grant your user access to add, change, or delete user attributes.
The header for the access token has the same structure as the ID token. However, the key ID (kid) is different because different keys are used to sign ID tokens and access tokens.

AWS Amplify Auth.updateUserAttributes() clear localStorage/sessionStorage

I am writing a web-based app that uses AWS Cognito as the authentication service. I use 'aws-amplify' to implement the client-app.
I am using Auth.updateUserAttributes() to update a custom attribute of users on Cognito. However, I found that the call of this function would clear all the Cognito-related items, including idToken, refreshToken, and accessToken stored in localStorage. As a result, the web app behaves like sign-out.
Here is the code about Auth's configuration
Amplify.configure({
Auth: {
userPoolId: process.env.REACT_APP_AWS_COGNITO_USER_POOL_ID,
region: process.env.REACT_APP_AWS_COGNITO_REGION,
userPoolWebClientId: process.env.REACT_APP_AWS_COGNITO_APP_CLIENT_ID,
storage: window.localStorage,
authenticationFlowType: 'CUSTOM_AUTH',
},
});
and the code I wrote to update the user's attribute. (I followed the example code from the amplify docs https://docs.amplify.aws/lib/auth/manageusers/q/platform/js/#managing-user-attributes)
let user = await Auth.currentAuthenticatedUser();
console.log(JSON.stringify(localStorage)); // it outputs the localstorage with idToken,
// refreshToken, accessToken and other items
// start with 'CognitoIdentityServiceProvider'
const result = await Auth.updateUserAttributes(user, {
'custom:attributes_1': '123456789',
});
console.log(result); // OUTPUT: SUCCESS
console.log(JSON.stringify(localStorage)); // Only 'amplify-signin-with-hostedUI'.
// idToken, refreshToken, accessToken and
// other items were gone. No key, no value.
After the last line, I could not interact with the web page anymore. If I refreshed the web page, I found I had signed out and had to sign in again.
It was still the same if I changed the storage for Auth from localStorage to sessionStorage.
Here are my questions:
Is this kind of behavior normal? Does Auth.updateUserAttributes() leads to a force sign-out?
If it's true, is there any way to avoid a mandatory sign-out?
If it's not, what's wrong with my code or configuration? Or should I do some specific configuration for the Cognito service?
Thanks a lot!
Well, I figured it out after reading the source code of aws-amplify.
Behind the call of Auth.userUpdateAttributes, amplify will finally call CognitoUser.refreshSession(refreshToken, callback, clientMetadata) (https://github.com/aws-amplify/amplify-js/blob/f28918b1ca1111f98c231c8ed6bccace9ad9e607/packages/amazon-cognito-identity-js/src/CognitoUser.js#L1446). Inside this function, amplify sends an 'InitiateAuth' request to Coginito. If an error of 'NotAuthorizedException' happens, amplify calls clearCachedUser() that delete everything I mentioned in my question from the localStorage.
There was an error of 'NotAuthorizedException' happening and reported by the network work monitor of Chrome Browser'. I thought it was generated after the sign-out-like behavior. However, it turned out to be triggered because no deviceKey was passed to the request's parameters.
So the whole story was:
I set remember device options in Cognito;
I used 'CUSTOM_AUTH' as the authentication flow type;
When a user successfully signed in to my application, Cognito didn't give the client the deviceKey because of the 'CUSTOM_AUTH' authentication flow type.
When Auth.userUpdateAttributes() was called, CognitoUser.refreshSession() was called behind it. It attached no deviceKey to Cognito when it sent ana request to asked Cognito to refresh the token. The Cognito rejected the request with an error of 'NotAuthorizedException'. The CognitoUser.refreshSession() handled the error and called clearCachedUser() to delete the stored tokens and other info from the localStorage.
My final solution is to turn off the remember device option since I have to use 'CUSTOM_AUTH' as the authentication flow type according to my application's functional requirements.
According to https://aws.amazon.com/premiumsupport/knowledge-center/cognito-user-pool-remembered-devices/, the remember device function only works when the authentication flow type is set as 'USER_SRP_AUTH'.

is the Authorization header passed to lambda function by Cognito Authorizer?

I'm planning to add some authorisation logic to my web app. Users are managed & authenticated by Cognito, and the API is powered by Lambda functions glued together with API Gateway.
Currently, Cognito just validates the user's OAuth token and allows/denies the request.
I'd like to further restrict what actions the user can take within my lambda functions by looking at the user's groups.
Looking at the OAuth token, claims about the groups are in the token body. My question is, does the Cognito Authorizer pass the value of the Authorization: Bearer foo header through to API Gateway and the Lambda handler?
The way I can do something like this:
const groups = getGroupsFromToken(event.headers.Authorization);
if (groups.includes('some group')) {
// let user do the thing
}
else {
callback({ statusCode: 401, body: 'You can\'t do the thing' });
}
It definitely sends through the token on a header for me, also it sends through requestContext.authorizer.jwt.claims which may be more useful to you.
The older api gateways I have always uppercase the header to "Authorization", irrespective of what case the actual header uses. The newer ones always lowercase it to "authorization".
I'd suggest trying:
const groups = getGroupsFromToken(event.headers.Authorization || event.headers.authorization);
I am using lambda proxy integration (what the new APIGW UI is calling lambda integration 2.0), from your callback it looks like you are using it too. If you are using the old lambda integration (1.0 in the new UI) then you need a mapping template.

"No current user": Isn't it even possible to make unauth calls to AWS AppSync through Amplify with authentication type AMAZON_COGNITO_USER_POOLS?

I have an AWS AppSync schema with the default authorization mode set to Amazon Cognito User Pool. I make calls to this AppSync endpoint from a web app using AWS Amplify GraphQL Client and, coherently, its configuration points Cognito User Pools as authentication type, too:
aws_appsync_authenticationType: 'AMAZON_COGNITO_USER_POOLS'
It works as expected when the user is authenticated; however (although the involving Cognito Identity Pool has proper Auth and Unath roles set already), when the website runs some Amplify fetch command like for a unauthenticated(guest) user:
const item = await API.graphql(graphqlOperation(getItem, { id: 'my-id' }))
Ends up with throwing an error:
"No current user"
Well, I expected it to perform if I allow unauthenticated users, but it simply fails. Seeking for a way out, I found some discussions like:
a GitHub issue comment here,
another Github issue,
or an SO question here.
And, all of the above suggest revisiting the Amplify configs so that the AppSync authentication type is converted from AMAZON_COGNITO_USER_POOLS to AWS_IAM or API_KEY. However, for some detailed reason 1:
I want to stick with AMAZON_COGNITO_USER_POOLS authentication type,
And still be able to fetch some AppSync resources as a guest user unless they are restricted with
#aws_auth decorators or such.
Is it possible in any way?
1 I have more granular controls depending on the user's group (admin, normal etc.) with decorators such as #aws_auth(cognito_groups: ["default-user-group"]) on the AppSync schema. So, I need Cognito User Pools for that usage.
So, I just went through a similar issue and managed to get it sorted. I hope this might help you sort this out. The SO question you mentioned in your question is almost the right way to do it. However, there are one "little" tiny details that are not documented and took me a while to find out.
Apart from having to enable both authenticated and unauthenticated access by running amplify update auth (you can see how in the SO linked above) there are other tweaks you need to do.
First, in your model you need to adjust your rules to be something like:
#auth(
rules: [
# allow owners ability to update and delete their these messages (user pools)
{ allow: owner },
# allow all authenticated users to access this data
{ allow: private, provider: userPools },
# allow all guest users (not authenticated) to access this data
{ allow: public, provider: iam }
]
)
Once you set up this model to allow user pools to access all the data they "own", you can also let any "guest" user to access the data too.
In the frontend, let's get your code as an example, you need to use a bit of a different approach:
Instead of
const item = await API.graphql(graphqlOperation(getItem, { id: 'my-id' }))
try something like
// Check if the user is logged in or not
let isLoggedIn = await isLoggedIn();
const item = (await API.graphql({
query: getItem,
variables: { id: 'my-id' },
authMode: isLoggedIn ? GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS : GRAPHQL_AUTH_MODE.AWS_IAM,
}));
by the way the "isLoggedIn" function look like this
async function isLoggedIn() {
// Another way to get if its a guest or not
//return await Auth.Credentials.getCredSource() === "guest"
try {
await Auth.currentAuthenticatedUser();
return true;
} catch {
return false;
}
}
So... this line is what it does the trick, which is not really well documented.
authMode: isLoggedIn ? GRAPHQL_AUTH_MODE.AMAZON_COGNITO_USER_POOLS : GRAPHQL_AUTH_MODE.AWS_IAM
you need to path the different methods according to the state of the user (logged in or guest), not just the AWS_IAM one.
This will only get you as far as READING the data, in order to make sure guests can do Create/Update/Delete, and separate the data ownership from each other and logged users, that's a completely different story that you will need to start digging up on resolvers to get it sorted. But the good news is, there is a way :)

Amazon AWS getAttribute() using AWS.config.credentials

I have just started with Amazon Cognito and I want to use it for my web application. I want to develop a stateless app, in which I SignUp/SignIn using Cognito and then using the JWT token in rest of the requests.
I have implemented sign-up and sign-in flow in Node.js using amazon-cognito-identity-js package and then using the JWT token to call a lambda function using aws-sdk. Things are as expected till here.
But now the issue is with different user operations like get attribute, verify attribute, update password etc. as listed #
https://docs.aws.amazon.com/cognito/latest/developerguide/using-amazon-cognito-user-identity-pools-javascript-examples.html
All of these operations require cognitoUser object and in the documentation they are using userPool.getCurrentUser(); expression.
And I have read somewhere that this method returns the last authenticated user. So I think this expression userPool.getCurrentUser(); will cause conflicts. For example if UserB logs in after UserA and UserA tries to update his password, it will not work.
Can someone suggests me what are the possible solutions?
Should I store the cognitoUser object in session at server side ?
[This solution breaks my stateless requirement and I will have to maintain session on server side.]
Is there any way to perform these operations using JWT token ?
Please suggest if you can think of any other better approach to implement Cognito in web app.
Thanks.
We have a stateless app using cognito and lambdas.
The way we have set it up is to not call lambdas directly but to use Api Gateway and lambda-proxy integration.
If you call lambdas directly from your front end code and are using the cognito tokens for authentication then you need to put a lot of logic in each lambda to validate the token, e.g. download the relevant keys, check the signature of the jwt, timestamps, issuer etc. If you use API gateway then you can just create a cognito authorizer and place it in front of your lambdas.
We pass the id_token when making api calls, then the call is validated by the authorizer and the lambda receives all the current attributes set up in the user pool. This means we don't need to make additional calls to get attributes.
For changing the user passwords this can be done from the front-end of the app by calling the cognito api with the access_token if you have allowed it in the user pool client setup.