DEV Community

Cover image for Firebase and Fauna B2C Auth App in AWS Land
Samuel Danquah
Samuel Danquah

Posted on • Updated on

Firebase and Fauna B2C Auth App in AWS Land

Introduction

In the previous article, we explored the authentication offerings of Firebase to turbocharge your next Fauna-powered application and introduce the building blocks to architect applications for the client-serverless paradigm. Now we will build a serverless authentication business-to-consumer (B2C) backend with services from the AWS ecosystem. But first, let's look at a few prerequisites needed to begin our journey:

Requirements

Authentication Requirements and Strategy

The client (frontend) application will communicate with our backend through an API. AWS provides API Gateway, which enables us to create, maintain, and secure our authentication API at any scale. API Gateway offers two RESTful API options for our B2C authentication application - HTTP and REST. Which option do we utilize for our business-to-consumer backend application? These two preliminary questions hold the clue to this puzzle:

  • Where are the target users located?
  • How do users access the authentication API?

This comprehensive guide helps you decide between the two for your application needs. For this sample application, we are working with the following assumptions:

  • Our users reside in a single continent.
  • Our users will access the authentication API from our client application.

Hence, we utilize the HTTP API option, which provides low latency and cheap regional API to proxy our business logic for authentication. AWS Lambda will encapsulate this business logic. To make working with the various AWS services a breeze, we will use the serverless framework. Let's build this faunabase authentication application.

Serverless Authentication App in AWS ecosystemServerless Authentication App in AWS ecosystem

Faunabase Authentication App

We install the serverless framework and get a free dashboard account from this guide. If you don't have Node installed, be sure to do that first. The recommended NodeJS version for installation is the long-term support (LTS) version. Next, we create our faunabase-auth application using the AWS NodeJS template with the following command:

serverless create --template aws-nodejs --path faunabase-auth
Enter fullscreen mode Exit fullscreen mode
or the shorthand version:

sls create -t aws-nodejs -p faunabase-auth
Enter fullscreen mode Exit fullscreen mode

Open the faunabase-auth folder with your text editor and you should see the results below:

faunabase-auth folder in code editor

The command we run created two files: serverless.yml and handler.js. The serverless.yml file serves as the control panel to manage all our AWS infrastructure resources. Let's go through the default configuration and make a few customizations.

default serverless.yml file

We will change the provider runtime from nodejs12.x to the current LTS version, which is nodejs14.x. We add a region and stage to the provider and set the default stage to dev and the default region to us-east-1 for this demo. Set this region to the location of your end-users. You should get the following results:

serverless.yml use node14.X
Let's clear all the comments to tidy up the serverless.yml file. Next, we set up our authentication API and integrate it with the business logic encapsulated in Lambda functions.

Setting up Firebase project

Before we proceed to create our API, let's create a Firebase project and Fauna database. Open the Firebase console and click the Create a project button.


Firebase console

Enter faunabase for the project name and click continue.

create firebase project

Click Continue and disable Google Analytics for this project. Finally, click on the Create project button to complete the process.

Create firebase project 2

Click Continue to proceed to the project dashboard.

create firebase project 3

Click on the Settings gear icon button in the sidebar menu and select Project settings.

firebase settings

Copy the Project ID for the project and save it. We need it later for the authentication API.

Image description

Creating a Database in Fauna

Login to your Fauna Dashboard, and click on the Create Database button to create a new database for this demo.


create fauna database

Type faunabase-b2c as the database name, and select the United States option for the region group. Click on the Create button to complete the process.

create fauna database 2

We chose the United States region for data locality and better latency since our authentication API (AWS) is in the United States. Once the database is available, select the Security tab and choose New Key to create your first key. Accept the defaults of the current database and Server for the role. Enter AWS Serverless Framework as the Key Name, and choose Save to create a new key.

create fauna database 3

Copy the generated secret. We are going to store it in the next step.

create fauna database 4

Securing Secret with AWS Parameter Store

The secret we generated in the previous step provides unlimited access to the newly created database. We have to store it safely and provide access to our authentication API logic. Hence, we use Parameter Store to safeguard the secret using SecureString value, which encrypts the key accessed from the authentication API. Click on the Create parameter button.


