Published ON
December 16, 2024
Updated on
December 16, 2024
CREATED ON
December 16, 2024

[103] Securing Your Terraform Serverless API with Cognito

Introduction

In the third part of our series, we’ll secure our serverless API using AWS Cognito. So, what exactly is AWS Cognito?

Amazon Cognito is a user authentication and authorization service provided by AWS. It offers:

  • Simple integration for sign-up, sign-in, and access control in web and mobile apps.
  • Secure user account management, supporting:
    • Social login providers like Google and Facebook.
    • Traditional email/password authentication.
    • Enterprise identity providers.

This makes Cognito a powerful tool for securing your applications without needing to build a custom authentication system from scratch.

In this part, we will:

  • Add Cognito user pool for authentication.
  • Secure existing endpoints via API Gateway Authorizer.
  • Introduce two new endpoints for user sign-up and sign-in.
  • Configure IAM policies for Lambda functions to interact with Cognito.

Let’s begin with the architectural overview and then proceed step by step.

P.S. If you haven’t read the previous articles in this series, we recommend checking them out:

Enhancing the Architecture

Below is an updated high-level architecture diagram that illustrates the integration of Cognito with API Gateway and Lambda.

Figure 1: Incorporating AWS Cognito for secured serverless API.

The source code for this part is available in the GitHub repository in the 103 directory. However, we strongly recommend following the tutorial to understand the concepts and steps, starting from the codebase created in the second [102] part.

Setting Up Cognito

We’ll begin by creating an AWS Cognito User Pool to manage user authentication.

Terraform Configuration for Cognito

Create a new cognito module in your terraform/modules directory.

Define the following files:

  1. main.tf: This file will set up the Cognito user pool and associated resources.
# terraform/modules/cognito/main.tf

# This Terraform configuration defines the following AWS Cognito resources:
# 1. Cognito User Pool (aws_cognito_user_pool): Creates a user pool with specified settings including account recovery, verification message template, auto-verified attributes, and password policy.
# 2. Cognito User Pool Domain (aws_cognito_user_pool_domain): Creates a domain for the user pool.
# 3. Cognito User Pool Client (aws_cognito_user_pool_client): Creates a user pool client with specified authentication flows and token validity settings.

resource "aws_cognito_user_pool" "user_pool" {
  name = var.cognito_user_pool_name
  account_recovery_setting {
    recovery_mechanism {
      name     = "verified_email"
      priority = 1
    }
  }

  verification_message_template {
    default_email_option = "CONFIRM_WITH_LINK"
  }

  auto_verified_attributes = ["email"]
  password_policy {
    minimum_length    = 8
    require_uppercase = true
    require_lowercase = true
    require_numbers   = true
    require_symbols   = true
  }
}

resource "aws_cognito_user_pool_domain" "user_pool_domain" {
  domain       = var.cognito_user_pool_domain
  user_pool_id = aws_cognito_user_pool.user_pool.id
}

resource "aws_cognito_user_pool_client" "user_pool_client" {
  name         = var.cognito_app_client_name
  user_pool_id = aws_cognito_user_pool.user_pool.id

  explicit_auth_flows = [
    "ALLOW_USER_PASSWORD_AUTH",
    "ALLOW_REFRESH_TOKEN_AUTH"
  ]
  generate_secret = false

  access_token_validity  = 60
  id_token_validity      = 60
  refresh_token_validity = 7

  token_validity_units {
    access_token  = "minutes"
    id_token      = "minutes"
    refresh_token = "days"
  }
}


  1. outputs.tf: Exports the Cognito user pool ID and client ID. These values will be used across modules to integrate the Cognito authorizer with the API Gateway and to provide them as environment variables for Lambda functions.
# terraform/modules/cognito/outputs.tf

# This Terraform configuration defines the following outputs of the cognito module:
# 1. user_pool_id: The ID of the created Cognito User Pool.
# 2. user_pool_client_id: The ID of the created Cognito User Pool Client.

output "user_pool_id" {
  value = aws_cognito_user_pool.user_pool.id
}

output "user_pool_client_id" {
  value = aws_cognito_user_pool_client.user_pool_client.id
}


  1. variables.tf: Define required input variables.
# terraform/modules/cognito/variables.tf

# This Terraform configuration defines the following input variables of the cognito module:
# 1. aws_region: The AWS region to deploy resources.
# 2. cognito_user_pool_name: The name of the Cognito User Pool.
# 3. cognito_app_client_name: The name of the Cognito App Client.
# 4. cognito_user_pool_domain: The domain name for the Cognito user pool.

