Can't use Auth.userAttributes with federated sign in - amazon-web-services

In my app, users are only allowed to sign in using google. However, I need to keep track of which users are considered "admins" in my system, and allow admins to promote other users to also be admins. For this, I have a custom attribute in my Cognito user pool called 'admin'.
However, when I tried using Auth.userAttributes, I ran into the error that it expects a CognitoUser object, and Auth.currentAuthenticatedUser isn't returning a CognitoUser object. After some researching, it looks like it just returns whatever it gets from the federated sign in process. Looking at my user pool, it would seem users also aren't being assigned anything at all for my custom attribute.
I'm thinking I may need to use lambda triggers? But which triggers do I use, and how do I get Auth.currentAuthenticatedUser to return a CognitoUser object? I'm thinking I'll at least need a trigger to check if a user is signing in for the first time, and to set the admin attribute to 0. But then do I use Auth.signIn? Also, from https://github.com/aws-amplify/amplify-js/blob/a047ce73/packages/auth/src/Auth.ts#L1191, the source code for Auth.currentAuthenticatedUser, it looks like it only looks for the user in the user pool if the cache doesn't have any entry for 'aws-amplify-federatedInfo', which is added when I use Auth.federatedSignIn. So do I need to clear that?
Thanks in advance. Any info or advice is appreciated.

Tricky! I agree this should be easier. I'd leave the cache alone. Try bypassing the cache.
import { Auth } from 'aws-amplify';
Auth.currentAuthenticatedUser({
bypassCache: true // If set to true, this call will send a request to Cognito to get the latest user data
}).then(user => console.log(user))
.catch(err => console.log(err));
Why? currentAuthenticatedUser() is returning a FederatedUser when this is somewhat helpful because the ideal response would be a CognitoUser. bypassCache alters the response of currentAuthenticatedUser().
May your app bring great felicity!

Related

AWS Cognito Google login overwrites mapped attribute values in user pool

Question specific to SocialIDP (google signin):
I give my users the option to change their 'preferred_username'. When they do, the preferred_username attribute is updated in my cognito user pool.
However, when they sign in again (google signin), Cognito does not keep the value it has stored in the pool for 'preferred_username'. It gets overwritten by whatever Google has for that mapped attribute.
Can anyone help? Here's my function that updates the user pool attributes
export const updateCognitoUserAttributes = async (user, attributes) => {
return await Auth.updateUserAttributes(user, {
...attributes.appSpecificCode
? { 'custom:appSpecificCode': attributes.appSpecificCode}
: { 'email': attributes.email,
'family_name': attributes.family_name,
'given_name': attributes.given_name,
'preferred_username': attributes.preferred_username
}
})
.then(res => {
return res;
})
.catch(err => {
return err;
});
}
Here are 3 pics showing preferred_username before change, after change, and then after I log out and log back in, notice it's changed back to what it was before. I can see why this would happen give that it is mapped to Google's "name" attribute, but the amplify docs indicate this value is changeable (and is in fact, as shown, changed in the pool), but that change should be permanent? What's the point in allowing these attributes to be changed if they just get overwritten every time a user logs in? (it's not just preferred_username that gets overwritten. Any attribute that is mapped to a google attribute gets overwritten). Any help appreciated.
Before change
After change
After logout/login
I guess there's nothing that can be done. Just found this same issue on the amplify github. In short, from amplify documentation:
Amazon Cognito must be able to update your mapped user pool attributes when users sign in to your application. When a user signs in through an identity provider, Amazon Cognito updates the mapped attributes with the latest information from the identity provider. Amazon Cognito updates each mapped attribute, even if its current value already matches the latest information. If Amazon Cognito can't update the attribute, it throws an error. To ensure that Amazon Cognito can update the attributes, check the following requirements:
https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pools-specifying-attribute-mapping.html.
https://github.com/aws-amplify/amplify-js/issues/7300
sigh.
You cannot turn the overwriting off.
However, you can set up an attribute mapping of Google's preferred_username to a custom Cognito attribute like custom:preferred_username.
Then update Cognito's preferred_username using custom:preferred_username in your application code, but only if preferred_username is not set yet.
custom:preferred_username will be overwritten on each login, but after the first login you will ignore it.

Amazon Cognito Need Something Explained