aws ssm

Parameter Store creates the fauna-secret parameter and displays it on the My parameters tab. We will access this secret from our authentication logic encapsulated in our lambda functions.

aws ssm 2

Configure AWS Credentials for Serverless Framework

The Serverless Framework needs access to our AWS cloud account to create and manage resources on our behalf. Since we are using the serverless command-line interface (CLI) to set up our AWS credentials, we use an AWS profile and create an IAM user for the serverless framework. Ensure that you are logged in to your AWS account to proceed. Click on Users and then Add users. Type faunabase-serverless for the User name and tick the Access key checkbox to enable programmatic access. Click Next: Permissions to go to the permissions page.


aws credential

Click on Attach existing policies directly. Select the AdministratorAccess option in the list, then click Next: Tags.

aws credential 2

Add optional tags in the next step and select Review to complete the process. Check to make sure everything looks good and click Create user. View and copy the Access Key ID & Secret Access Key as we will use them in the next step to configure our serverless CLI.

aws credential 3

It is important to note that the above step grants unrestricted access to resources in the AWS account and makes starting our application easy. The recommended security practice is to provide fine-grained permissions through IAM policies to match the scope of the application. Configure the serverless CLI with the following command using the copied values from the previous step:
serverless config credentials --provider aws --key copied-access-key-id --secret copied-secret-access-key
Enter fullscreen mode Exit fullscreen mode

Next, we proceed to set up API Gateway and integrate it with Lambda functions.

Configuring API Gateway

Our authentication API has two endpoints: signup and login. The client applications will use the Firebase client SDK to request an ID token - a JSON Web Token (JWT). It will send this token to our authentication API, which will verify the integrity of the JWT before calling the business logic at the API endpoint. But first, let us do a little housekeeping in our faunabase-auth folder. Create a folder at the root location and name it src. Inside the src folder, we will create the following folders - functions, libs.


faunabase-auth folder

Delete the handler.js file. Next, let us set up our API in the serverless.yml file and connect it with the functions. In the YAML file under provider, add the apiName property and give it a value of auth-b2c-api. This value is our API Gateway name.

faunabase-auth folder 2

Delete the hello function with its handler property and add the httpApi property under provider. Next, set the cors and metrics properties to true under httpApi. We enabled Cross-Origin Resource Sharing (CORS) and Cloudwatch metrics by setting cors and metrics to true. With CORS, we can specify which clients can access our API and what actions they can take. We also set up monitoring and alerts for our API with Cloudwatch. We enable logging for API Gateway - type logs under provider. Next, set httpApi under logs to true. Let's set up authorization logic in the authentication API. Firebase has an OIDC discovery document that will help us verify users from our firebase project in our authentication API. First, we will need the firebase project ID from earlier when setting up our firebase account. Go to the URL below and replace project-id with your firebase project ID.
https://securetoken.google.com/project-id/.well-known/openid-configuration
Enter fullscreen mode Exit fullscreen mode

In this demo, the project ID from the earlier is faunabase-687bd, so we visit the link below.


faunabase-auth folder 3

Our firebase project can accept and verify ID tokens from the discovery document. In serverless.yml, create authorizers property under httpApi. Under authorizers, we name our authorizer as firebaseB2CAuthorizer. We have to set an identity source, issuer URL, and audience. Under firebaseB2CAuthorizer, set identitySource to $request.header.Authorization, issuerUrl to https://securetoken.google.com/project-id, and audience to project-id. Remember to replace project-id with your Firebase project ID. Change the frameworkVersion value from '2' to '>=2.66.1 <4.0.0' - serverless framework at the time of this writing is transitioning from version 2 to version 3 which is currently in beta. Version 3 comes with breaking changes to the serverless.yml configuration. The current version of the stable serverless node library is 2.66.1. The current code can upgrade to the version 3 package when it becomes stable.

faunabase-auth folder 4

The frontend client must pass a valid Firebase ID token in the authorization header of the request. The identity source tells our authentication API where to find the ID token, a JWT to perform validation checks before providing access to the business logic encapsulated in a function. We have configured our authentication API. Now let's connect it to our two authentication endpoints - signup and login. We create the first endpoint for signing up. Navigate to the src folder --> then the functions folder. Create a signup.js and login.js file inside the functions folder.