variable "aws_region" {
  description = "The AWS region to deploy resources"
  type        = string
}

variable "cognito_user_pool_name" {
  description = "The name of the Cognito User Pool"
  type        = string
}

variable "cognito_app_client_name" {
  description = "The name of the Cognito App Client"
  type        = string
}


variable "cognito_user_pool_domain" {
  description = "The domain name for the Cognito user pool"
  type        = string
} 

Integrating Cognito Authorizer with API Gateway

We need to update the API Gateway configuration to use the Cognito authorizer for securing the endpoints. The authorizer will validate incoming requests using the JWT token provided in the Authorization header.

  1. Update the API Gateway module in the Terraform configuration to include the Cognito authorizer.
# terraform/modules/apigateway/main.tf

resource "aws_apigatewayv2_authorizer" "cognito_authorizer" {
  api_id           = aws_apigatewayv2_api.api_gateway.id
  authorizer_type  = "JWT"
  identity_sources = ["$request.header.Authorization"]
  name             = "cognito_authorizer"

  jwt_configuration {
    audience = [var.user_pool_client_id]
    issuer   = "https://cognito-idp.${var.aws_region}.amazonaws.com/${var.user_pool_id}"
  }
}


  1. Update the API Gateway module to export the Cognito authorizer ID.
# terraform/modules/apigateway/outputs.tf

output "cognito_authorizer_id" {
  value = aws_apigatewayv2_authorizer.cognito_authorizer.id
}


  1. Update apigateway module variables to include the Cognito user pool id and user pool client ID.
# terraform/modules/apigateway/variables.tf

variable "user_pool_id" {
  type = string
}

variable "user_pool_client_id" {
  type = string
}


We’ll update the existing Lambda functions to validate incoming requests using the JWT token provided in the Authorization header. The API Gateway will be configured to use the Cognito authorizer for securing the endpoints.

Modify:

  • 102/terraform/modules/lambda-api/lambda-handler-create-epic-failure.tf
  • 102/terraform/modules/lambda-api/lambda-handler-delete-epic-failure.tf
  • 102/terraform/modules/lambda-api/lambda-handler-get-all-failures.tf

Add the following snippet at the end of each aws_apigatewayv2_route block:

 authorization_type = "JWT"
 authorizer_id      = var.cognito_authorizer_id


Update Lambda API variables

We need to add the following variables to the Lambda API module. First two will be used in the Cognito Service we are going to impement, and the last one will is referenced in the API Gateway configuration.

variable "user_pool_id" {
  type = string
}

variable "user_pool_client_id" {
  type = string
}

variable "cognito_authorizer_id" {
  type = string
}


Extend lambda api shared environment variables

In the terraform/lambda-api.tf file, add the following to the shared environment variables:

locals {
  shared_env_vars = {
    USER_POOL_ID        = var.user_pool_id
    COGNITO_CLIENT_ID   = var.user_pool_client_id
    DYNAMODB_TABLE_NAME = var.dynamo_table_name
  }
}


IAM Policies for Cognito Access

We will create new Lambda execution roles to include permissions for interacting with Cognito in sign-up and sign-in handlers. Let's extend terraform/modules/lambda-api/main.tf with the following resources:

# IAM Role for Lambda execution that needs more permissions (sign up and sign in)
resource "aws_iam_role" "lambda_cognito_admin_role" {
  name = "lambda_cognito_admin_role"

  assume_role_policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = "sts:AssumeRole"
        Effect = "Allow"
        Principal = {
          Service = "lambda.amazonaws.com"
        }
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_cognito_admin_role_policy" {
  role       = aws_iam_role.lambda_cognito_admin_role.name
  policy_arn = "arn:aws:iam::aws:policy/service-role/AWSLambdaBasicExecutionRole"
}

resource "aws_iam_role_policy" "lambda_cognito_admin_role_policy" {
  name = "lambda_cognito_admin_role_policy"
  role = aws_iam_role.lambda_cognito_admin_role.id

  policy = jsonencode({
    Version = "2012-10-17"
    Statement = [
      {
        Action = [
          "cognito-idp:SignUp",
          "cognito-idp:AdminGetUser",
          "cognito-idp:AdminInitiateAuth"
        ]
        Effect = "Allow"
        Resource = [
          "*"
        ]
      }
    ]
  })
}

