AWS Cognito getCurrentUser() after authentication with no refresh - amazon-web-services

I'm trying to update a user's attribute right after authentication.
Auth works fine and I'm able to retrieve user attributes with it.
But the problem is var cognitoUser = getUserPool().getCurrentUser(); returns null. How do I retrieve the current user so that I am able to update the attribute but without refreshing the browser?
Perhaps another question would be, how do I use the accessToken to run functions on the current user without refreshing the browser?
var cognitoUser = getUserPool().getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
if ( err ) {
console.log(err);
return;
}else if( session.isValid() ){
updateUserAttribute( cognitoUser, 'custom:attr', attr )
}
});
}else{
console.log('Cognito User is null');
return;
}

getUserPool().getCurrentUser() looks for the user on the local storage, if it returns null is because the local storage do not have an user already set.
To setup the user on the local storage I use an instance of CognitoAuth that makes the job.
This solution is using Cognito Hosted UI.
On the callback url that is returned by Cognito UI:
Then, if you call getUserPool().getCurrentUser() this will not be null.
import { CognitoAuth } from "amazon-cognito-auth-js"
const authData = {
UserPoolId: "us-east-1_xxxxxx",
ClientId: "xxxxxxx",
RedirectUriSignIn: "https://examole.com/login",
RedirectUriSignOut: "https://example.com/logout",
AppWebDomain: "example.com",
TokenScopesArray: ["email"]
}
const auth = new CognitoAuth(authData)
auth.userhandler = {
onSuccess: function(result) {
//you can do something here
},
onFailure: function(err) {
// do somethig if fail
}
}
//get the current URL with the Hash that contain Cognito Tokens tokens
const curUrl = window.location.href
//This parse the hash and set the user on the local storage.
auth.parseCognitoWebResponse(curUrl)
Then, if you call getUserPool().getCurrentUser() this will not be null.

var cognitoUser = getUserPool().getCurrentUser();
That should return the last authenticated user, if the user pool is initialized correctly. Upon a successful authentication, we save the user keyed on the client id to local storage. Whenever you call getCurrentUser, it will retrieve that particular last authenticated user from local storage. The tokens are keyed on that user and client id. They are also saved to local storage after a successful authentication. Accessing the access token should be just:
cognitoUser.getSignInUserSession().getAccessToken().getJwtToken())
and you can use the token directly with the operations exposed in the CognitoIdentityServiceProvider client.

This is a fairly old question, so you may have moved on by now, but I think you need to create the user pool, using your userpoolId and clientID. I don't think you can just call getUserPool() without those being known and/or somehow available in memory. For example:
function makeUserPool () {
var poolData = {
UserPoolId : "your user pool id here",
ClientId : "you client id here"
};
return new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
}
var userPool = makeUserPool();
var currAWSUser = userPool.getCurrentUser();

Related

can we add custom data while requesting jwt id token in aws

exports.signIn = (body) => {
return new Promise((resolve, reject) => {
var authenticationData = {
Username: body['username'],
Password: body['password'],
};
var authenticationDetails = new AmazonCognitoIdentity.AuthenticationDetails(authenticationData);
var userPool = new AmazonCognitoIdentity.CognitoUserPool(poolData);
var userData = {
Username: body['username'],
Pool: userPool,
};
var cognitoUser = new AmazonCognitoIdentity.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: (result) => {
resolve({
"status": 1, "message": "user signed in successfully ", "data": {
"idToken": result.getIdToken().getJwtToken(),
"accessToken": result.getAccessToken().getJwtToken(),
"refreshToken": result.getRefreshToken().getToken()
}
});
},
onFailure: (error) => {
let message = "User sign in failed " + error
let status = 0
if(error.code == 'NotAuthorizedException'){
message = "Incorrect username or password"
} else if(error.code == 'UserNotConfirmedException'){
message = "User confirmation pending with OTP"
status = 2
}
reject({ "status": status, "message": message });
},
});
})
}
I need to add custom data inside the id token. The data is dynamic, so I cannot add it as a custom field in cogito user detail. The exact requirement is: Just before creating the id token, I need to fetch the data from the database and include it with JWT id token.
If you want a cognito jwt token, it's not possible at the moment.
In order to add data to JWT id token, you need decode the token, add data, and encode the updated data. However, in order to encode the updated data, you would need the private key that AWS cognito uses, and there is no way to get it or use your own private key at the moment to my knowledge.
An alternative method would be to use your own private key when you encode the updated data. Yet, the token would not be a Cognito JWT anymore and could cause problems in other parts of your app.
Therefore, my suggestion is to pass the data separately instead of including it in the JWT token. If you can include JWT token in your future requests, you can also include a parameter or a body as well. If it is a sensitive data, you can encode it with the typical algorithms
References:
where can I find the secret key for the JWT from cognito

