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
Related
I use AWS API Gateway + AWS Cognito (Federated Identity + User Pool). I'm creating REST API in API Gateway. There are methods for getting users list and for getting user by id. What should I use as user ID?
Response should contain te following user data: ID, username, first name, last name etc. Username, first name, last name are stored in User Pool attributes.
I see in examples that they use cognitoIdentityId as User ID. If I will use cognitoIdentityId as ID, then how should I get user data by this ID? Please give me an example or link to API reference how to get user data by cognitoIdentityId.
You should be able to get user attributes as shown in the following example using the Username.
var AWS = require('aws-sdk');
AWS.config.region = 'us-east-1'; //This is required to derive the endpoint
var poolData = { UserPoolId : 'us-east-1_TcoKGbf7n',
ClientId : '4pe2usejqcdmhi0a25jp4b5sh3'
};
var userPool = new AWS.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.getUserAttributes(function(err, result) {
if (err) {
alert(err);
return;
}
for (i = 0; i < result.length; i++) {
console.log('attribute ' + result[i].getName() + ' has value ' + result[i].getValue());
}
});
Reference: Examples: Using the JavaScript SDK
If you need to query the user attributes using AccessToken, then use the following example.
var params = {
AccessToken: 'STRING_VALUE' /* required */
};
cognitoidentityserviceprovider.getUser(params, function(err, data) {
if (err) console.log(err, err.stack); // an error occurred
else console.log(data); // successful response
});
Reference: AWS JavaScript SDK GetUser
Note: Currently there is no support in both AWS JavaScript SDK and API to get the user attributes by UserId.
I am new to aws cognito. Here is what I am trying to do:
I have some users and I want them to login to my website and then as far as their session is valid I would like to keep them in without forcing them to sign in. So as far as I understand I need to generate STS token using cognito and send to the user and then in the next calls user will send the sts token as a header and I check the sts token using cognito and if it is valid I will serve the users.
For that I use the instruction in the following link:
https://github.com/aws/amazon-cognito-identity-js/
So specifically I use this part to authenticate the user and establish session:
var authenticationData = {
Username : 'username',
Password : 'password',
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
var poolData = {
UserPoolId : '...', // Your user pool id here
ClientId : '...' // Your client id here
};
var userPool = new AWSCognito.CognitoIdentityServiceProvider.CognitoUserPool(poolData);
var userData = {
Username : 'username',
Pool : userPool
};
var cognitoUser = new AWSCognito.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.authenticateUser(authenticationDetails, {
onSuccess: function (result) {
console.log('access token + ' + result.getAccessToken().getJwtToken());
//POTENTIAL: Region needs to be set if not already set previously elsewhere.
AWS.config.region = '<region>';
AWS.config.credentials = new AWS.CognitoIdentityCredentials({
IdentityPoolId : '...', // your identity pool id here
Logins : {
// Change the key below according to the specific region your user pool is in.
'cognito-idp.<region>.amazonaws.com/<YOUR_USER_POOL_ID>' : result.getIdToken().getJwtToken()
}
});
// Instantiate aws sdk service objects now that the credentials have been updated.
// example: var s3 = new AWS.S3();
},
onFailure: function(err) {
alert(err);
},
});
So far so good. However, now in any subsequent call I do not want to sent
var authenticationData = {
Username : 'username',
Password : 'password',
};
and instead I want to use the sts token to authenticate the user:
var authenticationData = {
sts: 'XXXXXXXXXXXXXXXXXXXXXXXXXXXXXXXX'
};
var authenticationDetails = new AWSCognito.CognitoIdentityServiceProvider.AuthenticationDetails(authenticationData);
I do not see any example or usage of such a case. Is cognito the right service for this scenario and if yes how can I do the authentication check with sts?
I see that you are using AWS.CognitoIdentityCredentials in your authentication callback. That's used if your app needs AWS credentials to access various AWS services.
If your users are only interacting with your own website, the session you get after successful user authentication is valid up to 30 days by default.
You can check if the session is active or not by calling getSession on CognitoUser
https://github.com/aws/amazon-cognito-identity-js/blob/9e949c08188f13b8087106564e7b596ec58117ab/src/CognitoUser.js#L826
You can configure that duration by changing Refresh Token Validity for your client in Cognito User Pool console.
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 :-/
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.
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();