Amazon Cognito integration

This guide covers how to use Amazon Cognito to authenticate with a Fauna database.

Once set up, Cognito issues a JWT when end users log into your application. The JWT contains a Fauna token's authentication secret for the user in a private claim. Your application can use the secret to run queries on the user’s behalf.

Before you start

To complete this guide, you’ll need:

Authentication flow

You can’t use Cognito as an access provider. Cognito JWTs don’t support aud claims, which are required for JWTs used as an authentication secret in Fauna.

Instead, the setup uses the following authentication flow:

  1. The client application sends a request to Cognito. Cognito invokes an AWS Lambda function in the token generation phase.

  2. The Lambda function generates a Fauna token for the end user. The function includes the token’s secret in a private fauna_token_secret claim in the payload of the JWT issued by Cognito.

  3. Cognito returns the JWT to the client application. The client application uses the token secret to authenticate Fauna queries on the user’s behalf.

AWS Cognito authentication flow

Configure Fauna

  1. Log in to the Fauna Dashboard and select your database.

  2. In the Dashboard Shell, run the following FQL query to create a key with the server role:

    Key.create({
      role: "server"
    })
    {
      id: "404130885316640841",
      coll: Key,
      ts: Time("2099-07-22T17:08:15.803Z"),
      role: "server",
      secret: "fn..."
    }

    Copy the key’s secret. You’ll use it later.

  3. Create a collection to store identity documents for your application’s end users.

    Edit the collection’s schema to include an email field or similar identifier used to uniquely identify users. For example:

    collection Customer {
      unique [.email]
    
      index byEmail {
        terms [.email]
      }
    }
  4. Create one or more user-defined roles.

    Edit the role’s schema to include the previous collection in the role’s membership property. For example:

    role customer {
      membership Customer
    
      privileges Product {
        read
      }
      ...
    }

Create an AWS Lambda function

  1. Initialize a new SAM project. In your terminal, run:

    sam init
  2. When prompted, select:

    • AWS Quick Start Templates as the template source.

    • Hello World Example as the AWS Quick Start application template.

    • Enter N (no) when asked to use the most popular runtime and package type.

    • nodejs20.x as the runtime

    • Zip as the package type

    • Hello World Example as the starter template

Follow the prompts and select the Hello World Example (Node.js 18.x) starter template.

  1. Navigate to the project directory and install the Fauna JavaScript driver.

    cd your-project-name
    npm install fauna --save
  2. In the project directory, open app.mjs. The file contains the JavaScript code for the Lambda function. Replace the file’s contents with the following:

    import { Client, fql } from 'fauna';
    
    const client = new Client({
      secret: process.env.FAUNA_SECRET,
    });
    
    export const lambdaHandler = async (event, context) => {
      // Get the email from event.
      const email = event.request.userAttributes['email'];
      const fauna_response = await client.query(fql`
        // If a Customer document with the email exists, get it.
        // Otherwise, create a Customer document.
        let user = Customer.byEmail(${email}).first() ??
                   Customer.create({email: ${email}})
        // Create a token for the Customer document.
        let token = Token.create({
            document: user,
            ttl: Time.now().add(30, 'minutes')
        })
        // Return the Customer document's ID and the
        // token's secret.
        let payload = {
            userId: user!.id,
            token: token.secret
        }
        payload
      `);
      event.response = {
        "claimsOverrideDetails": {
          "claimsToAddOrOverride": {
            fauna_token_secret: fauna_response.data.token,
            userId: fauna_response.data.userId
          },
          "claimsToSuppress": ["email"]
        }
      };
      context.done(null, event);
      return event;
    };
  3. Notice in the Lambda function code you make a query to Fauna to create an identity document for the user. The ttl property sets the token’s expiration time to 30 minutes.

  4. Define the environment variable FAUNA_SECRET in the template.yaml file. In the template.yaml file, locate the Environment section under your Lambda function’s configuration. If it doesn’t exist, you can add it.

  5. In template.yaml, add the FAUNA_SECRET environment variable. Set the variable’s value to the Fauna key secret you created earlier.

    Resources:
      HelloWorldFunction:
        Type: AWS::Serverless::Function
        Properties:
          CodeUri: hello-world/
          Handler: app.lambdaHandler
          Runtime: nodejs20.x
          Environment:
            Variables:
              FAUNA_SECRET: "fn..."
  6. Build and deploy the Lambda function by running the following commands:

    sam build
    sam deploy --guided
  7. Note the name of the Lambda function. You’ll use it in the next step to integrate with Cognito.

Configure AWS Cognito

  1. Follow the Amazon Cognito guide to create a new user pool.

  2. Note your user pool ID and client ID. You’ll use it later to integrate with your application.

  3. Navigate to the user pool’s User-pool-properties tab and click Add Lambda trigger.

  4. On the Add-Lambda-trigger page, select Authentication > Pre token generation trigger.

  5. In the Assign Lambda function section, enter select the Lambda function you previously created.

  6. Click Add Lambda Trigger.

Test user access

Verify that the setup works:

  1. Create a test user in AWS Cognito.

  2. Log in to the AWS Cognito user pool using the test user’s credentials.

    The following Node.js sample code logs in a user using the AWS Cognito SDK: :

    // signin.js
    import {
      CognitoUserPool,
      CognitoUser,
      AuthenticationDetails
    } from 'amazon-cognito-identity-js';
    
    const config = {
      UserPoolId: '<COGNITO_USER_POOL_ID>',
      ClientId: '<COGNITO_CLIENT_ID>'
    }
    
    const poolData = {
      UserPoolId: config.UserPoolId,
      ClientId: config.ClientId,
    };
    
    const userPool = new CognitoUserPool(poolData);
    
    const cognitoUser = new CognitoUser({
      Username: '<USER_EMAIL>',
      Pool: userPool,
    });
    const authenticationDetails = new AuthenticationDetails({
        Username: '<USER_EMAIL>',
        Password: '<USER_PASSWORD>',
    });
    
    cognitoUser.authenticateUser(authenticationDetails, {
      onSuccess: data => {
        console.log(data);
      },
    
      onFailure: err => {
        console.log('Failed', err)
      },
    
      newPasswordRequired: newPass => {
        console.log('New Pass Required', newPass)
      }
    })
  3. After successful login, you should receive a CognitoIdToken JWT with the fauna_token_secret field in the payload:

    ognitoUserSession {
      idToken: CognitoIdToken {
        jwtToken: '...',
        payload: {
          ...
          fauna_token_secret: 'fn...',
          ...
        }
      },
      refreshToken: CognitoRefreshToken {
        ...
      },
      accessToken: CognitoAccessToken {
        ...
      },
      clockDrift: 0
    }
  4. In your client application, use the fauna_token_secret to run Fauna queries on behalf of the user.

Is this article helpful? 

Tell Fauna how the article can be improved:
Visit Fauna's forums or email docs@fauna.com

Thank you for your feedback!