How can I force a cognito token refresh from the client

I am using aws amplify and I know that the tokens get automatically refreshed when needed and that that is done behind the scenes.
What I need to do is change a custom attribute on the user in the cognito user pool via a Lambda backend process. This I can do, and it is working. However, the web client user never sees this new custom attribute and I am thinking the only way they can see it is if the token gets refreshed since the value is stored within the JWT token.
The correct solution as of 2021 is to call:
await Auth.currentAuthenticatedUser({bypassCache: true})
Here is how you can update tokens on demand (forcefully)
import { Auth } from 'aws-amplify';
try {
const cognitoUser = await Auth.currentAuthenticatedUser();
const currentSession = await Auth.currentSession();
cognitoUser.refreshSession(currentSession.refreshToken, (err, session) => {
console.log('session', err, session);
const { idToken, refreshToken, accessToken } = session;
// do whatever you want to do now :)
});
} catch (e) {
console.log('Unable to refresh Token', e);
}
Like it's said here:
https://docs.aws.amazon.com/cognito/latest/developerguide/amazon-cognito-user-pools-using-tokens-with-identity-providers.html
The access token and ID token are good for 1 hour. With Amplify you can get the info about the session using currentSession or currentUserInfo in Auth class to be able to retrieve information about tokens.
Undocumented, but you can use the refreshSession method on the User. Your next call to currentAuthenticatedUser and currentSession will have updated profile attributes (and groups)
User = Auth.currentAuthenticatedUser()
Session = Auth.currentSession()
User.refreshSession(Session.refreshToken)
#andreialecu wrote the correct answer. For full code to get the JWT:
static async amplifyRefresh() {
try {
const currentUser = await Auth.currentAuthenticatedUser({ bypassCache: true })
const currentSession = await Auth.currentSession()
const jwt = currentSession.getIdToken().getJwtToken()
// do what you want
} catch (error) {
console.log("error refreshing token: ", error)
throw error
}
}

AWS Cognito js: getCurrentUser() returns null

