Is it possible to modify AWS Cognito user attributes in the Lambda triggers

23,989

Solution 1

Yes, there's absolutely a way! You need to use AWS javascript SDK in your Lambda handler:

const AWS = require('aws-sdk');
AWS.config.update({region: 'ap-southeast-1'});

const cognitoidentityserviceprovider =
  new AWS.CognitoIdentityServiceProvider({
    apiVersion: '2016-04-18'
  });
cognitoidentityserviceprovider.adminUpdateUserAttributes(
  {
    UserAttributes: [
      {
        Name: 'YOUR_USER_ATTRIBUTE_NAME',
        Value: 'YOUR_USER_ATTRIBUTE_VALUE'
      }
    ],
    UserPoolId: event.userPoolId,
    Username: event.userName
  },
  function(err, data) {
    ...
  }
);

Make sure to give your Lambda function the right policies (i.e. allows "cognito-idp:AdminUpdateUserAttributes" action) and the user pool has the attribute defined.

Solution 2

There isn't a way to mutate/augment attributes during sign up, but during sign in, you can mutate/augment them with the pre-token generation trigger.

Solution 3

For anyone else looking into insight on this question, here is an example below

The lambda function #1 below takes into two custom attributes ida and ethaddress. The lambda is invoked during the PreSignUpHook for Cognito user pools

#2 (Before event changed logs) the original values for these attributes is ida=1 and ethaddress=ABCD

#3 (After event changed logs) reflects the changed values of these attributes: ida=2 and ethaddress=EFGH

However the values which are saved to cognito are the original ones: ida=1 and ethaddress=ABCD. Therefore updateing the userAttributes during the presignuphook does NOT work as suggested in some of the answers.

On a side note when the predefined attributes in the response object are modified they are updated as expected:

"response": {
    "autoConfirmUser": true,
    "autoVerifyEmail": false,
    "autoVerifyPhone": false
}
1. LAMBDA:
'use strict';
global.fetch = require('node-fetch')

module.exports.preSignUp = async (event, context, callback) => {
// Set the user pool autoConfirmUser flag after validating the email domain

let data = await fetch("http://***.***.***/api/members/create",
{
    headers: {
      'Accept': 'application/json',
      'Content-Type': 'application/json'
    },
    method: "POST",
})
.then(res => res.json())
.then(res => res);

event.response.autoConfirmUser = true;
console.log('before event:', JSON.stringify(event)); 
event.request.userAttributes['custom:ethaddress'] = String(data.address); 
event.request.userAttributes['custom:ida'] = "2";  
console.log('Received event:', JSON.stringify(event));  
console.log('Address:', data.address);


 // Return to Amazon Cognito
callback(null, event);
 };
2.

BEFORE EVENT CHANGE LOG:

2019-01-20T01:02:24.639Z    edce636e-75ea-492b-b6a0-dd4f22dc9038    before event:
{
    "version": "1",
    "region": "us-east-1",
    "userPoolId": "us-east-1-*****",
    "userName": "*******@gmail.com",
    "callerContext": {
        "awsSdkVersion": "aws-sdk-unknown-unknown",
        "clientId": "******************"
    },
    "triggerSource": "PreSignUp_SignUp",
    "request": {
        "userAttributes": {
            "custom:ida": "1",
            "custom:ethaddress": "ABCD",
            "email": "*******@gmail.com"
        },
        "validationData": {}
    },
    "response": {
        "autoConfirmUser": true,
        "autoVerifyEmail": false,
        "autoVerifyPhone": false
    }
}
3 .

AFTER EVENT CHANGE LOG:

Received event:
{
    "version": "1",
    "region": "us-east-1",
    "userPoolId": "us-east-1_0BaE6eaTY",
    "userName": "*******@gmail.com",
    "callerContext": {
        "awsSdkVersion": "aws-sdk-unknown-unknown",
        "clientId": "*****************"
    },
    "triggerSource": "PreSignUp_SignUp",
    "request": {
        "userAttributes": {
            "custom:ida": "2",
            "custom:ethaddress": "EFGH",
            "email": "*******@gmail.com"
        },
        "validationData": {}
    },
    "response": {
        "autoConfirmUser": true,
        "autoVerifyEmail": false,
        "autoVerifyPhone": false
    }
}

UPDATE:

It seems like there is no way to do this as a part of the PRESIGNUP process However it is possible to do this as a POSTCONFIRMATION trigger in cognito example provided below.

Some things to watch out for.

  1. the custom attribute has added in cognito and is mutable.
  2. in App client --> show details --> "Set attribute read and write permission" Ensure there is below read and write perms on the custom attribute.
  3. Ensure that the lambda function has a ROLE which allows it to execute: adminUpdateUserAttributes E.g. Attach AmazonCognitoPowerUser Policy to the LambaRole.