faunabase-auth folder 5

Enter the code below in both the signup.js and login.js.
 module.exports.handler = async (event, context) => {


 }
Enter fullscreen mode Exit fullscreen mode

We have set up the boilerplate code for our two endpoints - signup and login. Let's link the business logic to the endpoints in serverless.yml, beginning with the signup endpoint. Enter signup under functions. Next, we write the following code under signup:

functions:
  signup:
    handler: src/functions/signup.handler
    description: create user in fauna
    memorySize: 1048
    timeout: 15
    events:
      - httpApi:
          method: POST
          path: /signup          
          authorizer:
            name: firebaseB2CAuthorizer

Enter fullscreen mode Exit fullscreen mode
  • the handler property sets the source file location for the business logic
  • the description property provides information about the business logic
  • the memorySize property sets the memory size for this business logic in MB
  • the timeout property determines how long the business logic is allowed to run before termination in seconds
  • the events property shows which AWS events can trigger the business logic to execute
  • the httpApi property under events tells us that the API Gateway HTTP API event can run this business logic
  • the method property under httpApi sets which HTTP method can access the API endpoint
  • the path property under httpApi sets the pathname for the API endpoint
  • Under httpApi, the authorizer property with name, its child property allows us to use the firebaseB2CAuthorizer we configured earlier to provide a security layer for the endpoint. Requests without a valid ID token from our Firebase project cannot execute the underlying business logic.

We have set up our signup endpoint for authentication API. Let's repeat the same process for our login endpoint. The final code configuration should look like this:

functions:
  signup:
    handler: src/functions/signup.handler
    description: create user in fauna
    memorySize: 1048
    timeout: 15
    events:
      - httpApi:
          method: POST
          path: /signup          
          authorizer:
            name: firebaseB2CAuthorizer
  login:
    handler: src/functions/login.handler
    description: login user in fauna
    memorySize: 512
    timeout: 10
    events:
      - httpApi:
          method: POST
          path: /login          
          authorizer:
            name: firebaseB2CAuthorizer
Enter fullscreen mode Exit fullscreen mode

We have to make the database secret in Fauna available to the signup and login functions from the AWS Parameter Store. The serverless framework provides access to plugins. We will install the Serverless SSM Fetch plugin to help us retrieve the stored fauna secret in AWS. Run the command below to install the plugin as an npm package:

  serverless plugin install --name serverless-ssm-fetch
Enter fullscreen mode Exit fullscreen mode

Add the installed plugin to the serverless.yml file with the following code at the root:

plugins:
  - serverless-ssm-fetch
Enter fullscreen mode Exit fullscreen mode

Next, add a custom variable called FAUNA_SECRET as a property of custom.serverlessSsmFetch with this code:

custom:
  serverlessSsmFetch:
    FAUNA_SECRET: fauna-secret~true
Enter fullscreen mode Exit fullscreen mode

The serverlessSsmFetch is an accessor provided by the plugin we installed, which decrypts the encrypted fauna-secret parameter with the ~true flag and automatically injects the AWS Parameter Store Fauna server key as an environment variable, accessible in all our functions. Add the logRetentionInDays property under provider and set the number of days to retain the Cloudwatch logs we set up for the authentication API. We proceed to code the business logic for signup and login. The current serverless.yml file should have the following results:

service: faunabase-auth
frameworkVersion: '>=2.66.1 <4.0.0'

provider:
  name: aws
  runtime: nodejs14.x
  apiName: auth-b2c-api
  httpApi:
    cors: true
    metrics: true
    authorizers:
      firebaseB2CAuthorizer:
        identitySource: $request.header.Authorization
        issueUrl: https://securetoken.google.com/faunabase-687bd
        audience: faunabase-687bd
  logs:
    httpApi: true
  logRetentionDays: 14
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}
  lambdaHashingVersion: 20201221

plugins:
  - serverless-ssm-fetch

custom:
  serverlessSsmFetch:
    FAUNA_SECRET: fauna-secret~true

