https://mobile.awsblog.com/post/Tx1OSMBRHZVM9V0/Understanding-Amazon-Cognito-Authentication-Part-3-Roles-and-Policies
I'm pretty new to AWS. This blog series about AWS Cognito keep mentioning about amr like examples below
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "login.myprovider.myapp"
}
Finally, we specify that the amr of the token contains the value unauthenticated.
I couldn't find and figure out what it means.
amr stands for 'Authentication Methods References'. That comes from the OpenID Connect specification. http://openid.net/specs/openid-connect-core-1_0.html
In the context of Cognito, this represents how the user was authenticated. For example if the user was authenticated using any of the supported providers (facebook, google, amazon), the amr will contain 'authenticated' and also the name for the provider e.g. 'graph.facebook.com' if user was authenticated using facebook. If the user was authenticated using google, it will be 'accounts.google.com'. If the user was authenticated without using any supported providers (Unauthenticated Identity), it will be 'unauthenticated'.
This is useful when you use Cognito Credenitials to access your AWS services and want to implement fine grained access control
This is a really old question but I think it's worth mentioning that [AMR is an array[(https://self-issued.info/docs/draft-ietf-oauth-amr-values-01.html#rfc.section.2) in case anybody (like me) didn't notice. That's the reason why in your condition clause you need to use ForAnyValue:StringLike not just StringLike. Example:
"Condition": {
"StringEquals": {
"cognito-identity.amazonaws.com:aud": "us-east-1:<your-id-pool-id>"
},
"ForAnyValue:StringLike": {
"cognito-identity.amazonaws.com:amr": "authenticated"
}
}
Related
I'm getting a "Request header is too long" error when i was trying to access my API and send token in header. How we can manage a permissions in access-token because i have a lot of permissions in access-token it's approximately 15kb in size.
I know it's not recommended to store permissions in access-token then what's the best approach to authenticate and authorize the users on API side?
We are getting all the permissions in access token but now permissions are getting large as we have lot of modules. What is the other way to access user permissions in asp.net core API instead of keeping it in access token?
{
"roles": [
"Admin"
],
"iss": "Issuer",
"sub": "sub",
"aud": [
"https://example.com/api",
"https://example.com/userinfo"
],
"iat": 1666198659,
"exp": 1666205859,
"azp": "azp",
"scope": "openid profile email offline_access",
"org_id": "company1",
"permissions": [
"permission.1",
"permission.2",
........
"permission.150",
]
}
This could be a solution: https://fga.dev/. There's also the open source version: https://openfga.dev/
Basically, since every user has a lot of permissions, you don't store them in the token anymore; you can call this service which stores them for you.
Disclaimer: I am part of the team building this solution :)
You have two options.
You can try to shorten the permissions down using something like the approach here. There are lots of other similar questions with similar approaches. However, know that you limit your number of permissions based on what you inherit from so this may or may not work. You can stick a string into the JWT with each char working as a flag for a permission. This comes with more downsides, namely keeping the data up-to-date.
You setup a remote system for authorization. Something like Policy Server from Duende. This means no authorization data in the JWT. For instance you can make a simple http call to your identity server from your api/client and have the identity server evaluate if the user can do what they want to.
The latter seems right for your scenario because of your large amounts of permissions. It comes with overhead but there isn't really an alternative. https://github.com/Perustaja/PermissionServerDemo is an example that uses the built in ASP.NET Core authorization evaluation along with gRPC for the network calls. You can make something leaner and simpler that basically does the same thing if you don't want a lot of infrastructure.
I am writing an App and am trying to leverage Google for A&A. The app itself relies on access to the users Google Calendar, and so initially I leveraged their updated OAUTH2 library for A&A.
Here is my flow:
User goes to the index.html which has "https://accounts.google.com/gsi/client" script and google.accounts.oauth2.initCodeClient is called with my client_id, scopes, redirect url
<script src="https://accounts.google.com/gsi/client"></script>
<script>
let client;
function initClient() {
client = google.accounts.oauth2.initCodeClient({
client_id: 'xxxxx-xxxx.apps.googleusercontent.com',
scope:
'https://www.googleapis.com/auth/userinfo.profile \
https://www.googleapis.com/auth/userinfo.email \
https://www.googleapis.com/auth/calendar.readonly \
https://www.googleapis.com/auth/calendar.events',
ux_mode: 'redirect',
redirect_uri: 'http://localhost:5000/oauth2callback',
});
}
// Request an access token
function getAuthCode() {
client.requestCode();
}
The user clicks the login button, which kicks off requestCode() and they begin the login flow. They login or select their google account, then besides the unapproved app screen, they get to the consent screen with my requested scopes.
After, they are redirected to my expressjs endpoint and using the "googleapis" library I exchange with id_token for the access and refresh tokens.
...
const { tokens } = await oauth2Client.getToken(req.query.code); //exchange code for tokens
const userInfo = (
await oauth2Client.verifyIdToken({
idToken: tokens.id_token,
audience: config.google.clientID,
})
).payload;
if (!indexBy.email[userInfo.email]) { // check if user exists
const newUser = {
name: userInfo.name,
email: userInfo.email,
o_id: userInfo.sub,
picture: userInfo.picture,
r_token: tokens.refresh_token,
};
...
Ok, all good.... but not quite. The problem is, that next time the user wants to login to the app, they go through the entire flow again, including the consent screen (again).
So, after going through more docs, even looking at examples from google. I was surprised and I noticed that many of those apps used the passport oauth2 plugin :( Something i've done in the past, but was hoping to avoid that with the recently updated Google client and nodejs libraries.
Ok, how to not prompt for consent screen on subsequent logins?
Maybe separate A&A, so first I use "Sign In With Google" for Authentication, then when I get the user info, check if the user is already registered (hence I have already saved the refresh token) and they start the app.
On the other hand, if they are new (not in existing app user collection), after authenticating, I will then call the OAUTH2 authorization redirect, so again they on Googles site, this time to do the scopes api confirmation.
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed). Hopefully this will still work ok when things come up with expired/invalid refresh token (fingers crossed the default google library handles that).
When doing the Authorize for consent, can I pass something from the previous Authenticate flow so they don't need to do that again.
Or maybe when doing the Authenticate process (Google Identity Service), there is some flag or param so that if they have already consented, they don't have to do that again on subsequent logins.
Incase I wasn't clear, in a nutshell the question is: should I be doing Authenticate for login, separately from Authorization (oauth2 token). Or should I go right into the Authorization flow, which first Authenticates the user, and can I skip the Authorization consent screens if they've already done that. Or maybe there's another way which is the best practice.
Thanks for your attention.
Background info
Authentication is the act where by a user logs in into a system using their login and password. With authentication we know that the user is behind the machine. For this we use Open id connect, which was built on top of Oauth2. Open id connect returns and id_token which can be used to identify the user, it is often a jwt containing some claims to identify the subject or the user behind the Authentication.
The scope used for open id connect is profile and email. open id connect grants you consent to access a users profile information.
This is an example of the decrypted id token returned by google from a simple call using profile scope only. All this id token is telling you is who the user behind the machine is.
{
"iss": "https://accounts.google.com",
"azp": "4074087181.apps.googleusercontent.com",
"aud": "4074087181.apps.googleusercontent.com",
"sub": "1172004755672775346",
"at_hash": "pYlH4icaIx8PssR32_4qWQ",
"name": "Linda Lawton",
"picture": "https://lh3.googleusercontent.com/a-/AOh14GhroCYJp2P9xeYeYk1npchBPK-zbtTxzNQo0WAHI20=s96-c",
"given_name": "Linda",
"family_name": "Lawton",
"locale": "en",
"iat": 1655219027,
"exp": 1655222627
}
In the same call google also returned an access token. Now my call contained only the scope for profile, due to the fact that its open id connect. This means that I will only have access to the data that the profile scope would grant access to. In this case most of what is behind the Google people api.
Note: The user does not see a consent screen with open id connect, even though they are consenting to profile scope. It is assumed by signing into your account that the system you are logging into would have access to your profile info.
Authorization
Authorization is the process by which a user grants your application authorization to access their private user data. The user is shown a consent screen where they consent to your application accessing data defined by some scopes.
In the case of google calendar api there are serval
https://www.googleapis.com/auth/calendar See, edit, share, and permanently delete all the calendars you can access using Google Calendar
https://www.googleapis.com/auth/calendar.events View and edit events on all your calendars
https://www.googleapis.com/auth/calendar.events.readonly View events on all your calendars
https://www.googleapis.com/auth/calendar.readonly See and download any calendar you can access using your Google Calendar
https://www.googleapis.com/auth/calendar.settings.readonly View your Calendar settings
In this case you are only given an access token this is again Oauth2 it is authorization to access the users calendar data it is not authentication this is not related to login.
Your question
So, first question, is that the best practice with most apps with leverage a Google API via OAuth? To first Authenticate, then possibility Authorize (as needed).
You would do both at the same time.
When you authencation your user make sure to include your google calendar scope then the access token and refresh token returned will grant you access to google calendar.
I am going to assume that you have some kind of user system. When you store the user be sure to store the refresh token that is returned.
As far as Authentication goes i will assume you either have a remember me system which will set a cookie on their machine and remember the user so that you can then get the refresh token from their system the next time they come back.
If they did not chose to select a remember me option then will then have to login every time they visit your site but part of the login will return the "sub": "1172004755672775346", this is the users id on google system so you can use that in your database to match the user when they come back.
Your question is quite complex and will depend upon the type of system you have what it is designed to do as well as what programming language you are using. That being said I hope this very long answer clears things up a bit.
I installed a vault and configured OIDC with gsuite, that was already an adventure in itself as the documentation is limited and even wrong at more than one place.
Finally I have a working authentication with my google accounts and I began to create roles, and there I saw a huge issue. How do you restrict google users from using a role. Let's say I create a gsuite-admin role that has access to all of vault administration, any user entering the role before login can assume it.
I tried to use the different claims but those seems to be only for vault created groups or other things.
Does anyone has a solution for that?
Thank you in advance.
EDIT:
The configuration I'm using whith group claims:
{
“allowed_redirect_uris”: “https://URL/ui/vault/auth/oidc/oidc/callback,http://localhost:8250/oidc/callback”,
“user_claim”: “sub”,
“policies”: “vault_admin”,
“ttl”: “24h”,
“groups_claim”: “devops”,
“oidc_scopes”: “profile”,
“bound_claims”: {
“group”: [“devops”]
}
}
That configuration only provides a lock of the role that can't be used anymore by anyone. From what I could see the JWT doesn't have any informations and that is why we used the config with the fetchgroup option in the oidc configuration.
I found a solution for this problem. Firstly, we need to ensure that a user is part of a G Suite group. Then mapping the G Suite group with Vault group (that has a policy assigned) ensures that the user is bound to the Vault policy.
This article contains some example steps and might be helpful.
An app is communicating via the Open ID Connect protocol with AWS Cognito, which is connected to ADFS, communicating via SAML. Cognito is essentially "proxying" the ADFS server.
ADFS holds a group mapping that the app requires, and I would like to import these groups into Cognito as actual Cognito Group - which will then be read by the app from the cognito:groups from the ID-token Cognito provides.
In the AWS Cognito User Pool setup, I don't see a way to map ADFS groups to Cognito Groups - must I absolutely rely on a custom attribute for my User Pool that I can map to the ADFS-property, or am I missing some piece of configuration that allows Cognito to create new groups on the fly and automatically assign the users to the groups in Cognito?
edit: To clarify, Is it possible to setup Cognito to add/create groups (not as a custom property, but a actual manageable cognito groups) when it imports users?
I had the same issue, and I have not found a static mapping option in Cognito either.
The only way I see is to map the AD groups to custom:adgroups attribute in Cognito, and set up a Cognito "Pre Token Generation" lambda trigger. The lambda reads the value of the custom:adgroups and manually overrides the user's Cognito groups.
NB - this does not change the cognito user's group permanently, only for the current session, but from the application perspective that's exactly what I needed.
Please see a dummy static (non conditional) ADMIN group assignment example here:
def lambda_handler(event, context):
print(f'incoming event: {json.dumps(event)}')
# manual cognito group override
if event['triggerSource'] == "TokenGeneration_HostedAuth":
event['response'] = {
"claimsOverrideDetails": {
"groupOverrideDetails": {
"groupsToOverride": [
"ADMIN"
]
}
}
}
return event
More detailed documentation here: https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-pre-token-generation.html
Could you use the Post authentication Lambda trigger to update the user's group in the Cognito User Pool based off the group in AD? You could use the APIs: AdminAddUserToGroup and AdminRemoveUserFromGroup. The only issue with this approach is that if you change the user's group in AD, it won't be updated in Cognito until the user authenticates to Cognito again.
How to setup ADFS with Cognito is documented in this link. The section answering your question is the mapping in step 4, item 5. I'm copying the relevant text below:
Choose Attribute mapping. These mappings map the claims from the SAML assertion from AD FS to the user pool attributes.
Make sure that ADFS is sending the groups in the assertions. For setting up the ADFS side for groups this link might be useful.
You could debug the flow with SAML-tracer plugin in Firefox.
This is probably a question a bit in between SO and UX.se.
I'm implementing a signup/login sytem which allows for multiple ways of authentication: social signups using Facebook, Twitter, Github and a local (username + password) signup.
In the backend I'm having a User model and a Passport model. A User may have multiple Passports, meaning a User may authenticate through different means (e.g.: through local signin,. or through facebook for example)
For good measure: the Passports of a particular User will always be from a different provider (facebook, twitter, local). I.e.: a Facebook-passport, a Local-passport, etc.
This seems like a good way and would allow me to have a User account connected to multiple ways of authentication.
I'm familiar with the security issues this might raise, so for passports to be combined/merged a User has to be logged in to both.
Ok on to the problem. Consider the following flow:
user-a signs up with a provider, say local, with email user-a#gmail.com
user-a signs out (or has it's session expired).
user-a signs in using another provider, say facebook. Chances are the facebook account has a email-record of user-a#gmail.com
Currently, I've defined email to be unique on the User-model. This would mean the above signup would fail, because there's already a User-account that, by means of the local Passport, has the mentioned email-address.
What would be considered a best practice in a situation like this? I trust there must be many implementations floating around that must have seen this problem pop-up.
Options:
Warn the user the authentication isn't possible and mention the user that the current email-address is already registered by means of another authentication mechanism? This would be reasonably user-friendly.
Note that a User-account, by means of a different provider exists with the same email-address and as a consequence merge the new Passport with the User.. I just put this in for good measure: this is a pretty big attack-vector and would allow a user-b to get access to an account by faking the email-address (through a social provider which doesn't do email-validation)
Don't have a uniqueness constraint on <user,email>, but on <passport, email>. This would allow a new User and associated Passport to be created and all goes well. Now the same actual person probably has 2 User Accounts: 1 for each authentication provider. As a next stage allow the User-accounts to be merged, by signing in to both and acknowledging the merge.
I'm leaning towards 3, but 1 is simpler. I'm not sure, what do you think?
Have you encountered this before?
Yes, #3. I have implemented this myself. The concept you're looking at is: Associate multiple SSO accounts. My user structure is as follows:
name : {
first: { type: String},
last: { type: String }
},
emails: [{ type: String, unique: true, 'index': true }], //all known emails as provided by SSO services. we use this to cross ref when the user uses a different SSO to login after initial setup. this avoids account dupes
sso: [{
provider: { type: String, required: true}, //matches the name of passport strategy name employed
userid: { type: String, required: true } //the specific SSO provider userID that's unique in the provider's realm
}]
So, in your auth sequence, you look it up by email OR provider+userid combo, if you don't find the SSO provider, you attach it. The reason for or, someone may update their email but the specific SSO provider ID never changes.
Another common practice (if it makes sense in your app) is to allow the user to "link" SSO accounts. That allows you to handle different email addresses. Example: user FB email is a personal one but in LinkedIn he lists as primary the business one. LinkedIn sadly gives you only the primary via their OAuth2 call.