I am looking at the AWS Amplify and AWS Cognito documentation, and I need something explained. Take the following code:
Auth.signIn({
username, // Required, the username
password, // Optional, the password
validationData, // Optional, a random key-value pair map which can contain any key and will be passed to your PreAuthentication Lambda trigger as-is. It can be used to implement additional validations around authentication
}).then(user => console.log(user))
.catch(err => console.log(err));
Here is some more code from the documentation:
Auth.signUp({
username,
password,
attributes: {
email, // optional
phone_number, // optional - E.164 number convention
// other custom attributes
},
validationData: [] //optional
})
.then(data => console.log(data))
.catch(err => console.log(err));
My question is, where do the tokens get stored? Do you store them in state? If so, how do they get refreshed when they do. Or does Auth take care of this and you can just call auth when you need to. If so, do you have to wrap your entire ap with withAuthenticator? I don't understand this. Thanks!
Also, if you want a secure endpoint with AppSync, how does this work? Does it automatically check auth? You're not sending a token so I don't understand how this works. Thanks for your help!
If you're using Amplify it will keep the controls inside the LocalStorage. This will be managed by the Amplify Library. It is also responsible to request a new token when the first one is expired. Again: You don't need to worry about this, the library will manage this for you.
The thing is: you also must use Amplify to send your request o AppSync. When you use Amplify the library will see that you are making a request to a AWS Resource and you're logged in in Cognito and it will append the needed HTTP headers to the request before send it. All this will be done for you by the Amplify library. You can just use it..

AWS Cognito remove required attribute

I created a Cognito User Pool and used the Federated login ( to Facebook )
However when the application goes into testing I foundout that lots of Facebook user do not have "email" which cause the whole login process to fail!
Worse than that, I found out that it is not possible to remove "required attribute" after user pool creation
Also there is no way to easily migate user from one pool to another ( I am exclusively using fedarated login, so no password needed however )
Have anyone get into the same issue and came up with a solution?
As far as I know An attribute cannot be switched between required and not required after a user pool has been created.
I suggest you:
Wanna keep current Userpool -> Create a lambda function, then you can add the required attribute at the pre-sign-up step.
If all user is from Facebook -> Create a new Userpool then export, and import user again. I think no problem in this case because Facebook AppID is same
If your attribute is required, the cognito signup call will fail because of the required attribute. I'm pretty certain it won't even call out to your presignup trigger. That's been my experience at least.

AWS Cognito - run another lambda after migration lambda has run