functions:
  signup:
    handler: src/functions/signup.handler
    description: create user in fauna
    memorySize: 1048
    timeout: 15
    events:
      - httpApi:
          method: POST
          path: /signup          
          authorizer:
            name: firebaseB2CAuthorizer
  login:
    handler: src/functions/login.handler
    description: login user in fauna
    memorySize: 512
    timeout: 10
    events:
      - httpApi:
          method: POST
          path: /login          
          authorizer:
            name: firebaseB2CAuthorizer
Enter fullscreen mode Exit fullscreen mode

Business Logic in Lambda Functions

We need to create a collection in our database called User to house each user’s document. We access the database from our client application through FQL or GraphQL in Fauna. FQL can access resources created with the GraphQL API but not the converse. We create a main.gql file which we will upload to bootstrap resources for our database in this demo. Inside the src folder, navigate to the libs folder and create a new folder with fauna. Create a folder named schema within the fauna folder. Create the main.gql file inside the schema folder and type the following code in the schema:

type User {
  role: Role!
  email: String! @unique  
}

enum Role {
  ADMIN
  MEMBER
}

type Mutation {
  logoutUser: Boolean! @resolver(name: "logout_user")
}
Enter fullscreen mode Exit fullscreen mode

In the code above, we started with creating a collection in our database using type User with the role and email attributes. We also set a uniqueness constraint on the email field to validate against duplicates using the @uinque directive. The role attribute will allow us to implement fine-grained access control for the database using User Defined Roles. The logoutUser mutation allows our client application to revoke the access token from the frontend. We will implement the logout logic in the logout_user User Defined Function (UDF).

On the Fauna dashboard, Select the faunabase-b2c database and click the GraphQL tab. Click on the IMPORT SCHEMA button to upload the schema we created.


faunabase-auth folder 6

If successful, you should obtain the following results:

faunabase-auth folder 7

Click on Functions located to the left of your Fauna dashboard. We will update the logic for the logout_user which was created when we uploaded our graphql schema.

faunabase-auth folder 8

Clear the contents of Function Body and replace it with the following code:
Query(
  Lambda(
    [],
    Logout(true)
  )
)
Enter fullscreen mode Exit fullscreen mode

This logout logic revokes all authenticated token access for the current user. The client application can access this functionality from the GraphQL API. Click on the SAVE button to update the logout logic.


faunabase-auth folder 9

Setting up Libraries

Next, let’s install the fauna driver with this command inside our codebase:

  npm install --save faunadb
Enter fullscreen mode Exit fullscreen mode

Create the firebase folder inside the libs folder in addition to the fauna folder. Also, create a util.js file inside the libs folder. The fauna folder will house all code about our Fauna database. The firebase folder will house functionality on Firebase authentication. The util.js file will contain all reusable utility functions which we will use in the login and signup endpoints. Paste the following code inside util.js:

const response = (code, message) => {
  return JSON.stringify({
    statusCode: code,
    body: message
  })
}

module.exports = { response }
Enter fullscreen mode Exit fullscreen mode

The response function accepts an HTTP code and message and returns the response to the client application requesting the authentication API endpoint. The client application will send a request to API Gateway which will validate the JWT in the authorization header and call the AWS Lambda function. Inside the firebase folder, create an index.js which will contain logic to decode the JWT. Run the following code to install the jwt-decode node library:

 npm install jwt-decode
Enter fullscreen mode Exit fullscreen mode

Enter the following code in the index.js:

const decode = require('jwt-decode')

const decodeJWT = (jwt) => {
  return decode(jwt)
}

module.exports = { decodeJWT }
Enter fullscreen mode Exit fullscreen mode

The decodeJWT function accepts a JWT as input and decodes the payload. Inside the fauna folder, create a function.js file and paste the following code:

const {
  Create, Tokens, Ref, Match, Now,
  Collection, Select, Get, Index, TimeAdd
} = require('faunadb').query

const loginUDF = (email) => {
  return Create(
    Tokens(), {
      instance: Ref(
        Collection('User'),
        Select(
          ["ref", "id"],
          Get(
            Match(
              Index('unique_User_email'),
              email
            )
          )
        )
      ),
      ttl: TimeAdd(Now(), 1, 'hour')
    }
  )
}

