AWS Builder Center
Building a Secure, Zero-Trust Serverless Banking App on AWS

Building a Secure, Zero-Trust Serverless Banking App on AWS

Here is complete code snippet of Simple Banking app built using AWS services & Terraform

Curious content creator
Hey Builders,
I hope you are doing very well and exploring all latest AWS service features.
Have you ever wondered how modern financial applications secure your data while remaining incredibly fast and scalable? The secret often lies in combining the power of serverless architecture with a strict "Zero-Trust" security model.
In this post, I will walk you through how I built LCUBank—a high-fidelity, production-ready serverless banking platform. We'll explore how to use AWS to build a highly secure, completely serverless backend, deploy it using Terraform (Infrastructure as Code), and connect it to a beautiful modern frontend.
Whether you are a cloud beginner looking to understand serverless concepts or an intermediate builder wanting to dive into Custom Lambda Authorizers, this guide is for you!

🎯 What Are We Building?

We are building a decoupled application featuring:
  1. A Secure Serverless API that handles user authentication and transaction processing.
  2. A Premium "Glassmorphism" Frontend that looks like a modern, sleek banking dashboard.

The Architecture

At a high level, every time a user makes a request from the frontend, it passes through several security checkpoints before ever touching our database.
ArchDiagram
ArchitectureDiagram
Our tech stack includes:
  • Amazon Cognito: For user registration, login, and token generation.
  • Amazon API Gateway: To act as the front door for our backend API.
  • AWS Lambda (Custom Authorizer): To validate JSON Web Tokens (JWTs) securely.
  • AWS Lambda (Business Logic): To fetch the user's banking profile and transactions.
  • Amazon DynamoDB: For fast, scalable, NoSQL storage using a Single-Table design.
  • HashiCorp Terraform: To deploy the entire AWS infrastructure automatically.

🔒 The Core Concept: Zero-Trust Security

In traditional architectures, once a user is inside the network, they are often trusted. In a Zero-Trust model, we assume the network is already compromised. We verify every single request, regardless of where it comes from.
In LCUBank, we achieve this by explicitly decoupling Authentication (who you are) from Authorization (what you are allowed to do).

1. Identity with Amazon Cognito

Instead of building a custom login system (which is prone to security flaws), we offload user identity to Amazon Cognito. When a user signs up on the LCUBankfrontend, Cognito handles the password hashing, email verification, and multi-factor authentication (MFA) policies. Upon a successful login, Cognito returns three tokens: an ID Token, an Access Token, and a Refresh Token.

2. The Gatekeeper: API Gateway & Custom Lambda Authorizer

When the frontend wants to fetch the user's bank balance, it sends an HTTP request to Amazon API Gateway with the Access Token in the Authorization header.
Here is where the magic happens. Before API Gateway routes the request to our business logic, it triggers a Custom Lambda Authorizer.
This Python-based Lambda function does the heavy lifting of security:
  1. It downloads the public keys (JWKS) from Cognito.
  2. It mathematically verifies the signature of the JWT to ensure it wasn't tampered with.
  3. It checks the token's expiration date.
  4. It verifies the token's audience (App Client ID).
If—and only if—all checks pass, the Authorizer generates an IAM Policy explicitly allowing the request to proceed to the business logic.

🛠️ Step-by-Step Implementation

Let's break down how this is implemented using Terraform.

Step 1: Defining the Database (DynamoDB)

We use a Single-Table Design pattern for DynamoDB. This means all our entities (Users, Accounts, Transactions) live in the same table, differentiated by a Partition Key (PK) and Sort Key (SK). This makes queries incredibly fast and cheap.
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
resource "aws_dynamodb_table" "bank_table" {
name = "LCUBank-db"
billing_mode = "PAY_PER_REQUEST"
hash_key = "PK"
range_key = "SK"

attribute {
name = "PK"
type = "S"
}
attribute {
name = "SK"
type = "S"
}
}

Step 2: Setting up Identity (Cognito)