resource "aws_iam_role_policy_attachment" "lambda_cognito_admin_dynamodb_policy_attachment" {
  role       = aws_iam_role.lambda_cognito_admin_role.name
  policy_arn = aws_iam_policy.lambda_dynamodb_policy.arn
} 

New methods created in node.js application will require those permissions to interact with Cognito.

Updating root main.tf and variables.tf files

The final modification for Terraform is updating the root main.tf file to include the Cognito module and to provide existing modules with additional shared variables.

# terraform/main.tf

# This root Terraform configuration defines the following AWS resources and modules:
# 1. AWS Provider (provider "aws"): Configures the AWS provider with the specified region and profile.
# 2. API Gateway Module (module "apigateway"): Deploys an API Gateway with the specified settings and integrates with Cognito.
# 3. Lambda API Module (module "lambda-api"): Deploys Lambda functions for API operations, integrates with API Gateway, Cognito, and DynamoDB.
# 4. DynamoDB Module (module "dynamodb"): Creates a DynamoDB table with the specified settings.
# 5. Cognito Module (module "cognito"): Creates a Cognito User Pool, User Pool Client, and User Pool Domain with the specified settings.

provider "aws" {
  region  = "eu-central-1"
  profile = "move2edge-dev"
}

module "apigateway" {
  source                 = "./modules/apigateway"
  aws_region             = var.aws_region
  api_gateway_name       = var.api_gateway_name
  api_gateway_stage_name = var.api_gateway_stage_name
  user_pool_id           = module.cognito.user_pool_id
  user_pool_client_id    = module.cognito.user_pool_client_id
}

module "lambda-api" {
  source                    = "./modules/lambda-api"
  aws_region                = var.aws_region
  dynamo_table_name         = var.dynamo_table_name
  api_gateway_id            = module.apigateway.api_gateway_id
  api_gateway_execution_arn = module.apigateway.api_gateway_execution_arn
  cognito_authorizer_id     = module.apigateway.cognito_authorizer_id
  user_pool_client_id       = module.cognito.user_pool_client_id
  user_pool_id              = module.cognito.user_pool_id
}

module "dynamodb" {
  source            = "./modules/dynamodb"
  aws_region        = var.aws_region
  dynamo_table_name = var.dynamo_table_name
}

module "cognito" {
  source                   = "./modules/cognito"
  aws_region               = var.aws_region
  cognito_user_pool_name   = var.cognito_user_pool_name
  cognito_app_client_name  = var.cognito_app_client_name
  cognito_user_pool_domain = var.cognito_user_pool_domain
}

# terraform/variables.tf

# This Terraform configuration defines the following input variables:
# 1. aws_region: The AWS region to deploy resources.
# 2. api_gateway_name: The name of the API Gateway.
# 3. api_gateway_stage_name: The name of the API Gateway Stage.
# 4. dynamo_table_name: The name of the DynamoDB table.
# 5. cognito_user_pool_name: The name of the Cognito User Pool.
# 6. cognito_app_client_name: The name of the Cognito App Client.
# 7. cognito_user_pool_domain: The domain name for the Cognito user pool.

variable "aws_region" {
  description = "The AWS region to deploy resources"
  default     = "eu-central-1"
}

variable "api_gateway_name" {
  description = "The name of the API Gateway"
  default     = "epic-failures-apigateway"
}

variable "api_gateway_stage_name" {
  description = "The name of the API Gateway Stage"
  default     = "dev"
}

variable "dynamo_table_name" {
  description = "The name of the DynamoDB table"
  default     = "epic-failures"
}

variable "cognito_user_pool_name" {
  description = "The name of the Cognito User Pool"
  default     = "epic-failures-user-pool"
}

variable "cognito_app_client_name" {
  description = "The name of the Cognito App Client"
  default     = "epic-failures-app_client"
}


variable "cognito_user_pool_domain" {
  description = "The domain name for the Cognito user pool"
  type        = string
  default     = "epic-failures"
}


Adding Sign-Up and Sign-In Endpoints

Extending the Node.js Application

To interact with Cognito, install the necessary AWS SDK packages:

yarn add @aws-sdk/client-cognito-identity-provider

Implementing CognitoService

Let's create a new service class that will handle user sign-up and sign-in operations using the AWS SDK.

// epicfailure-api/src/services/ICognitoService.ts

import { ILoginData } from "src/models/LoginData";

// This file defines the ICognitoService interface, which specifies the methods for interacting with AWS Cognito.