const registerUDF = (email) => {
  return Create(
    Collection("User"),
    {
      data: {
        email,
        role: "MEMBER"
      }
    }
  )
}

module.exports = { loginUDF, registerUDF }
Enter fullscreen mode Exit fullscreen mode

This file contains the signup and login business logic. The loginUDF exchanges the decoded Firebase JWT token for an access token to the Fauna database which is valid for one hour. The registerUDF creates a new user in the Fauna database with the decoded email input from the Firebase JWT and assigns a basic role of MEMBER. Finally, we export the loginUDF and registerUDF with module.exports. Next, we set up our Fauna client to query the database - create an index.js file and input the following code:

const faunadb = require('faunadb') // import faunadb driver
const { loginUDF, registerUDF } = require('./function') // import UDFs
// create new fauna client
const client = new faunadb.Client({
  secret: process.env.FAUNA_SECRET
})
// get Fauna access token from verified and decoded JWT
const login = (email) => {
  return client.query(loginUDF(email))
}
// create new Fauna user from verified and decoded JWT
const register = (email) => {
  return client.query(registerUDF(email))
}

module.exports = { login, register }
Enter fullscreen mode Exit fullscreen mode

The code above imports the Fauna driver as well as the user-defined functions (UDFs) we created in function.js. A new Fauna client is instantiated to perform the login and register operations in our database. Finally, we export the login and register functions for use in our API endpoints. Next, we finalize the signup and login lambdas in the functions folder.

Signup Lambda

Open the signup.js file inside the functions folder. We import the register function, we just created from the index.js file with the following code:

const { register } = require('../libs/fauna')
Enter fullscreen mode Exit fullscreen mode

We also import Firebase JWT decode function with this code:

const { decodeJWT } = require('../libs/firebase')
Enter fullscreen mode Exit fullscreen mode

Next, we import the response function from the util.js file:

const { response } = require('../libs/util')
Enter fullscreen mode Exit fullscreen mode

The handler function accepts event and context as inputs. The event input contains information such as the JWT token in the authorization header from the API invoker. The context input provides information on the invocation, function, and execution environment. Since AWS Lambda utilizes a pay-as-you-go model, we want to timeout our function immediately after receiving a response from our promise instead of waiting for the event loop to complete. We configure the runtime to send the response immediately with context.callbackWaitsForEmptyEventLoop to false inside the handler function to obtain the following code:

const { register } = require('../libs/fauna')
const { decodeJWT } = require('../libs/firebase')
const { response } = require('../libs/util')

module.exports.handler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false
}
Enter fullscreen mode Exit fullscreen mode

Add the following code after context.callbackWaitsForEmptyEventLoop inside the handler function:

 try {
    const user = event.headers && event.headers.authorization
    ? decodeJWT(event.headers.authorization)
    : { email: null }


    if(!user.email) return response(401, "authorization header not found")
  } catch (error) {
     console.log("signup error", error.message)
  }
Enter fullscreen mode Exit fullscreen mode

In the above code, we decode the JWT located in event.headers.authorization and store it in user if the authorization header exists, else we exit the function with a 401 HTTP response. Next, we implement the logic to create a new user in our Fauna database with the following code:

 const dbResponse = await register(user.email).catch(
  error => {
   console.log("create fauna user error", error.message || error.description)
    return response(500, "Oops! Something went wrong from our end.")
  }
 )
 if (
   dbResponse && dbResponse.data
   && dbResponse.data.email === user.email
 ){
   return response(200, dbResponse.data)
 }
Enter fullscreen mode Exit fullscreen mode

If the register function executes successfully, dbResponse data is sent to the invoker and the function exited immediately. However, if an error occurs during this process, the invoker is notified and the error logged to AWS Cloudwatch Logs. The complete code for signup is shown below:

const { register } = require('../libs/fauna')
const { decodeJWT } = require('../libs/firebase')
const { response } = require('../libs/util')
module.exports.handler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop = false
  try {
    const user = event.headers && event.headers.authorization
      ? decodeJWT(event.headers.authorization)
      : { email: null }
    if(!user.email) return response(401, "authorization header not found")

    const dbResponse = await register(user.email).catch(
     error => {
      console.log("create fauna user error", error.message || error.description)
      return response(500, "Oops! Something went wrong from our end.")
     }
    )
    if (
      dbResponse && dbResponse.data
      && dbResponse.data.email === user.email
    ) {
      return response(200, dbResponse.data)
    }
  } catch (error) {
    console.log("signup trycatch error", error.message)
  }
}
Enter fullscreen mode Exit fullscreen mode

Now, we implement the login lambda function.

Login Lambda

Open the login.js file inside the functions folder. Next, we import the login function from the fauna folder, decodeJWT from the firebase folder, and response function from utils.js all located in the libs folder. Set the context.callbackWaitsForEmptyEventLoop to false inside handler like we did earlier in signup.js.The code should look like this:

const { login } = require('../libs/fauna')
const { decodeJWT } = require('../libs/firebase')
const { response } = require('../libs/util')

module.exports.handler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop =  false
}
Enter fullscreen mode Exit fullscreen mode

Enter the following code after context.callbackWaitsForEmptyEventLoop inside the handler function:

try {
 const user = event.headers && event.headers.authorization
  ? decodeJWT(event.headers.authorization)
  : { email: null }
 if(!user.email) return response(401, "authorization header not found")
} catch (error) {
 console.log("login trycatch error", error.message)
}
Enter fullscreen mode Exit fullscreen mode

Likewise in the signup lambda, we decode the JWT located in the event.headers.authorization and store it in user if the authorization header exists, else we exit the function with a 401 HTTP response. Next, we implement the login logic to create a user access token from the verified and decoded email with the code below inside the trycatch block:

 const dbResponse = await login(user.email).catch(
  error => {
   console.log("create fauna token error", error.message || error.description)
   return response(500, "Oops! Something went wrong from our end.")
  }
 )
 if (
   dbResponse && dbResponse.data
   && dbResponse.data.email === user.email
 ) {
      return response(200, dbResponse.data)
 }
Enter fullscreen mode Exit fullscreen mode

On successful execution of the login function, dbResponse data is sent to the invoker and the function exited immediately. However, if an error occurs during this process, the invoker is notified and the error logged to AWS Cloudwatch Logs. The complete code for login is shown below:

 const { login } = require('../libs/fauna')
 const { decodeJWT } = require('../libs/firebase')
 const { response } = require('../libs/util')

 module.exports.handler = async (event, context) => {
  context.callbackWaitsForEmptyEventLoop =  false
  try {
   const user = event.headers && event.headers.authorization
    ? decodeJWT(event.headers.authorization)
    : { email: null }
   if(!user.email) return response(401, "authorization header not found")
   const dbResponse = await login(user.email).catch(
    error => {
     console.log("create fauna token error", error.message || error.description)
     return response(500, "Oops! Something went wrong from our end.")
    }
   )

   if (
      dbResponse && dbResponse.data
      && dbResponse.data.email === user.email
    ) {
      return response(200, dbResponse.data)
    }

  } catch (error) {
    console.log("login trycatch error", error.message)
  }
}
Enter fullscreen mode Exit fullscreen mode

We have completed the signup and login lambda functions. Next, we look at the process to deploy the code to the AWS Cloud Service.

Deploy API to AWS

We are in the last mile of building our application and deploying it to the AWS cloud. Serverless Framework packages all node libraries and their dependencies which can result in very bloated lambda functions. Lambda functions in general are plagued by cold-starts which is the time taken to prepare the execution environment. This time scales with the code size. Therefore, we can reduce the cold-start time with webpack bundling, importing only the code needed for each function. The overarching benefit is that end-users get a better user experience with reduced latencies. The serverless-webpack plugin provides an option to achieve this optimization.

Webpack Bundle Setup