We define an AWS Cognito User Pool with strict password policies and email verification enabled. We also create a User Pool Client for our frontend to interact with.
1
2
3
4
5
6
7
8
9
10
11
12
13
resource "aws_cognito_user_pool" "pool" {
name = "LCUBank-users"

password_policy {
minimum_length = 8
require_lowercase = true
require_numbers = true
require_symbols = true
require_uppercase = true
}

auto_verified_attributes = ["email"]
}

Step 3: Writing the Custom Authorizer (Python)

Instead of relying solely on API Gateway's default Cognito authorizer, we wrote a custom Lambda Authorizer in Python using the python-jose library. This gives us granular control. For example, we can inject custom user claims directly into the API Gateway context, which are then passed downstream to our business logic.
If the token is valid, the Python script returns a policy like this:
1
2
3
4
5
6
7
8
9
10
11
12
13
{
"principalId": "user-sub-id",
"policyDocument": {
"Version": "2012-10-17",
"Statement": [
{
"Action": "execute-api:Invoke",
"Effect": "Allow",
"Resource": "arn:aws:execute-api:region:account-id:api-id/stage/GET/account"
}
]
}
}

Step 4: Business Logic (Lambda)

Once authorized, API Gateway forwards the request to our Query Lambda function. Because the Authorizer already validated the user, this Lambda function only needs to focus on fetching the data.
It safely extracts the user's unique ID from the authenticated context (not from the request body, which could be spoofed!) and queries DynamoDB to return their account balance and recent transactions.

Step 5: Tying it together with API Gateway

API Gateway acts as the glue. In our Terraform code, we map the /account path to our Business Logic Lambda, but we attach the Custom Authorizer to that specific route. We also enable CORS (Cross-Origin Resource Sharing) so our frontend application can successfully make requests to the API.

🎨 The Frontend: Glassmorphism Meets Security

A secure backend is only half the battle; users expect a premium experience.
For the frontend, I avoided heavy frameworks and stuck to Vanilla JavaScript and CSS. By utilizing CSS variables, backdrop filters (backdrop-filter: blur()), and semi-transparent RGBA backgrounds, I created a stunning Glassmorphism UI that looks like frosted glass floating over a dynamic, colorful background.
The frontend is completely decoupled. It uses the AWS SDK (via CDN) to handle Cognito sign-ups and logins natively. Once logged in, it stores the JWT securely and attaches it to the HTTP headers of all native fetch() calls made to our API Gateway.

🚀 Key Takeaways from this Project

Building LCUBank taught me several critical lessons about cloud architecture:
  1. Infrastructure as Code is Non-Negotiable: By defining everything in Terraform, I can tear down and rebuild this entire banking infrastructure in under 2 minutes. No manual clicking in the AWS Console.
  2. Never Trust the Client: By extracting the User ID directly from the validated JWT context inside API Gateway, we eliminate the risk of a malicious user modifying the request body to query someone else's bank account.
  3. Serverless is Cost-Effective: Because we are using API Gateway, Lambda, and DynamoDB (On-Demand), this entire architecture costs $0.00 when no one is using it. It falls entirely under the AWS Free Tier.

🏁 Conclusion

Security doesn't have to be a bottleneck. By leveraging AWS managed services like Cognito and API Gateway, we can build enterprise-grade, zero-trust security layers with just a few dozen lines of code.
If you want to try building this yourself, I highly recommend starting with Terraform and a simple Lambda function, and gradually adding the Cognito Authorizer once your base API is working!
Hope this article given you good understanding of building simple applications using AWS services & Terraform.
Feel free to connect with me on LinkedIn  and check out complete Github code here .
Happy Building! ☁️🚀
Cheers,
Logeswaran
Any opinions in this article are those of the individual author and may not reflect the opinions of AWS.
Enjoyed reading this content? Let the author know!

Your likes, comments, shares, and saves help creators reach more builders.

Loading recommendations

Loading article