Cognito has a migration lambda that allows us to confirm a user in our db. They send the email and PW to Cognito, the lambda fires, we verify matches, and the user is entered into Cognito.
At this point - behind the scenes - Cognito generates a username of some kind (UUID). The problem is, I need a way to get this username into our existing database, because our systems going forward will no longer rely on email and instead rely on this username.
Ideal flow:
Sign In
Migration Succeeds
Cognito generates username
Username is sent to our server.
Now because we have email set to auto-verified, no post-confirmation lambda can be called. The only way I see to do this with Cognito as-is is to either:
Ask users who already exist in our system to confirm their email again. This is a non-starter
Create a post-auth lambda, check user login count through a custom attribute, and if 0 (or if not already registered with the service, etc.) migrate the username to the new service.
If there is any other way to do this, please let me know.
After the user migration lambda is called your pre sign-up lambda will be called, assuming you have implemented it. The parameters received by your lambda will include username with the value being the UID you referenced. Parameters will also include user attributes containing email. You can use this information to update your database.
I did not want to add the PreSignup trigger, its a complicated way of doing it if you already rely on PostConfirmation, and if the majority of new users won't be migrations. My use case has a frontend initiate the signup process as well, which I use here.
Instead, I set a Cognito attribute on the new user during the UserMigration trigger. It could be 'user_migration': <oldUserSub>, or however you want to mark it. Just make sure you allow this property within the Cognito user pool settings.
When the UserMigration trigger returns, this information is now accessible through verifying the IdToken, or found in the JWT on the frontend if you're using that. So, when the user is migrated into Cognito and the response gets back to the Cognito client on the frontend, I can now recognize this user needs to be migrated into my personal database. Seeing this, I'll call a new endpoint on my backend to handle this. This new endpoint does exactly what PostConfirmation would typically do.
Then just delete the 'user_migration' property from the Cognito user, return the new user data to the frontend and everything should be set up.
You can use Pre sign-up trigger. In order to detect if the trigger event came from your migration trigger, you can check at the trigger_source value from the event object. In my case (i'm using migration trigger) the value is PreSignUp_AdminCreateUser. By knowing the value of trigger_source you can differentiate if it was migrated or regular user. You can also check the user attributes to know whether the email or phone is verified or not.
Here's my sample code on python:
def lambda_handler(event, context):
trigger_source = event.get('triggerSource')
user_attributes = request.get('userAttributes')
email_verified = user_attributes.get('email_verified')
if trigger_source == 'PreSignUp_AdminCreateUser' and email_verified == 'true':
# create user on db

jax-rs rest webservice authentication and authorization

I have a web application that needs to allow users using different webclients (browser, native mobile app, etc) to register. After signing in they can access restricted content or their own content (like entries they create, etc).
What I did so far: I created a jax-rs rest webservice (I'm hosting my application on glassfish) that exposes the following methods:
register - user POST's his desired username/password/email/etc; if username/email is unique, an entry for this user is created in the database (I'm using Hibernate for persistence)
login - user POST's username and password. If they are ok a UUID is created and returned to the user (this will be used as a token for future requests). I have a table called logedusers, with userID, token, validSince as columns.
Here is where it gets confusing for me.
Let's say that I have another method, getUserEntries, that should return all the entries made by the user. To make this clearer, there will be a Entry table with the following fields: entryId, userId, text.
What is the best approach here?
What i do now, is I make a get request and pass in the token like this:
localhost:8080/myApp/getUserEntries?token=erf34c34
Afterwards, if the token is valid, I get the userID from the logedusers table and based on that userId, get all the entries and return them as json.
Something like this:
#GET
#Path("getUserEntries")
#Produces(MediaType.APPLICATION_JSON)
public Response getUserEntries(#QueryParam("token") String token) {
String userId=getUserIdFromToken(token);
if (userId == null){
return Response.status(Response.Status.UNAUTHORIZED).build();
} else {
//get some data associated with that userId, put it in the response object and send it back
return Response.ok().entity(response).build();
}
}
However, what happens if I have more methods that provide data if they are called by a valid user?
I'd have to do this check at the beginning of every method.
I want to make this authorization process transparent
So, two major questions here:
Is this design ok? The whole authenticate with user/pass, server creates and stores and sends token to the user, user sends token on future requests.
What do I do if i have many endpoints that need to determine the identity of the calling user? Can I mark them with some annotations, use some sort of security provider / authenticator (where I can add my own logic for validating - eg check to see if the token isn't older than 5 days, etc).
Thanks
Is this design ok? The whole authenticate with user/pass, server creates and stores and sends token to the user, user sends token on future requests.
It's somewhat OK. The conceptual level isn't too bad (provided you're OK with self-registration at all) but the interface needs a lot of tweaking. While yes, POST to register and login is correct, for the rest of your webapp you should be pulling the identity information out of the context if you need it, and using role-based access control at the method level where you can.
Note that your container has a whole set of authentication and authorization-support mechanisms built in. Use them.
What do I do if i have many endpoints that need to determine the identity of the calling user? Can I mark them with some annotations, use some sort of security provider / authenticator (where I can add my own logic for validating - eg check to see if the token isn't older than 5 days, etc).
Do they need the identity? Or do they just need to know that the user is allowed to access them? If the latter, the easiest method is to put a suitable #RolesAllowed annotation on the method, at which point (with suitable configuration; see the JEE5 security docs). If the former, you need to get the HttpServletRequest object for the current action and call its getUserPrincipal() method to get the user's identity (or null if they've not logged in yet). This SO question describes how to go about getting the request object; there are a few possible ways to do it but I recommend injection via a #Resource annotation.
What I wouldn't do is allow users to normally provide their own identity via a #QueryParam; that's just wildly open to abuse. You can allow them to ask about other users that way, but then you need to decide whether you are going to tell them anything or not based on whether the current user is permitted to know anything about the other user. That's the sort of complex security problem that comes up in a real app, and is a good point for needing the current verified user identity.