DEV Community

Cover image for Create a simple OTP system with AWS Serverless

Create a simple OTP system with AWS Serverless

(This blog post first appeared in pubudu.dev)

Intro

This post describes how to implement a simple One Time Password (OTP) system with AWS Serverless services which can be used as a part of two-step verification.

Below tools and technologies used to build this application.

Architecture

Simple OTP Architecture

How it works

  1. In this scenario, I used a login form, which is developed with VueJS and hosted using Amplify static web hosting.

  2. User will enter their email and password and once the credentials are validated, an API endpoint is called to execute "Generate OTP" Lambda function which generates a 6 digit code along with a session id.

  3. Once the code and session id is generated, "Generate OTP" Lambda will save these data into a DynamoDB table.

  4. Then only the session id will be returned as the response of the API endpoint.

  5. DynamoDB streams are enabled in the table. So, once the data are saved, it will trigger the "Send Email" Lambda function.

  6. Within the "Send Email" Lambda function, it will call the Simple Email Service (SES) to send out an email with the generated code to the email address provided.

  7. Meanwhile on the frontend side, once the session id is received from the API, 2nd form is presented to enter the code, which is emailed to the given address.

  8. Once the user enters the code and submits it, it will validate the code along with the session id using another API gateway endpoint that proxy to "Verify OTP" Lambda function.

  9. In "Verify OTP" Lambda function, it queries the DynamoDB table with the given session-id and code and returns the success or error responses.

Key points/Lessons learned

  1. Here I enabled DynamoDB TTL to delete the entries after a specific time to prevent fill out the table very quickly. However, DynamoDB will not delete your record immediately when the TTL is expired. It is deleted eventually and AWS only guarantees it to be deleted within 48 hours. Because of this, when verifying the OTP code, it has to consider the same "expiredAt" field which was used to set TTL.

  2. When design the DynamoDB table, I used sessionId + OTP code as a primary key to easily query the required record. So, when verifying the code, I query by primary key with this combination in the DynamoDB table.

  3. When using DynamoDB steam as a trigger for Lambda, it gets triggers for all the DynamoDB events (ex: insert, delete, update). So, within the Lambda function, had to filter out only the INSERT events using "eventName" of the record.

  4. To send out emails, I have used AWS's own Simple Email Service (SES). However, you need to first verify your sending email address to send emails to any address. This can be done with a support request.

  5. Here, I used Amplify static web hosting to host the frontend of the application. I have used Amplify features - the auto deployments when Github repository modified and custom domain name set up only with few button clicks.

  6. I have used AWS SAM to deploy the backend resources. The expiry time of the OTP and no of digits in the OTP code can be configured at the deployment time.

How to set up

Prerequisites

  • AWS CLI
  • AWS SAM CLI
  • Set up and verified SES send email address

Backend

  1. Clone the repository: https://github.com/pubudusj/simple-otp

  2. Run

sam init && sam deploy -g
Enter fullscreen mode Exit fullscreen mode

After providing your stack information and AWS environment parameters, this will create the backend stack. Copy the ApiBaseUrl output value.

Frontend

  1. Copy the .env.example into .env file and add the ApiBaseUrl value as VUE_APP_API_BASE_URL.

  2. You may zip the whole frontend directory and use that in Amplify web hosting, or authorize your GitHub repository to automatically deploy the application when a git push is made.

  3. If you need to run the frontend in local, navigate to frontend directory and run npm run serve

To Delete the stack

To remove the backend, run sam delete.

Demo

Demo version of this application is available at :
https://simple-otp.pubudu.dev/

Feedback

Your valuable feedback on this project is mostly welcome! I would like you to play around with this and if you have any questions or general comments, please reach out to me via Personal Blog, LinkedIn, Twitter or Github.

Keep building, keep sharing!

Discussion (11)

Collapse
karanpratapsingh profile image
Karan Pratap Singh

Great post! what tool did you use for the architecture diagram, Lucidcharts?

Collapse
pubudusj profile image
Pubudu Jayawardana Author

Thanks @karanpratapsingh for the feedback.
I use draw.io for these diagrams.

Collapse
karanpratapsingh profile image
Karan Pratap Singh

Thanks

Collapse
aviboy2006 profile image
Avinash Dalvi

Very explained post. Thanks for this. 👍🏻👍🏻

Collapse
pubudusj profile image
Pubudu Jayawardana Author

Thanks for the feedback @aviboy2006

Collapse
3much profile image
3much

Great and useful post. 💯✌

Collapse
pubudusj profile image
Pubudu Jayawardana Author

Thanks @3much for the feedback!

Collapse
aaronbrighton profile image
Aaron Brighton

FYI, the Github repo link is 404, may need to make it public?

Collapse
pubudusj profile image
Pubudu Jayawardana Author

Just made it public. Thanks for pointing this out @aaronbrighton .

Collapse
eliasibgerardo profile image
Gerardo Eliasib

Excellent post!
I wonder if this implementation limits the number of OTP's generated by email to avoid unnecessary consumption?

Collapse
pubudusj profile image
Pubudu Jayawardana Author

Thanks for the feedback @eliasibgerardo
In this implementation there is no limitation enforced. However, there are several ways to protect the unnecessary consumptions.
Since this OTP functionality meant to be consumed by already authenticated users, that will reduce the unnecessary usage since we can track the users who are actually using the system.
Also, in the infrastructure level, we can use Web Application Firewall (WAF) rules with throttling to protect the API end points per IP for example. docs.aws.amazon.com/waf/latest/dev...
Further, in the code level, we can implement our own rate limits per email address using the email address and expiryAt field values.