Building a simple application using the examples on their github page. I can log into my application using Cognito. What I can not do is logout because no matter what I try I can't get a hold of the user object. I've dorked around with various other calls to no avail (found here on their API page). The only other post on SO I found isn't applicable because I'm not using Federated Identity. The code I'm using is pretty much verbatim what's on the github page, but will post here for convenience:
login code:
var userName = $('#user_name_login').val();
var userPassword = $('#user_password_login').val();
var userData = {Username: userName, Pool : userPool};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
var authenticationData = {Username : userName, Password : userPassword};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
// now that we've gotten our identity credentials, we're going to check in with the federation so we can
// avail ourselves of other amazon services
//
// critical that you do this in this manner -- see https://github.com/aws/amazon-cognito-identity-js/issues/162
// for details
var loginProvider = {};
loginProvider[cognitoCredentialKey] = result.getIdToken().getJwtToken();
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId: identityPoolId,
Logins: loginProvider,
});
// //AWS.config.credentials = AWSCognito.config.credentials;
// AWSCognito.config.credentials = AWS.config.credentials;
// //call refresh method in order to authenticate user and get new temp credentials
// AWS.config.credentials.refresh((error) => {
// if (error) {
// alert(error);
// } else {
// console.log('Successfully logged in!');
// }
// });
// this is the landing page once a user completes the authentication process. we're getting a
// temporary URL that is associated with the credentials we've created so we can access the
// restricted area of the s3 bucket (where the website is, bruah).
var s3 = new AWS.S3();
var params = {Bucket: '********.com', Key: 'restricted/pages/user_logged_in_test.html'};
s3.getSignedUrl('getObject', params, function (err, url) {
if (err) {
alert(err);
console.log(err);
}
else {
console.log("The URL is", url);
window.location = url;
}
});
},
mfaRequired: function(session){
new MFAConfirmation(cognitoUser, 'login');
},
onFailure: function(err) {
alert("err: " + err);
},
});
I'm attempting to logout by executing:
userPool.getCurrentUser().signOut();
Note that the userPool and such are defined in another file, and is initialized thusly:
var poolData = {
UserPoolId : '*****',
ClientId : '*****'
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
so how do I sign my users out of the application?
closing this as the issue, as stated here, turned out to be a red herring. if you're doing what I was trying to do above in using cognito to generated a signed url to access an html file located in a restricted 'folder' in the bucket and you want to be able to logout from that new window location, make sure the signed url is of the same domain as your landing page.
for example, if you land at foo.com because you've got an A or CNAME DNS record set up so that your users don't have to hit a doofy cloudfront or s3 generated url in order to get to your website, you need to make sure you ALSO generate a signed URL that has the same domain name. Otherwise you won't be able to access the bucket. Moreover - you won't be able to access your user object because the session object is keyed to a different domain name than the one you're currently at.
see this for information on how to specify what the domain of the signed url should be.
and also note that there's a lot of trouble you can get into if you are using a third-party domain registar. I just burned two weeks unborking myself because of that :-/

Managing cognito users attributes from Lambda -- must they sign in?

I have done lots of get / set attributes work client side with the Cognito SDK, but now I have the need to be able to modify a user's custom attribute from the backend via Lambda functions (within a step function).
But in the client side version of the process, there is a step where I have to retrieve the current Cognito user, which is available because they had previously authenticated. Here's that code:
var poolData = this.poolData;
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var cognitoUser = userPool.getCurrentUser();
if (cognitoUser != null) {
cognitoUser.getSession(function(err, session) {
var attributeList = [];
var attribute = {
Name : attr,
Value : value
};
var attribute = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserAttribute(attribute);
attributeList.push(attribute);
console.log("attributeList", attributeList);
cognitoUser.updateAttributes(attributeList, function(err, result) {
callback(err, result);
});
});
}
But in the backend version of this, i'm technically administering a user.
So, how would I modify a user's attributes data from a lambda function? where the user isn't necessarily signing in first?
You can't update the attributes on the user as you point out because you do not have a token for that user.
Instead use the adminUpdateUserAttributes on CognitoIdentityServiceProvider that takes a username in the parameters and updates the attributes.
This will require you to have the correct permissions on your Lambda execution role since that will be used for the SDK call. So you need permissions for your lambda to edit the coginito attributes.
Documentation of the call: adminUpdateUserAttributes

Change password using AWS.CognitoIdentityServiceProvider

I'm trying to figure out how to use the changePassword function of the AWS.CognitoIdentityServiceProvider.
I need to pass the following as params:
{
PreviousPassword: 'STRING_VALUE', /* required */
ProposedPassword: 'STRING_VALUE', /* required */
AccessToken: 'STRING_VALUE'
}
I use this inside a Lambda function, so how do I get hold of the access token? I have the cognitoIdentityPoolId and the cognitoIdentityId to use, but I can't understand which this access token is.
Because there is no admin version of changePassword, you must first authenticate as the user you are trying to impact, then you can call the changePassword routine. It took me a long time to figure this out and no other posts seem to cover the case where you are running a NodeJS lambda function with the admin calls and UserPools, where you want to support "admin" changing of a user password. My (currently working) code is below. Note I believe preventing the admin from changing the user password is a deliberate design decision made by AWS, so I am not sure how long the workaround below will continue to be valid...
const cognitoidentityserviceprovider = new AWS.CognitoIdentityServiceProvider();
// Accept a POST with a JSON structure containing the
// refresh token provided during the original user login,
// and an old and new password.
function changeUserPassword(event, context, callback) {
// Extract relevant JSON into a request dict (This is my own utility code)
let requiredFields = ['old_password','new_password','refresh_token'];
let request = Utils.extractJSON(event['body'], requiredFields);
if (request == false) {
Utils.errorResponse("Invalid JSON or missing required fields", context.awsRequestId, callback);
return; // Abort here
}
// This function can NOT be handled by admin APIs, so we need to
// authenticate the user (not the admin) and use that
// authentication instead.
let refreshToken = request['refresh_token']
// Authenticate as the user first, so we can call user version
// of the ChangePassword API
cognitoidentityserviceprovider.adminInitiateAuth({
AuthFlow: 'REFRESH_TOKEN',
ClientId: Config.ClientId,
UserPoolId: Config.UserPoolId,
AuthParameters: {
'REFRESH_TOKEN': refreshToken
},
ContextData: getContextData(event)
}, function(err, data) {
if(err){
Utils.errorResponse(err['message'], context.awsRequestId, callback);
return // Abort here
} else {
// Now authenticated as user, change the password
let accessToken = data['AuthenticationResult']['AccessToken'] // Returned from auth - diff signature than Authorization header
let oldPass = request['old_password']
let newPass = request['new_password']
let params = {
AccessToken: accessToken, /* required */
PreviousPassword: oldPass, /* required */
ProposedPassword: newPass /* required */
}
// At this point, really just a pass through
cognitoidentityserviceprovider.changePassword(params, function(err2, data2) {
if(err2){
let message = {
err_message: err2['message'],
access_token: accessToken
}
Utils.errorResponse(message, context.awsRequestId, callback);
} else {
let response = {
'success': 'OK',
'response_data': data2 // Always seems to be empty
}
callback(response)
}
});
}
});
}
As You are using the AWS Lambda you dont need to worry about the access token you can simply pass the username and password along with the poolID to the cognito function adminSetUserPassword().this function will update the password easily
const updateCognitoPassword = async(user_name, password) => {
try {
var changePasswordParams = {
Password: password,
Permanent: true,
Username: user_name.trim(),
UserPoolId: constants.user_pool_id
};
let data = await cognitoidentityserviceprovider.adminSetUserPassword(changePasswordParams).promise();
return data;
}
catch (err) {
throw new Error(err);
}
};
I would like to extend on some answers above with a solution that can be used inside the lambda function and also shows how to set the authentication required (using an AWS access key and secret access key.
This is a worked example of a "change password" function created as a lambda.
export async function change_password (event, context, callback) {
context.callbackWaitsForEmptyEventLoop = false;
try {
const { aws_cognito_id, newPassword } = JSON.parse(event.body)
const cognitoIdentityService = new AWS.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18', region: '***AWS REGION GOES HERE***' });
const userPoolId = "***COGNITO USER POOL ID GOES HERE***";
const params = {
Password: newPassword,
Permanent: true,
Username: aws_cognito_id,
UserPoolId: userPoolId
};
AWS.config.region = '**AWS REGION**';
cognitoIdentityService.config.update({
accessKeyId: '***AWS ACCESS KEY***',
secretAccessKey: '***AWS SECRET ACCESS KEY***'
})
let result = await cognitoIdentityService.adminSetUserPassword(params).promise();
return generate_response(200, result)
} catch (err) {
return generate_error(500, err.message)
}
}
The identity pool id and identity id are Cognito federated identities concepts, while the ChangePassword API is a user pools one. They are two different services - think of user pools as an identity provider to your identity pool.
The short version is you can get the access token by signing in with a user in your user pool. Doing so returns an access token, id token, and refresh token. That being said, a common theme is to use the admin versions of the various user pool APIs on Lambda side, since you may not have user credentials there.