Search code examples
javascriptamazon-web-servicesaws-lambdaamazon-cognitoamazon-ses

Passing custom data to AWS createAuthChallenge trigger


I'm having trouble customizing my Auth Flow with AWS-cognito and AWS-Ses. I would like to do a simple magic link auth, that sends a token by mail whenever a user whats to sign in. My problem is : I need to customize this email before sending it to the users mailbox. I want to have a link that redirects users to my website but they can come from different locations. Let's say, I'm going to redirect some users to https://mysite/foo?<token> and some others to https://mysite/bar?<token>.

But I'm not able to pass custom variables to my AWS cognito auth Triggers.

Right now I have a CUSTOM_AUTH flow, and I use @aws-sdk/client-cognito-identity-provider to initiateAuthCommand. I do not want to use Amplify because all the rest of my app uses the aws SDK.

Here is how I call the initiateAuth command :

import { CognitoIdentityProviderClient, InitiateAuthCommand } from '@aws-sdk/client-cognito-identity-provider'

const poolConfig = {
    accessKeyId: process.env.AWS_APIKEY,
    secretAccessKey: process.env.AWS_SECRETKEY,
    region: process.env.AWS_REGION,
    poolId: process.env.AWS_COGNITO_POOL_ID,
    poolClientId: process.env.AWS_COGNITO_POOL_CLIENT_ID
}

const provider = new CognitoIdentityProviderClient(poolConfig)

const authInput = {
    AuthFlow: "CUSTOM_AUTH",
    AuthParameters : {
        USERNAME: "[email protected]",
    },
    ClientId: process.env.AWS_COGNITO_POOL_CLIENT_ID
}

const signinCommand = new InitiateAuthCommand(authInput)

try {
    const res = await provider.send(signinCommand)
    console.log('Signin success. Result: ', res)
} catch (e) {
    console.log('Signin fail. Error: ', e)
}

and here is my createAuthChallenge trigger :

import { SESv2Client, SendEmailCommand } from "@aws-sdk/client-sesv2";
import { randomDigits } from 'crypto-secure-random-digit';

const config = {
    accessKeyId: process.env.AWS_APIKEY,
    secretAccessKey: process.env.AWS_SECRETKEY,
    region: process.env.AWS_REGION,
}

const client = new SESv2Client(config);

export const handler = async (event) => {

    let secretLoginCode;

    if (!event.request.session || !event.request.session.length) {

        secretLoginCode = randomDigits(10).join('');

        // My problem is here, I can't manage to retrieve here a custom variable, that would be
        // originUrl in this example

        await sendEmail(event.request.userAttributes.email, originUrl, secretLoginCode);

    } else {

        const previousChallenge = event.request.session.slice(-1)[0];
        secretLoginCode = previousChallenge.challengeMetadata.match(/CODE-(\d*)/)[1];
    }

    event.response.publicChallengeParameters = {
        email: event.request.userAttributes.email
    };

    event.response.privateChallengeParameters = { secretLoginCode };

    event.response.challengeMetadata = `CODE-${secretLoginCode}`;

    return event;
};

async function sendEmail(emailAddress, originUrl, secretLoginCode) {

    var params = {
        Destination: {
            ToAddresses: [
                emailAddress
            ]
        },
        Content: {
            Simple: {
                Body: {
                    Html: {
                        Charset: "UTF-8",
                        Data: `<p>Your link to connect is : <a href="${originUrl}/register?t=${secretLoginCode}">here</a></p>`
                    },
                },
                Subject: {
                    Charset: 'UTF-8',
                    Data: 'Test email'
                }
            }

        },
        FromEmailAddress: '[email protected]',
    };

    const command = new SendEmailCommand(params);

    await client.send(command);
}

I have tried a lot of things that seems to be documented in AWS docs here :

https://docs.aws.amazon.com/cognito/latest/developerguide/cognito-user-pool-settings-adaptive-authentication.html#user-pool-settings-adaptive-authentication-accept-user-context-data

It's talking about an EnablePropagateAdditionalUserContextData boolean in my app's client but it looks that this is for a different use case. For device fingerprinting and so. Moreover, it costs extra because you have to enable 'advanced security' and I can't do that as I'm not in charge of the billing. Thus it seems weird to have to pay extra to do something as simple as this.

I have also tried to use ClientMetadata. It should be available in the request to the lambda as this docs shows :

https://docs.aws.amazon.com/cognito/latest/developerguide/user-pool-lambda-create-auth-challenge.html#aws-lambda-triggers-create-auth-challenge-example

But this doc says that these parameters are not communicated to the createAuthChallenge trigger :

https://docs.aws.amazon.com/cognito-user-identity-pools/latest/APIReference/API_InitiateAuth.html

Why ? Why can't I send custom data to my lambda trigger ? I'm sure I'm missing something but I admit being out of ressource to resolve this. I have tried a lot of things but with no result. It is weird because the need to do this kind of thing must be really common, especially if using a CUSTOM_FLOW_AUTH.

What am I missing ? Does someone around here already had this issue ?

Thanks :)


Solution

  • This is based on my experience with Cognito when I was dealing with a similar problem.

    I discovered 2 workarounds to this which require additional AWS resources:

    1. Use a DynamoDB table which store short lived session data, with session ID as the partition key. This gives you the most freedom to store any kind of data. Haven't tested this myself, but I saw a post where someone mentioned this (I'll see if I can dig up the source). You could create an additional "Challenge" that just takes input data and stores it in the table.

    2. Use multiple UserPoolClients. Awhile back I had to customize a text message based on the mobile phone platform (android vs ios) and I could not find a way to do this but ended up having separate user pool clients for the two platforms. This works because the User Pool Client ID is accessible in the event data in all custom trigger handlers.