export interface ICognitoService {
  signUp(email: string, password: string, name: string): Promise<void>;
  signIn(email: string, password: string): Promise<ILoginData>;
}

// epicfailure-api/src/services/CognitoService.ts

// This file defines the CognitoService class, which implements the ICognitoService interface.
// The class provides methods for interacting with AWS Cognito, including user sign-up and sign-in.
// It uses the AWS SDK for JavaScript to communicate with the Cognito Identity Provider.

import { CognitoIdentityServiceProvider } from 'aws-sdk';
import {
  CognitoIdentityProviderClient,
  InitiateAuthCommand,
  InitiateAuthCommandInput,
  SignUpCommand,
  AdminGetUserCommand,
} from '@aws-sdk/client-cognito-identity-provider';

import { ICognitoService } from './ICognitoService';
import { ILoginData } from 'src/models/LoginData';


class CognitoService implements ICognitoService {
  private client: CognitoIdentityProviderClient;

  private userPoolId: string;
  private clientId: string;

  constructor() {
    this.client = new CognitoIdentityProviderClient({ region: process.env.AWS_REGION });

    this.userPoolId = process.env.USER_POOL_ID!;
    this.clientId = process.env.COGNITO_CLIENT_ID!;

  }

  async signUp(email: string, password: string, name: string): Promise<void> {
    const params: CognitoIdentityServiceProvider.SignUpRequest = {
      ClientId: this.clientId,
      Username: email,
      Password: password,
      UserAttributes: [
        {
          Name: 'email',
          Value: email,
        },
        {
          Name: 'name',
          Value: name,
        },
      ],
    };

    try {
      const command = new SignUpCommand(params);
      const response = await this.client.send(command);
      console.log('Sign up successful:', response);
    } catch (error) {
      console.error('Error signing up:', error);
      throw error;
    }
  }
  async signIn(email: string, password: string): Promise<ILoginData> {
    const params: InitiateAuthCommandInput = {
      AuthFlow: 'USER_PASSWORD_AUTH',
      ClientId: this.clientId,
      AuthParameters: {
        USERNAME: email,
        PASSWORD: password,
      },
    };

    try {
      const command = new InitiateAuthCommand(params);
      const response = await this.client.send(command);
      console.log('Sign in successful:', response);

      // Fetch user attributes
      const userParams = {
        UserPoolId: this.userPoolId,
        Username: email,
      };
      const adminGetUserCommand = new AdminGetUserCommand(userParams);
      const userResponse = await this.client.send(adminGetUserCommand);
      console.log('User attributes fetched:', userResponse);

      return {
        email,
        name: userResponse.UserAttributes?.find((attr) => attr.Name === 'name')?.Value || '',
        idToken: response.AuthenticationResult?.IdToken || '',
      };
    } catch (error) {
      console.error('Error signing in:', error);
      throw error;
    }
  }

}

export default CognitoService;


To handle this code we are missing LoginData model. Let's create it.

// epicfailure-api/src/models/LoginData.ts

// This file defines the LoginData class, which represents the data returned after a successful user login.

export interface ILoginData {
  idToken: string;
  name: string;
  email: string;
}

export default class LoginData implements ILoginData {
  idToken: string;
  name: string;
  email: string;
}

Create the Sign-Up Handler

Define a new handler for user registration. The implementation will:

  • Accept user credentials (email, password) from the request body.
  • Use Cognito’s SignUp API to register the user.

Let's expose the handler as a Lambda function in the Terraform configuration.

# # terraform/modules/lambda-api/lambda-handler-signup.tf

# This Terraform configuration defines the following AWS resources:
# 1. Lambda Function for Sign-Up (aws_lambda_function): Creates a Lambda function for handling user sign-ups.
# 2. Data Source for Sign-Up Lambda Code (data "archive_file"): Archives the sign-up Lambda function code into a zip file.
# 3. API Gateway Integration for Sign-Up Lambda (aws_apigatewayv2_integration): Creates an integration between API Gateway and the sign-up Lambda function.
# 4. API Gateway Route for Sign-Up (aws_apigatewayv2_route): Creates a route in API Gateway for the sign-up endpoint.
# 5. Lambda Permission for API Gateway (aws_lambda_permission): Grants API Gateway permission to invoke the sign-up Lambda function.

