AWSAmplify - A simple Multi Tenant Approach using Post Confirmation Lambda Trigger
Tags: aws, cognito, javascript, amplify
Published: 2020-10-24 08:04:48

Your application may have the need for separate groups of users to access their own space.  One way of managing this is by using Cognito groups to achieve this.

@AWSAmplify gives you the ability to create a post confirmation lambda trigger out of the box where you can choose the name of a group to add users to.

However this will add all users who signup to the same group which would not give the access control required to have a group per tenant.

# Example 1

Assuming you have already created the post confirmation lambda from the amplify console already, let's modify the generated lambda function to create a random group name of our choosing when a user signs up.

Go to the source for the lambda trigger located at: `amplify/backend/function/<GENERATEDID>PostConfirmation/src/index.js`

Modify it as follows:

```javascript
/* eslint-disable-line */ const aws = require('aws-sdk');
import { v4 as uuidv4 } from 'uuid';

exports.handler = async (event, context, callback) => {
  const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({ apiVersion: '2016-04-18' });

  const groupPrefix = '<SOME_PREFIX>'
  const uuid = uuidv4()

  const groupParams = {
    GroupName: groupPrefix + uuid,
    UserPoolId: event.userPoolId,
  };

  const addUserParams = {
    GroupName: groupPrefix + uuid,
    UserPoolId: event.userPoolId,
    Username: event.userName,
  };

  try {
    await cognitoidentityserviceprovider.getGroup(groupParams).promise();
  } catch (e) {
    await cognitoidentityserviceprovider.createGroup(groupParams).promise();
  }

  try {
    await cognitoidentityserviceprovider.adminAddUserToGroup(addUserParams).promise();
    callback(null, event);
  } catch (e) {
    callback(e);
  }
};
```

When finished push this update using `amplify push`

This will create a group name with a UUID added each time a user signs up if it doesn't already exist then add the user to it.

This can be used as a basis to have individuals be assigned to a group that is created.  This can be extended as in the example below.


# Example 2

The example below will create 2 UserGroups, Admin and Employee and assign the current user to the Admin group.  We also need to resolve an issue with lambda execution time, which is limited to 5 seconds in Cognito.  
As we call more services the lambda trigger takes longer to execute and may be called more than once due to this limitation.

There is also an open bug with the auto generated code that causes the modules to be loaded inside the execution of the lambda rather than at cold-start that uses up much of that time.  See [https://github.com/aws-amplify/amplify-cli/issues/3212#issuecomment-654315960] for a solution.

```javascript
/* eslint-disable-line */
const aws = require('aws-sdk');
const {v4: uuidv4} = require('uuid');

exports.handler = async (event, context, callback) => {
    const cognitoidentityserviceprovider = new aws.CognitoIdentityServiceProvider({apiVersion: '2016-04-18'});

    const groupPrefix = '<SOME_PREFIX>'
    const uuid = uuidv4()
    const owner = '_owner'
    const employee = '_employee'

    const ownerGroup = `${groupPrefix}${uuid}${owner}`
    const employeeGroup = `${groupPrefix}${uuid}${employee}`

    const groupParamsOwner = {
        GroupName: ownerGroup,
        UserPoolId: event.userPoolId,
    }

    const groupParamsEmployee = {
        GroupName: employeeGroup,
        UserPoolId: event.userPoolId,
    }

    const addUserParams = {
        GroupName: ownerGroup,
        UserPoolId: event.userPoolId,
        Username: event.userName,
    }

    const getUserParams = {
        UserPoolId: event.userPoolId,
        Username: event.userName,
    }


    console.log(`Attempting to list groups for ${event.userName}`);

    let userGroups = null

    try {
        userGroups = await cognitoidentityserviceprovider.adminListGroupsForUser(getUserParams).promise();
        console.log('userGroups: ', userGroups)
    } catch (e) {

        console.log("error retrieving user", e)
    }

    if (userGroups == null || userGroups.Groups.length === 0) {
        try {
            await cognitoidentityserviceprovider.getGroup(groupParamsOwner).promise();
        } catch (e) {
            await cognitoidentityserviceprovider.createGroup(groupParamsOwner).promise();
        }

        try {
            await cognitoidentityserviceprovider.getGroup(groupParamsEmployee).promise();
        } catch (e) {
            await cognitoidentityserviceprovider.createGroup(groupParamsEmployee).promise();
        }

        try {
            await cognitoidentityserviceprovider.adminAddUserToGroup(addUserParams).promise();
            callback(null, event);
        } catch (e) {
            callback(e);
        }
    }
};
```

For this approach you will need to give an additional cognito permissions to the lambda function: `adminListGroupsForUser`.

In `amplify/backend/auth/<GENERATEDID>/<GENERATEDID>-cloudformation-template.yml`. we add `- cognito-idp:AdminListGroupsForUser` to the `<GENERATEDID>PostConfirmationAddToGroupCognito` policy.

When finished push this update using `amplify push`