Install the serverless-webpack plugin, and webpack, as [devDependencies](https://docs.npmjs.com/specifying-dependencies-and-devdependencies-in-a-package-json-file) with this code:

 npm install --save-dev serverless-webpack webpack
Enter fullscreen mode Exit fullscreen mode

Go to the serverless.yml file at the root of the faunabase-auth folder and add the serverless-webpack plugin under plugins after serverless-ssm-fetch.
Next, we add the webpack configuration file to serverless.yml. Under the custom: property add the code below:

webpack:
    webpackConfig: 'webpack.config.js'
Enter fullscreen mode Exit fullscreen mode

The serverless.yml file should be similar to the code below:


faunabase-auth folder 10

We exclude any GraphQL files (.gql) from the bundle with the excludeFiles property under webpack: in our src folder.
webpack:
    webpackConfig: 'webpack.config.js'
    excludeFiles: src/**/*.gql
Enter fullscreen mode Exit fullscreen mode

After the plugins property at the root of the serverless.yml file add the code below to tell webpack to package our functions individually:

package:
  individually: true
Enter fullscreen mode Exit fullscreen mode

The final serverless.yml file should have the following code:

service: faunabase-auth
frameworkVersion: '>=2.66.1 <4.0.0'

provider:
  name: aws
  runtime: nodejs14.x
  apiName: auth-b2c-api
  httpApi:
    cors: true
    metrics: true
    authorizers:
      firebaseB2CAuthorizer:
        identitySource: $request.header.Authorization
        issueUrl: https://securetoken.google.com/faunabase-687bd
        audience: faunabase-687bd
  logs:
    httpApi: true
  logRetentionDays: 14
  stage: ${opt:stage, 'dev'}
  region: ${opt:region, 'us-east-1'}
  lambdaHashingVersion: 20201221

plugins:
  - serverless-ssm-fetch
  - serverless-webpack

package:
  individually: true

custom:
  serverlessSsmFetch:
    FAUNA_SECRET: fauna-secret~true
  webpack:
    webpackConfig: 'webpack.config.js'
    excludeFiles: src/**/*.gql

functions:
  signup:
    handler: src/functions/signup.handler
    description: create user in fauna
    memorySize: 1048
    timeout: 15
    events:
      - httpApi:
          method: POST
          path: /signup          
          authorizer:
            name: firebaseB2CAuthorizer
  login:
    handler: src/functions/login.handler
    description: login user in fauna
    memorySize: 512
    timeout: 10
    events:
      - httpApi:
          method: POST
          path: /login          
          authorizer:
            name: firebaseB2CAuthorizer




Enter fullscreen mode Exit fullscreen mode

Create a webpack.config.js file in the root directory and import the serverless-webpack library:

const slsw = require('serverless-webpack')
Enter fullscreen mode Exit fullscreen mode

The file we created will house all our code bundling configurations for deployment. Export an empty object with module.exports:

const slsw = require('serverless-webpack')

module.exports = {


}
Enter fullscreen mode Exit fullscreen mode

Inside the empty object, we set the target property to node. At build time, the plugin can automatically determine the correct entry point for each lambda function. We set the entry to use slsw.lib.entries inside the exported object. Finally, we set mode to run in development mode if we are running locally else production mode with:

 slsw.lib.webpack.isLocal ? 'development' : 'production'
Enter fullscreen mode Exit fullscreen mode

The final webpack configuration file should be similar to this result:

const slsw = require('serverless-webpack')
const nodeExternals = require('webpack-node-externals')

module.exports = {
  target: 'node',
  entry: slsw.lib.entries,
  mode: slsw.lib.webpack.isLocal ? 'development' : 'production'
}
Enter fullscreen mode Exit fullscreen mode

Serverless Deploy to AWS Cloud

We run the serverless deploy command to deploy all artifacts to the AWS cloud with the default stage and region:

 serverless deploy
Enter fullscreen mode Exit fullscreen mode

Summary

We have been able to build a serverless authentication API for a business-to-consumer application. The frontend or client application can access the signup endpoint to create a new user and the login endpoint to exchange a Firebase JWT for an access token to the Fauna database to facilitate direct communication. It is important to secure the access token with a user-defined role (UDR). We will do that in a future article in this series. In the next article, we explore building a serverless authentication API for a multi-tenant business-to-business application.

Discussion (0)