resource "aws_lambda_function" "lambda_sign_up" {
  function_name = "${var.lambda_function_name_prefix}-sign-up"
  runtime       = "nodejs18.x"
  handler       = "sign-up-handler.handler"

  role = aws_iam_role.lambda_cognito_admin_role.arn

  filename         = data.archive_file.sign_up_lambda_zip.output_path
  source_code_hash = data.archive_file.sign_up_lambda_zip.output_base64sha256

  environment {
    variables = local.shared_env_vars
  }
}

data "archive_file" "sign_up_lambda_zip" {
  type        = "zip"
  source_file = "${path.module}/../../../epicfailure-api/dist/handlers/sign-up-handler.js"
  output_path = "${path.module}/../../../epicfailure-api/dist/signUp.zip"
}

resource "aws_apigatewayv2_integration" "lambda_sign_up" {
  api_id = var.api_gateway_id

  integration_uri    = aws_lambda_function.lambda_sign_up.invoke_arn
  integration_type   = "AWS_PROXY"
  integration_method = "POST"
}

resource "aws_apigatewayv2_route" "post_sign_up" {
  api_id = var.api_gateway_id

  route_key = "POST /sign-up"
  target    = "integrations/${aws_apigatewayv2_integration.lambda_sign_up.id}"
}

resource "aws_lambda_permission" "api_gw_sign_up" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_sign_up.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${var.api_gateway_execution_arn}/*/*"
} 

Create the Sign-In Handler

The sign-in handler will:

Accept user credentials. Use Cognito’s InitiateAuth API to authenticate and return a JWT token.

// epicfailure-api/src/handlers/sign-in/sign-in-handler.ts

// This file defines the AWS Lambda handler for user sign-in using Cognito.
// It imports necessary services and libraries, validates the incoming request,
// and uses CognitoService to authenticate the user with the provided email and password.

import { APIGatewayProxyEvent, APIGatewayProxyResult } from 'aws-lambda';
import * as Joi from 'joi';
import CognitoService from 'src/services/CognitoService';

const cognitoService = new CognitoService();

const signInSchema = Joi.object({
  email: Joi.string().email().required(),
  password: Joi.string().required(),
});

const signInHandler = async (event: APIGatewayProxyEvent): Promise<APIGatewayProxyResult> => {
  const { email, password } = JSON.parse(event.body || '{}');

  const { error } = signInSchema.validate({ email, password });
  if (error) {
    return {
      statusCode: 400,
      body: JSON.stringify({ message: 'Invalid request body', error: error.details[0].message }),
    };
  }

  try {
    const loginData = await cognitoService.signIn(email, password);
    return {
      statusCode: 200,
      body: JSON.stringify(loginData),
    };
  } catch (error) {
    console.log(error);
    return {
      statusCode: 401,
      body: JSON.stringify({ message: 'Unauthorized' }),
    };
  }
};

exports.handler = signInHandler;


Create analogous Terraform resources for the sign-in handler:

# terraform/modules/lambda-api/lambda-handler-signin.tf

# This Terraform configuration defines the following AWS resources:
# 1. Lambda Function for Sign-In (aws_lambda_function): Creates a Lambda function for handling user sign-ins.
# 2. Data Source for Sign-In Lambda Code (data "archive_file"): Archives the sign-in Lambda function code into a zip file.
# 3. API Gateway Integration for Sign-In Lambda (aws_apigatewayv2_integration): Creates an integration between API Gateway and the sign-in Lambda function.
# 4. API Gateway Route for Sign-In (aws_apigatewayv2_route): Creates a route in API Gateway for the sign-in endpoint.
# 5. Lambda Permission for API Gateway (aws_lambda_permission): Grants API Gateway permission to invoke the sign-in Lambda function.

resource "aws_lambda_function" "lambda_sign_in" {
  function_name = "${var.lambda_function_name_prefix}-sign-in"
  runtime       = "nodejs18.x"
  handler       = "sign-in-handler.handler"

  role = aws_iam_role.lambda_cognito_admin_role.arn

  filename         = data.archive_file.sign_in_lambda_zip.output_path
  source_code_hash = data.archive_file.sign_in_lambda_zip.output_base64sha256

  environment {
    variables = local.shared_env_vars
  }
}

data "archive_file" "sign_in_lambda_zip" {
  type        = "zip"
  source_file = "${path.module}/../../../epicfailure-api/dist/handlers/sign-in-handler.js"
  output_path = "${path.module}/../../../epicfailure-api/dist/signIn.zip"
}

resource "aws_apigatewayv2_integration" "lambda_sign_in" {
  api_id = var.api_gateway_id

  integration_uri    = aws_lambda_function.lambda_sign_in.invoke_arn
  integration_type   = "AWS_PROXY"
  integration_method = "POST"
}

resource "aws_apigatewayv2_route" "post_sign_in" {
  api_id = var.api_gateway_id

  route_key = "POST /sign-in"
  target    = "integrations/${aws_apigatewayv2_integration.lambda_sign_in.id}"
}

resource "aws_lambda_permission" "api_gw_sign_in" {
  statement_id  = "AllowExecutionFromAPIGateway"
  action        = "lambda:InvokeFunction"
  function_name = aws_lambda_function.lambda_sign_in.function_name
  principal     = "apigateway.amazonaws.com"
  source_arn    = "${var.api_gateway_execution_arn}/*/*"
}


