Custom attributes missing in the getUserAttributes response - AWS cognito - amazon-web-services

const userData = { Username: username, Pool: userPool };
const cognitoUser = new AWS.CognitoIdentityServiceProvider.CognitoUser(userData);
cognitoUser.getUserAttributes(function(err, result) {
if (err) {
alert(err);
return;
}
console.log(result)
for (i = 0; i < result.length; i++) {
console.log('attribute ' + result[i].getName() + ' has value ' + result[i].getValue());
}
});
I have a custom field userType. I have set the read, write permission for the custom attribute. But in the response of getUserAttributes i am getting only standard attributes(sub, email_verified, email). How can i retrieve both the standard and custom attributes?
This is my response

First of all, you need to make sure the App client has permissions to read the attribute(s).
Go to AWS Cognito, select the User Pool, then General Settings > App clients and select the App Client, then click "Set attribute read and write permissions"
You should see the following options (notice I have "custom:premium" attribute created and permission to read set)
Then in order to see the custom attributes in cognitoUser.getUserAttributes() results you need to set some value to it, this can be done for example via AWS CLI with the following command:
aws cognito-idp admin-update-user-attributes \
--user-pool-id userPoolId \
--username userName \
--user-attributes Name=custom:premium,Value=1
In the example above I'm setting "custom:premium" attribute to value 1 (obviously you need to replace userPoolId and userName with correct values.
userPoolId can be found in AWS Cognito (for example: eu-central-1_896ERRASw)
userName is username of the user you are currently logged in with in your app (and invoking cognitoUser.getUserAttributes())
And here is the result of the function call after properly setting permission and value of the custom attribute.
Hope it helps!

Related

Cannot write required AWS Cognito attribute

