DEV Community 👩‍💻👨‍💻

DEV Community 👩‍💻👨‍💻 is a community of 963,673 amazing developers

We're a place where coders share, stay up-to-date and grow their careers.

Create account Log in
Andrew Shanks
Andrew Shanks

Posted on

AWSAmplify - A simple Multi Tenant Approach using Post Confirmation Lambda Trigger

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:

/* 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);
  }
};
Enter fullscreen mode Exit fullscreen mode

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.

/* 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);
        }
    }
};
Enter fullscreen mode Exit fullscreen mode

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

Top comments (0)

In defense of the modern web

I expect I'll annoy everyone with this post: the anti-JavaScript crusaders, justly aghast at how much of the stuff we slather onto modern websites; the people arguing the web is a broken platform for interactive applications anyway and we should start over;

React users; the old guard with their artisanal JS and hand authored HTML; and Tom MacWright, someone I've admired from afar since I first became aware of his work on Mapbox many years ago. But I guess that's the price of having opinions.