Deploying and Testing

Once the configurations are in place, build and deploy the updated code and infrastructure. You can use single yarn script that combines bundling handlers' code script and the terraform apply command.

cd epicfailure-api && yarn build-and-deploy

In the terminal export the INVOKE_URL from the build and deploy script output.

export INVOKE_URL="https://<api-id>.execute-api.<region>.amazonaws.com/<stage>"

Create an epic failure records

Use the curl POST request to create epic failure record the same way we did in the previous 102 part.

curl -X POST \
  ${INVOKE_URL}/epic-failures \
  -H "Content-Type: application/json" \
  -d '{
    "failureID": "001",
    "taskAttempted": "deploying a feature on Friday at 4:59 p.m.",
    "whyItFailed": "triggered a cascade of unexpected errors",
    "lessonsLearned": ["never deploy on a Friday afternoon"]
  }'

Figure 2: Curl response on unauthorized access.

Since we have secured the API with Cognito, the request will be unauthorized. Let’s sign up and sign in to get the JWT token.

Sign up

Export the email address for the sign-up request.

export EMAIL="your-email@example.com"

curl -X POST \
  ${INVOKE_URL}/sign-up \
  -H "Content-Type: application/json" \
  -d '{
    "email": "'"${EMAIL}"'",   
    "password": "Password123!",
    "name": "Test User"
  }'

Figure 3: Sign up succeeded.

Now you need to open your inbox and accept the invitation to sign up. This is a standard Cognito flow to verify the email address. This step is crucial to be able to sign in.

Figure 4: Email verification message.
Figure 5: Registration confirmation.

Now let's open AWS Cognito console and check if the user has been created.

Figure 6: AWS Cognito Console.

Sign in and export idToken

OK, now we have an user created in Cognito. Let's sign in and export the idToken. It will be used in following requests.

export ID_TOKEN=$(curl -s -X POST \
  ${INVOKE_URL}/sign-in \
  -H "Content-Type: application/json" \
  -d '{
    "email": "'"${EMAIL}"'",
    "password": "Password123!" 
  }' | jq -r '.idToken')        

             
If your email address has not been verified, Cognito will not return an idToken. In this case, the ID_TOKEN variable will be set to null. To resolve this, verify your email address and sign in again.

Create Epic Failure once again

curl -X POST \
  ${INVOKE_URL}/epic-failures \
  -H "Content-Type: application/json" \
  -H "Authorization: Bearer ${ID_TOKEN}" \
  -d '{
    "failureID": "001",
    "taskAttempted": "deploying a feature on Friday at 4:59 p.m.",
    "whyItFailed": "triggered a cascade of unexpected errors",
    "lessonsLearned": ["never deploy on a Friday afternoon"]
  }'


Figure 7: Epic failure created successfully.

Well done! You have successfully secured your API with Cognito and implemented user sign-up and sign-in endpoints. The JWT token ensures that unauthorized users cannot gain access.

Wrapping Up

At this stage, your API is secure, with endpoints protected by Cognito authentication. Users can register and log in, and JWT tokens ensure secure communication. All Terraform code is organized to allow straightforward updates, making this project ready for future enhancements.

The final source code for this part is available in the GitHub repository in the 103 directory. Be sure to follow along for the full implementation details and deploy it in your environment.

In the next, last part 104 we will optimize the deployment by introducing Lambda Layers. Stay tuned!

Newsletter

Enjoying our content?
Sign up to our newsletter for more.
Insightful content on cloud, 5G, media streaming, and IoT delivered to you every someday.
Thank you! Your submission has been received!
Oops! Something went wrong while submitting the form.