module.exports.postConfirmation = async (event, context,callback) => {
        const cognitoIdServiceProvider = new CognitoIdentityServiceProvider({
          region: 'us-east-1'
        });

        var params =  {
            UserAttributes: [
              {
                  Name: 'custom:sillyName',
                  Value: 'customSillyName'
              }
            ],
            UserPoolId: event.userPoolId,
            Username: event.userName
        }

        cognitoIdServiceProvider.adminUpdateUserAttributes(params, function(err, data) {
          if (err) console.log(err, err.stack); // an error occurred
          else     console.log(data);           // successful response
        }); 

        callback(null,event);

};

Note if you attempt to user cognitoIdServiceProvider.adminUpdateUserAttributes in the preSignUp trigger hook you;ll get an exception saying the user does not exit yet

Solution 4

To fill in some detail around @Khoi's very helpful answer, and for all you cut-and-pasters (you know who you are), here is a template for a Lambda that runs off a Cognito User Pool Post Confirmation trigger.

The Lambda sets a new value in a user's custom attribute, which is "fruit" in this example. Here are some gotchas to avoid in the implementation minefield:

  1. The custom attribute must be defined in the user pool at the time you create the user pool, and you must set the attribute Mutable when you define it.
  2. After deploying the Lambda to your AWS account, you need to point the Post confirmation trigger to this Lambda through the User Pools configuration pages in the AWS console.
    Services->Cognito->Manage User Pools->Your User Pools->Triggers->Post Confirmation
  3. The Lambda needs permission to update the User Pool attributes, which is accomplished by attaching an IAM policy to the Lambda that allows "cognito-idp:AdminUpdateUserAttributes" action. See example below.
  4. You access the attribute with the prefix custom: as in custom:fruit .
  5. The colon in the custom attribute makes Javascript unhappy, so when accessing the custom attribute in a client from a CognitoUser object e.g. from the Amplify signIn() method you need to use
    let a = user.attributes['custom:fruit']

Sample Lambda

    const aws = require('aws-sdk');
    const cisProvider = new aws.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' });

    // Cognito User Pool Lambda triggers are documented here:
    // https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-identity-pools-working-with-aws-lambda-triggers.html
    exports.lambdaHandler = async (event, context, callback) => {

        const params = {
            UserPoolId: event.userPoolId,
            Username: event.userName,
            UserAttributes:  // this parameter needs to be an array
                [
                    {
                        Name: 'custom:fruit',
                        Value: 'banana'
                    }
                ]
        };

        if (event.request.userAttributes.email) {
            try {
                await cisProvider
                    .adminUpdateUserAttributes(params)
                    .promise();

                console.log('Success');
            } catch (error) {
                console.error('Error', error);
            }
        }

        callback(null, event);
    };

SAM YAML template

If you are using AWS SAM (as I hope you are) to debug and deploy your Lambda, here is a template for this Lambda. Note the Role resource. Before deploying the Lambda you will need to define this IAM role in the AWS console. There probably is a way to define the role right here in the template, but I'm not that expert with AWS YAML. Deploy the Lambda to your AWS account with the SAM CLI command
sam deploy --guided

  AWSTemplateFormatVersion: '2010-09-09'
  Transform: AWS::Serverless-2016-10-31
  Description: >
    SAM Template for lambda function that runs as a Cognito User Pool post confirmation trigger.
    Cognito invokes this function when a new user signs up.
    
  Globals:
    Function:
      Timeout: 3

  Resources:
    PostConfirmationFunction:
      Type: AWS::Serverless::Function
      Properties:
        CodeUri: post-confirmation/
        Handler: app.lambdaHandler
        Runtime: nodejs14.x
        Role:
          # This role gives Lambda permission to update user pool attributes as well as basic execution.
          arn:aws:iam::xxxxxxxxxxxx:role/lambda-cognito-update-role

  Outputs:
    PostConfirmationFunction:
      Description: "Post Confirmation Lambda Function ARN"
      Value: !GetAtt PostConfirmationFunction.Arn

IAM Role

You need to create a role in the IAM console that includes permission to update user attributes. I prefer to do this with an "inline policy" to avoid a proliferation of IAM policies in my account with unclear dependency. The best way I have found to do this is a two step process:

  1. Create a role in the IAM console with the same name listed in the YAML template, lambda-cognito-update-role in this example. Attach the AWSLambdaBasicExecutionRole in the Permissions step, and give it the the role name lambda-cognito-update-role.
  2. In the IAM section of the AWS console, under Roles, find your newly created role and click to open it.
    • While you're in here, copy the role ARN from the top of the Summary page into your SAM YAML template.
    • Click Add inline policy on the right, click the JSON tab, and overwrite the boilerplate with the JSON below:
{
  "Version": "2012-10-17",
  "Statement": [
    {
        "Sid": "CognitoUpdate",
        "Effect": "Allow",
        "Action": "cognito-idp:AdminUpdateUserAttributes",
        "Resource": "*"
    }
  ]
}

Arguably the resource should be more specific, limited only to user pools in your account maybe. I leave that to your discretion.

Share:
23,989
altus
Author by

altus

Updated on February 17, 2022

Comments