I created a user pool in AWS's Cognito, specified given_name and family_name as required user attributes. I also added an app and checked the permissions :
If - using the Cognito Identidy SDK - I try to register a user and omit the attributes, I get: "Attributes did not conform to the schema: given_name: The attribute is required family_name: The attribute is required" which is expected.
If I include the attributes:
const firstNameAttribute = new CognitoUserAttribute({ Name: 'first_name', Value: firstName });
const lastNameAttribute = new CognitoUserAttribute({ Name: 'last_name', Value: lastName });
cognitoUserPool.signUp(username, password, [firstNameAttribute], [lastNameAttribute], callback);
The server returns
{
"__type":"NotAuthorizedException",
"message":"A client attempted to write unauthorized attribute"
}
How can I fix this and let users set their name during registration?
The app in which the users would try register sent the wrong field names :(
You have to check Write permission is allowed in app client.
Location:
Amazon Cognito > User Pools > {User Pool} > App Integration > App
Client > Attribute read and write permissions > Edit

How to resend confirmation code in Cognito's sign-up process

I'm having issues with resending the confirmation code for cases where for some reason the confirmation code was not delivered to the users. This is what I have:
First, I have a sign-up step:
const cognito = new AWS.CognitoIdentityServiceProvider({region});
const params = {
ClientId,
Username,
Password,
};
const result = await cognito.signUp(params).promise();
This step (in case of success) should send an email to the user's email address (which is also their username) with their confirmation code. Now let's assume for some reason that email is not sent (the reason itself is not important). I would like to provide a chance to the user to ask for a new email to be sent. These are what I've been testing for this purpose so far:
First, I've tested the resendConfirmationCode method:
const cognito = new AWS.CognitoIdentityServiceProvider({region});
const params = {
ClientId,
Username,
};
const result = await cognito.resendConfirmationCode(params).promise();
Executing this code throws this error message:
UnhandledPromiseRejectionWarning: NotAuthorizedException: Cannot resend codes. Auto verification not turned on.
Then, I tested this approach (because of the answer given in this post):
const cognito = new AWS.CognitoIdentityServiceProvider({region});
const params = {
UserPoolId,
Username,
DesiredDeliveryMediums: ["EMAIL"],
ForceAliasCreation: false,
MessageAction: "RESEND",
}
const result = await cognito.adminCreateUser(params).promise();
This time, I'm getting this error:
UnhandledPromiseRejectionWarning: UnsupportedUserStateException: Resend not possible. ********-****-****-****-********** status is not FORCE_CHANGE_PASSWORD
The retracted part is the user's sub.
So, does anyone know how I can resend the confirmation code for a user that is not confirmed yet?
For anyone else who might be facing this issue, this was because the User Pool's setting was not to sent the verification code in the first place. Here's how to enable sending the verification code on User Pool:
Go to Coginto and the User Pool
Go to the page "MFA and verifications"
In the section "Which attributes do you want to verify?" select one of the items (for me it was "Email")
But the actual issue for me was that this option was initially set properly earlier. But it was reset to "No verification" when I executed this command:
aws cognito-idp update-user-pool \
--region us-east-1 \
--user-pool-id us-east-1_********* \
--lambda-config CustomMessage=arn:aws:lambda:us-east-1:************:function:composeEmail
This command is supposed to introduce a lambda function to compose the email for verification code. But for some reason, it will reset the other setting as well and I have no idea why.
In any case, once you have that setting set properly, my first solution will work:
const cognito = new AWS.CognitoIdentityServiceProvider({region});
const params = {
ClientId,
Username,
};
const result = await cognito.resendConfirmationCode(params).promise();
After a chat with AWS support, you can specify verification attribute like this:
aws cognito-idp update-user-pool \
--region us-east-1 \
--user-pool-id us-east-1_********* \
--lambda-config CustomMessage=arn:aws:lambda:us-east-1:************:function:composeEmail
--auto-verified-attributes email

Access permissions on AWS API Gateway

I'm building an application where some data within DynamoDb can be accessed by users over a Rest API.
What I have in mind is:
User accesses API Gateway, authenticated by a Cognito user pool;
API Gateway invokes a Lambda function (written in Python);
Lambda function accesses DynamoDB and returns data.
I'd like to be able to restrict the level of access to DynamoDb according to the user. Initially I thought that the Lambda function could inherit its permissions from the user, but this doesn't work because it needs an execution role.
What is the best way of achieving this? For example, can I pass user information to the Lambda function, which in turn can assume this role before accessing DynamoDb? If so a code example would be appreciated.
Take a look at SpaceFinder - Serverless Auth Reference App and Use API Gateway Lambda Authorizers
With Cognito you can use RBAC:
Amazon Cognito identity pools assign your authenticated users a set of
temporary, limited privilege credentials to access your AWS resources.
The permissions for each user are controlled through IAM roles that
you create. You can define rules to choose the role for each user
based on claims in the user's ID token. You can define a default role
for authenticated users. You can also define a separate IAM role with
limited permissions for guest users who are not authenticated.
so you can create specific roles for each user, although it would be better to use groups
With Lambda authorisers you create your own policy. An example is in awslabs.
In addition to blueCat's answer, I briefly tried giving my Lambda function sts:AssumeRole permissions, and then allowing it to assume the role of the Cognito user that invoked it via the API. I can then use this to get a new set of credentials and carry out some activity with the Cognito user's permissions. Roughly the code inside the lambda is:
def lambda_handler(event, context):
sts_client = boto3.client('sts')
role = event['requestContext']['authorizer']['claims']['cognito:roles']
cognito_role = sts_client.assume_role(
RoleArn=role,
RoleSessionName='lambda-session',
DurationSeconds=3600
)
credentials = cognito_role['Credentials']
sess = boto3.session.Session(
aws_access_key_id=credentials['AccessKeyId'],
aws_secret_access_key=credentials['SecretAccessKey'],
aws_session_token=credentials['SessionToken']
)
# Do something as the assumed user, e.g. access S3
s3_client = sess.client('s3')
# Do stuff here...
Although this works I found that there was roughly 0.5s overhead to assume the role and get the S3 client, and I can't re-use this session between invocations of the function because it is user-specific. As such this method didn't really suit my application.
I've decided instead to give my Lambda full access to the relevant DynamoDb tables, and use the Cognito user groups plus a Lambda authorizer to restrict the parts of the API that individual users are able to call.
I dealt with this issue also but I implemented my solution with Node.js and I figured that although your question is for a Python implementation, then maybe someone would stumble upon this question looking for an answer in JS and I figured this could help out the next person who comes along.
It sounds like you're trying to come up with an effective Authorization strategy after the user has Authenticated their credentials against your Cognito User Pool using custom attributes.
I created a library that I use to export a few functions that allow me to capture the UserPoolId and the Username for the authenticated user so that I can capture the custom:<attribute> I need within my lambda so that the conditions I have implemented can then consume the API to the remaining AWS Services I need to provide authorization to for each user that is authenticated by my app.
Here is My library:
import AWS from "aws-sdk";
// ensure correct AWS region is set
AWS.config.update({
region: "us-east-2"
});
// function will parse the user pool id from a string
export function parseUserPoolId(str) {
let regex = /[^[/]+(?=,)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the user pool id: ", match);
return match.toString();
}
// function will parse the username from a string
export function parseUserName(str) {
let regex = /[a-z,A-Z,0-9,-]+(?![^:]*:)/g;
let match = regex.exec(str)[0].toString();
console.log("Here is the username: ", match);
return match.toString();
}
// function retries UserAttributes array from cognito
export function getCustomUserAttributes(upid, un) {
// instantiate the cognito IdP
const cognito = new AWS.CognitoIdentityServiceProvider({
apiVersion: "2016-04-18"
});
const params = {
UserPoolId: upid,
Username: un
};
console.log("UserPoolId....: ", params.UserPoolId);
console.log("Username....: ", params.Username);
try {
const getUser = cognito.adminGetUser(params).promise();
console.log("GET USER....: ", getUser);
// return all of the attributes from cognito
return getUser;
} catch (err) {
console.log("ERROR in getCustomUserAttributes....: ", err.message);
return err;
}
}
With this library implemented it can now be used by any lambda you need to create an authorization strategy for.
Inside of your lambda, you need to import the library above (I have left out the import statements below, you will need to add those so you can access the exported functions), and you can implement their use as such::
export async function main(event, context) {
const upId = parseUserPoolId(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Step 2 --> Get the UserName from the requestContext
const usrnm = parseUserName(
event.requestContext.identity.cognitoAuthenticationProvider
);
// Request body is passed to a json encoded string in
// the 'event.body'
const data = JSON.parse(event.body);
try {
// TODO: Make separate lambda for AUTHORIZATION
let res = await getCustomUserAttributes(upId, usrnm);
console.log("THIS IS THE custom:primaryAccountId: ", res.UserAttributes[4].Value);
console.log("THIS IS THE custom:ROLE: ", res.UserAttributes[3].Value);
console.log("THIS IS THE custom:userName: ", res.UserAttributes[1].Value);
const primaryAccountId = res.UserAttributes[4].Value;
} catch (err) {
// eslint-disable-next-line
console.log("This call failed to getattributes");
return failure({
status: false
});
}
}
The response from Cognito will provide an array with the custom attributes you need. Console.log the response from Cognito with console.log("THIS IS THE Cognito response: ", res.UserAttributes); and check the index numbers for the attributes you want in your CloudWatch logs and adjust the index needed with:
res.UserAttributes[n]
Now you have an authorization mechanism that you can use with different conditions within your lambda to permit the user to POST to DynamoDB, or use any other AWS Services from your app with the correct authorization for each authenticated user.

How to validate non authenticated users from a mobile app on a server?

My goal : Have a mobile app that does not require users to ever sign in. Have these unauthenticated users hit my server.
What I have : My server is using the AWS API Gateway / AWS Lambda setup. The custom authorizer I used for AWS API Gateway was designed using this example. I Also pasted the code from this example below (A).
My Question : From the code block below (A), I get the impression I should use JWT. How can I use JWT to validate unauthenticated users when these tokens expire? If JWT is not the best thing to use, what would be?
Thanks!
(A)
var nJwt = require('njwt');
var AWS = require('aws-sdk');
var signingKey = "CiCnRmG+t+ BASE 64 ENCODED ENCRYPTED SIGNING KEY Mk=";
exports.handler = function(event, context) {
console.log('Client token: ' + event.authorizationToken);
console.log('Method ARN: ' + event.methodArn);
var kms = new AWS.KMS();
var decryptionParams = {
CiphertextBlob : new Buffer(signingKey, 'base64')
}
kms.decrypt(decryptionParams, function(err, data) {
if (err) {
console.log(err, err.stack);
context.fail("Unable to load encryption key");
} else {
key = data.Plaintext;
try {
verifiedJwt = nJwt.verify(event.authorizationToken, key);
console.log(verifiedJwt);
// parse the ARN from the incoming event
var apiOptions = {};
var tmp = event.methodArn.split(':');
var apiGatewayArnTmp = tmp[5].split('/');
var awsAccountId = tmp[4];
apiOptions.region = tmp[3];
apiOptions.restApiId = apiGatewayArnTmp[0];
apiOptions.stage = apiGatewayArnTmp[1];
policy = new AuthPolicy(verifiedJwt.body.sub, awsAccountId, apiOptions);
if (verifiedJwt.body.scope.indexOf("admins") > -1) {
policy.allowAllMethods();
} else {
policy.allowMethod(AuthPolicy.HttpVerb.GET, "*");
policy.allowMethod(AuthPolicy.HttpVerb.POST, "/users/" + verifiedJwt.body.sub);
}
context.succeed(policy.build());
} catch (ex) {
console.log(ex, ex.stack);
context.fail("Unauthorized");
}
}
});
};
My Question : From the code block below (A), I get the impression I
should use JWT. How can I use JWT to validate unauthenticated users
when these tokens expire? If JWT is not the best thing to use, what
would be?
Why do you need any validation at all? If you want to allow unauthenticated access to your API you should just do that and save yourself some effort.
Update
If you want to restrict access and not require a user to login, you may want to consider simply using Cognito Identity. It supports unauthenticated identities and migration to authenticated access either via Cognito Your User Pools or another federated provider. It is worth noting that if you go this route, you will want to secure/obscure your identity pool id properly within your app to minimize the chance of it being extracted.
Using Cognito Identity, you would be able to get AWS credentials to sign the requests to your API using standard Signature version 4 and avoid the cost and overhead of managing a custom authorizer.

Signing with temporary credentials for API gateway request problems

I'm trying to invoke my API Gateway with authenticated users with the REST API. For this I'm using: Cognito UserPool + Cognito Identity Pool + API Gateway + AWS_IAM Authorization + Cognito Credentials. From what I've gathered I need to sign my request with (temporary credentials). Based on this thread I want to sign my request with the following keys:
{
SecretKey: '',
AccesKeyId: '',
SessionKey: ''
}
If I use an associated user from my IAM console and use the corresponding SecretKey + AccesKeyID everything works fine. However, I want to use the Unauthenticated and Authenticated roles from my Identity Pools to apply IAM policies based on authenticated or unauthenticated users.
FYI: I can call the authenticated functions from this part of the documentation.
I'm building a React-Native app, and because of that I want to keep the native SDK to a minimum and I'm only using AWSCognitoIdentityProvider part. For the user handling.
I trying to receive the correct keys using this Objective-C code:
[[self.credentialsProvider credentials] continueWithBlock:^id(AWSTask *task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
else {
AWSCredentials *response = task.result;
NSString *accessKey = response.accessKey;
NSString *secretKey = response.secretKey;
NSString *sessionKey = response.sessionKey;
NSDictionary *responseData = #{
#"AccessKey" : accessKey,
#"SecretKey" : secretKey,
#"SessionKey": sessionKey
};
}
return nil;
}];
The rest I've setup using the relevant docs.
I (wrongly?) tried to sign my requests with the
AccessKey, SecretKey, SessionKey retrieved from the CredentialsProvider above.
{
SecretKey: credentials.SecretKey,
AccesKeyId: credentials.AccessKey,
SessionKey: credentials.SessionKey
}
The signing fails with the following error:
{ message: 'The security token included in the request is invalid.' }
So the question I have is: which keys should I use to sign my requests for authenticated users so that I can apply the attached IAM policies from my Cognito Setup?
Thanks for any help :)
Like Michael - sqlbot, point out, it should be SessionToken instead of SessionKey. I found a better instruction on how to get credentials from Cognito.