<?xml version="1.0" encoding="UTF-8"?>
<rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom" xmlns:dc="http://purl.org/dc/elements/1.1/">
  <channel>
    <title>DEV Community: Geovane Oliveira</title>
    <description>The latest articles on DEV Community by Geovane Oliveira (@geovane_oliveira).</description>
    <link>https://dev.to/geovane_oliveira</link>
    <image>
      <url>https://media2.dev.to/dynamic/image/width=90,height=90,fit=cover,gravity=auto,format=auto/https:%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Fuser%2Fprofile_image%2F3664142%2Fc04c0578-8cd5-4fb0-bb23-051242fcc2a7.png</url>
      <title>DEV Community: Geovane Oliveira</title>
      <link>https://dev.to/geovane_oliveira</link>
    </image>
    <atom:link rel="self" type="application/rss+xml" href="https://dev.to/feed/geovane_oliveira"/>
    <language>en</language>
    <item>
      <title>Protecting an EC2 hosted web application with AWS WAF in practice</title>
      <dc:creator>Geovane Oliveira</dc:creator>
      <pubDate>Thu, 08 Jan 2026 00:29:40 +0000</pubDate>
      <link>https://dev.to/geovane_oliveira/protecting-an-ec2-hosted-web-application-with-aws-waf-in-practice-3mb</link>
      <guid>https://dev.to/geovane_oliveira/protecting-an-ec2-hosted-web-application-with-aws-waf-in-practice-3mb</guid>
      <description>&lt;p&gt;Web applications on EC2 are everywhere. And honestly, a lot of them are just sitting there exposed to the internet with nothing more than a Security Group between them and every bot, scanner, and attacker out there.&lt;/p&gt;

&lt;p&gt;This article walks through building a straightforward architecture to answer one specific question: How do you protect a web application on EC2 from common web attacks without touching the application code?&lt;/p&gt;

&lt;p&gt;We'll use AWS WAF for Layer 7 protection, Security Groups for network control, and AWS Certificate Manager for encrypted transport, all at minimal cost and complexity.&lt;/p&gt;

&lt;h2&gt;
  
  
  Understanding network layers before adding security controls
&lt;/h2&gt;

&lt;p&gt;Before jumping into AWS services, let's get clear on how network communication actually works. The OSI model helps here, but we'll keep it practical.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 3 – Network layer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Deals with IP addresses. This is about knowing where traffic is coming from and where it's headed.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 4 – Transport layer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
Deals with ports and protocols. This controls how connections get established, think TCP on port 80 or 443.&lt;/p&gt;

&lt;p&gt;Together, these two layers answer questions like: Who's sending this traffic? Which port are they using? Should this connection even be allowed?&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Layer 7 – Application layer&lt;/strong&gt;&lt;br&gt;&lt;br&gt;
This is where HTTP and HTTPS live. It understands URLs, headers, query strings, request bodies and the actual content of what's being sent.&lt;/p&gt;

&lt;p&gt;Layer 7 answers a different kind of question: What is the user actually trying to do?&lt;/p&gt;

&lt;p&gt;Here's the thing: most web attacks happen at Layer 7, not at the network level. Understanding this distinction is critical.&lt;/p&gt;

&lt;h2&gt;
  
  
  What a Security Group is and how it works
&lt;/h2&gt;

&lt;p&gt;Security Groups are probably the first security control you run into in AWS. They're virtual firewalls that control network level access to things like EC2 instances and load balancers.&lt;/p&gt;

&lt;p&gt;A Security Group evaluates traffic based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Source IP address&lt;/li&gt;
&lt;li&gt;Destination port
&lt;/li&gt;
&lt;li&gt;Protocol&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From the OSI perspective, Security Groups operate at Layer 3 (Network) and Layer 4 (Transport).&lt;/p&gt;

&lt;p&gt;Key characteristics:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Stateful&lt;/strong&gt; – if traffic is allowed in, the response is automatically allowed out&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Allow only&lt;/strong&gt; – you can't write deny rules, only allow rules&lt;/li&gt;
&lt;li&gt;Everything else is denied by default&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Example rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow inbound TCP traffic on port 80 from anywhere (0.0.0.0/0)&lt;/li&gt;
&lt;li&gt;Allow SSH on port 22 only from your office IP&lt;/li&gt;
&lt;li&gt;Deny everything else (implicit)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Security Groups are essential. They're your first line of defense.&lt;/p&gt;

&lt;h2&gt;
  
  
  What Security Groups cannot protect against
&lt;/h2&gt;

&lt;p&gt;But here's the limitation: Security Groups don't understand HTTP or HTTPS content at all.&lt;/p&gt;

&lt;p&gt;They can't:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Inspect URLs&lt;/li&gt;
&lt;li&gt;Analyze query strings&lt;/li&gt;
&lt;li&gt;Detect SQL injection attempts&lt;/li&gt;
&lt;li&gt;Catch Cross Site Scripting (XSS)&lt;/li&gt;
&lt;li&gt;Identify malicious payloads in request bodies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;From a Security Group's perspective, if port 80 is open and the source IP is allowed, the request gets through no matter what's actually inside that request.&lt;/p&gt;

&lt;p&gt;This is a fundamental limitation when you're trying to protect web applications.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why application layer protection is required
&lt;/h2&gt;

&lt;p&gt;Web attacks are application layer problems. That means you need Layer 7 inspection.&lt;/p&gt;

&lt;p&gt;This is where AWS WAF comes in.&lt;/p&gt;

&lt;p&gt;AWS WAF is a fully managed Web Application Firewall that inspects HTTP and HTTPS requests before they ever reach your application. It looks at:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;URL paths&lt;/li&gt;
&lt;li&gt;Headers&lt;/li&gt;
&lt;li&gt;Query strings&lt;/li&gt;
&lt;li&gt;Request bodies&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Based on rules you define (or use AWS Managed Rules), it can:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow requests&lt;/li&gt;
&lt;li&gt;Block requests&lt;/li&gt;
&lt;li&gt;Count requests for monitoring&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The best part? No agents on your EC2 instances. No changes to your application code.&lt;/p&gt;

&lt;h2&gt;
  
  
  Why an Application Load Balancer is part of the design:
&lt;/h2&gt;

&lt;p&gt;AWS WAF can't attach directly to an EC2 instance. It needs to be associated with one of these:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Application Load Balancer&lt;/li&gt;
&lt;li&gt;API Gateway&lt;/li&gt;
&lt;li&gt;CloudFront&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;For this architecture, the Application Load Balancer serves as:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;The public entry point&lt;/li&gt;
&lt;li&gt;The integration point for AWS WAF&lt;/li&gt;
&lt;li&gt;The boundary between the internet and your compute layer&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Even if you are only running a single EC2 instance, this design mirrors what you would see in production environments.&lt;/p&gt;

&lt;h2&gt;
  
  
  Architecture overview
&lt;/h2&gt;

&lt;p&gt;The architecture below shows the scenario we are going to discuss in this article.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8aslhi8in6l1yt8wgmg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fd8aslhi8in6l1yt8wgmg.png" alt="AWS WAF Architecture Diagram" width="800" height="400"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 1: Conceptual architecture for protecting an EC2-hosted web application using AWS WAF attached to an internet-facing Application Load Balancer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The flow works like this:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Internet traffic hits the Application Load Balancer&lt;/li&gt;
&lt;li&gt;AWS WAF inspects HTTP requests at Layer 7&lt;/li&gt;
&lt;li&gt;Allowed requests get forwarded to the EC2 instance&lt;/li&gt;
&lt;li&gt;Security Groups restrict network level access between components&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;&lt;strong&gt;Architecture components:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Amazon EC2&lt;/strong&gt; – Runs a simple Nginx web application in a public subnet&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Application Load Balancer&lt;/strong&gt; – Exposes the application to the internet and routes traffic to EC2&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS WAF&lt;/strong&gt; – Inspects and filters HTTP requests before they reach the application&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;AWS Certificate Manager&lt;/strong&gt; – Provides free SSL/TLS certificates for HTTPS encryption&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Systems Manager Session Manager&lt;/strong&gt; – Provides secure, SSH free access to EC2 instances via IAM&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Auto Scaling Group&lt;/strong&gt; – Maintains a single EC2 instance (and makes it easy to scale later if needed)&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Groups&lt;/strong&gt; – Enforce strict Layer 3 and Layer 4 access rules&lt;/p&gt;

&lt;p&gt;Each component protects a specific layer. There's no redundancy or overlap.&lt;/p&gt;

&lt;h2&gt;
  
  
  Setting up the infrastructure
&lt;/h2&gt;

&lt;p&gt;Let's walk through the key configuration steps. I'll focus on the security specific settings that matter most.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 1: Creating the Application Load Balancer
&lt;/h3&gt;

&lt;p&gt;First, we need to set up the ALB that will serve as our public entry point.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi218h3bviujo2wl5ntg9.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi218h3bviujo2wl5ntg9.png" alt="ALB Configuration in AWS Console" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 2: Application Load Balancer basic configuration&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Key settings to configure:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Scheme&lt;/strong&gt;: Internet facing&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;IP address type&lt;/strong&gt;: IPv4&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Listeners&lt;/strong&gt;: HTTP (port 80) and HTTPS (port 443)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Availability Zones&lt;/strong&gt;: Select at least two for high availability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The ALB needs to be in a public subnet since it's receiving traffic directly from the internet.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 2: Configuring Security Groups
&lt;/h3&gt;

&lt;p&gt;Security Groups control network level access. We need two distinct groups here.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;ALB Security Group:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxdzu5v4r2vtnoua74qo.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fnxdzu5v4r2vtnoua74qo.png" alt="ALB Security Group Rules" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 3: Security Group rules for the Application Load Balancer&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inbound rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow HTTP (port 80) from 0.0.0.0/0&lt;/li&gt;
&lt;li&gt;Allow HTTPS (port 443) from 0.0.0.0/0&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;EC2 Security Group:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2os2c35r7h3591b4kfg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fu2os2c35r7h3591b4kfg.png" alt="EC2 Security Group Rules" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 4: Security Group rules for the EC2 instance&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;Inbound rules:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Allow HTTP (port 80) &lt;strong&gt;only&lt;/strong&gt; from the ALB Security Group&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note:&lt;/strong&gt; Unlike traditional setups, we're not opening SSH port 22. Access to the instance for management purposes is handled through AWS Systems Manager Session Manager, which uses IAM authentication and doesn't require any inbound ports to be open.&lt;/p&gt;

&lt;p&gt;This is crucial: the EC2 instance never accepts traffic directly from the internet. Only the ALB can reach it on port 80, and administrative access is through SSM Session Manager only.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 3: Creating the AWS WAF Web ACL
&lt;/h3&gt;

&lt;p&gt;Now we add the Layer 7 protection. This is where AWS WAF comes in.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cd3706i4dbh7vmhsiu1.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3cd3706i4dbh7vmhsiu1.png" alt="AWS WAF Web ACL Configuration" width="800" height="667"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 5: Creating a Web ACL in AWS WAF&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;When creating the Web ACL:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Give it a descriptive name (e.g., &lt;code&gt;ec2-app-protection-waf&lt;/code&gt;)&lt;/li&gt;
&lt;li&gt;Select &lt;strong&gt;Application Load Balancer&lt;/strong&gt; as the resource type&lt;/li&gt;
&lt;li&gt;Associate it with the ALB you created earlier&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Step 4: Adding AWS Managed Rules
&lt;/h3&gt;

&lt;p&gt;Instead of writing custom rules from scratch, we'll use AWS Managed Rule Groups. These are maintained by AWS and updated automatically.&lt;/p&gt;

&lt;p&gt;Recommended rule groups to enable:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Core rule set (CRS)&lt;/strong&gt; – Protects against common attacks like OWASP Top 10&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Known bad inputs&lt;/strong&gt; – Blocks requests with patterns associated with exploit attempts&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;SQL database&lt;/strong&gt; – Prevents SQL injection attacks&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Linux operating system&lt;/strong&gt; – Blocks requests targeting common Linux vulnerabilities&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;Each rule group adds a layer of protection. Start with these four since they cover the most common attack vectors.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 5: Associating WAF with the ALB
&lt;/h3&gt;

&lt;p&gt;Once the Web ACL is configured, associate it with your Application Load Balancer.&lt;/p&gt;

&lt;p&gt;The association happens in one of two ways, depending on where you started the configuration. If you created the Web ACL from the WAF console, you selected the ALB during the initial setup under (Associated AWS resources). If you're adding WAF to an existing ALB, you will find the option in the Load Balancer console under the (Integrated services) tab.&lt;/p&gt;

&lt;p&gt;From the Load Balancer perspective, you will see a section labeled (AWS WAF) where you can attach a Web ACL. Click "Edit" and select your newly created Web ACL from the dropdown. The attachment is immediate there is no downtime, no need to restart anything.&lt;/p&gt;

&lt;p&gt;Once associated, you will see the Web ACL name displaied in the ALB's integrated services section. You can verify the reverse connection by going back to the WAF console, selecting your Web ACL, and checking the (Associated AWS resources) tab. Your ALB should be listed there.&lt;/p&gt;

&lt;p&gt;This is the critical connection point. Every HTTP request hitting your ALB will now pass through AWS WAF inspection before reaching your EC2 instance. The WAF evaluates each request against your rule groups in order of priority, and either allows it through, blocks it with a 403 response, or counts it for monitoring purposes depending on how you've configured each rule.&lt;/p&gt;

&lt;p&gt;The inspection happens inline with minimal latency, typically adding only single digit milliseconds to request processing time. If WAF blocks a request, the connection never reaches your application layer. The request is terminated at the ALB with a 403 Forbidden response.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 6: Configuring IAM Role for Systems Manager
&lt;/h3&gt;

&lt;p&gt;To enable secure, SSH free access to the EC2 instance, we attach an IAM role that allows Systems Manager Session Manager to function.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bomh0iaud2rdgyfad95.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F1bomh0iaud2rdgyfad95.png" alt="IAM Role for SSM" width="800" height="805"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 7: IAM role configuration for Systems Manager access&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The IAM role includes the &lt;code&gt;AmazonSSMManagedInstanceCore&lt;/code&gt; managed policy, which provides:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Permission for the SSM agent to communicate with Systems Manager service&lt;/li&gt;
&lt;li&gt;Ability to retrieve commands and send outputs&lt;/li&gt;
&lt;li&gt;CloudWatch Logs integration for session logging (optional but recommended)&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Note on SSM Agent:&lt;/strong&gt; If you are using Amazon Linux 2023 (as in this setup), the SSM agent comes pre installed and enabled by default. For other AMIs like Ubuntu or older Amazon Linux versions, you may need to install the agent manually. The combination of the IAM role and the pre installed agent is what enables Session Manager to work immediately after instance launch.&lt;/p&gt;

&lt;p&gt;Once attached, you can access the instance through the AWS console or CLI without opening SSH ports or managing key pairs. All sessions are authenticated through IAM and can be logged for audit purposes.&lt;/p&gt;

&lt;h3&gt;
  
  
  Step 7: Monitoring and testing
&lt;/h3&gt;

&lt;p&gt;After everything is configured, you can monitor WAF activity through CloudWatch metrics.&lt;/p&gt;

&lt;p&gt;AWS WAF provides real time metrics that show how your Web ACL is performing. These metrics are automatically published to CloudWatch and typically appear within 5 to 10 minutes of activity.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key metrics to watch:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;AllowedRequests:&lt;/strong&gt; Legitimate traffic getting through&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;BlockedRequests:&lt;/strong&gt; Malicious requests stopped by WAF&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CountedRequests:&lt;/strong&gt; Requests matching rules in count mode&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;To view these metrics:&lt;/p&gt;

&lt;ol&gt;
&lt;li&gt;Navigate to the AWS WAF console&lt;/li&gt;
&lt;li&gt;Select your Web ACL&lt;/li&gt;
&lt;li&gt;Go to the Overview tab&lt;/li&gt;
&lt;li&gt;CloudWatch metrics will display showing request patterns over time&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;You can also view detailed logs in CloudWatch Logs if you enable WAF logging.&lt;/p&gt;

&lt;h2&gt;
  
  
  Transport security with HTTPS
&lt;/h2&gt;

&lt;p&gt;All traffic between clients and the Application Load Balancer is encrypted using TLS 1.2 or higher. The SSL certificate is provided by AWS Certificate Manager at no cost and renews automaticaly.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8aym6syxy6abohjspf7.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fe8aym6syxy6abohjspf7.png" alt="HTTPS Certificate Configuration" width="800" height="409"&gt;&lt;/a&gt;&lt;br&gt;
&lt;em&gt;Figure 10: SSL/TLS certificate from AWS Certificate Manager&lt;/em&gt;&lt;/p&gt;

&lt;p&gt;The ALB is configured to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Accept HTTPS connections on port 443 with a valid SSL certificate&lt;/li&gt;
&lt;li&gt;Redirect all HTTP traffic to HTTPS (301 permanent redirect)&lt;/li&gt;
&lt;li&gt;Use modern cipher suites (TLS 1.2+ only)&lt;/li&gt;
&lt;li&gt;Serve a certificate trusted by all major browsers&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This ensures that sensitive data cannot be intercepted or read by attackers performing man-in-the-middle attacks. The certificate is issued and managed by AWS Certificate Manager, which handles automatic renewal before expiration.&lt;/p&gt;




&lt;p&gt;&lt;strong&gt;Video walkthrough&lt;/strong&gt;: For a complete hands on demonstration of this setup, check out the video tutorial below where I walk through each step in the AWS console.&lt;/p&gt;

&lt;p&gt;[Vídeo soon]&lt;/p&gt;




&lt;h2&gt;
  
  
  What AWS WAF protects against in practice
&lt;/h2&gt;

&lt;p&gt;Using AWS Managed Rules, AWS WAF can block common attack patterns like:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;SQL injection attempts&lt;/li&gt;
&lt;li&gt;Cross-Site Scripting (XSS)&lt;/li&gt;
&lt;li&gt;Path traversal attacks&lt;/li&gt;
&lt;li&gt;Malformed or suspicious HTTP requests&lt;/li&gt;
&lt;li&gt;Basic automated scanners and bots&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Important point:&lt;/strong&gt; AWS WAF doesn't fix insecure code. What it does is reduce your exposure by blocking known malicious patterns before they ever reach your application.&lt;/p&gt;

&lt;h2&gt;
  
  
  What this architecture does NOT cover (and why it matters)
&lt;/h2&gt;

&lt;p&gt;This architecture provides solid foundational protection with network segmentation, application layer filtering, encrypted transport, and IAM based instance access. However, it's important to understand its limitations. This is a strong starting point that demonstrates core security principles, &lt;u&gt;not a complete enterprise grade solution&lt;/u&gt;.&lt;/p&gt;

&lt;h3&gt;
  
  
  What's missing for a production environment:
&lt;/h3&gt;

&lt;p&gt;&lt;strong&gt;Network Isolation:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;EC2 in public subnet (production should use private subnet, see "Ideal Architecture" below)&lt;/li&gt;
&lt;li&gt;EC2 has public IP (though no direct access is possible due to Security Groups)&lt;/li&gt;
&lt;li&gt;No VPC Endpoints for fully isolated SSM access&lt;/li&gt;
&lt;li&gt;No NAT Gateway for controlled outbound traffic from private instances&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced DDoS Protection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No AWS Shield Advanced for sophisticated volumetric attacks&lt;/li&gt;
&lt;li&gt;No rate based rules in WAF to limit request velocity per IP&lt;/li&gt;
&lt;li&gt;No geographic restrictions (geo blocking) for region specific threats&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Monitoring and Threat Detection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;WAF logging disabled (to minimize costs for this demo)&lt;/li&gt;
&lt;li&gt;No AWS GuardDuty for threat intelligence and anomaly detection&lt;/li&gt;
&lt;li&gt;No Security Hub for centralized security findings across services&lt;/li&gt;
&lt;li&gt;No VPC Flow Logs for network traffic analysis&lt;/li&gt;
&lt;li&gt;No CloudWatch Alarms for proactive alerting on suspicious activity&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Identity and Secrets Management:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No AWS Secrets Manager for database credentials or API keys&lt;/li&gt;
&lt;li&gt;Session Manager logging not enabled (optional enhancement)&lt;/li&gt;
&lt;li&gt;No MFA requirement for Session Manager access&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Compliance and Governance:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No AWS Config for continuous compliance checking&lt;/li&gt;
&lt;li&gt;No automated patch management with Systems Manager Patch Manager&lt;/li&gt;
&lt;li&gt;No backup strategy with AWS Backup&lt;/li&gt;
&lt;li&gt;No vulnerability scanning with AWS Inspector&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Advanced Application Protection:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No AWS WAF Bot Control for sophisticated bot mitigation&lt;/li&gt;
&lt;li&gt;No reCAPTCHA challenges for suspicious activity&lt;/li&gt;
&lt;li&gt;No account takeover prevention features&lt;/li&gt;
&lt;li&gt;No fraud detection patterns&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Content Delivery:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;No CloudFront for global edge caching and additional DDoS protection&lt;/li&gt;
&lt;li&gt;No origin shielding to reduce load on the ALB&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Recommendation:&lt;/strong&gt; For production, place CloudFront in front of the ALB to:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Hide the ALB's DNS name from public access&lt;/li&gt;
&lt;li&gt;Add an additional WAF layer at the edge&lt;/li&gt;
&lt;li&gt;Improve global performance with caching&lt;/li&gt;
&lt;li&gt;Protect against DDoS attacks with AWS Shield&lt;/li&gt;
&lt;/ul&gt;

&lt;h3&gt;
  
  
  Ideal architecture for production:
&lt;/h3&gt;

&lt;p&gt;For production workloads, the recommended architecture would place the EC2 instance in a private subnet with no public IP address and use VPC Interface Endpoints for Systems Manager connectivity. Here's what that would look like:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Enhanced Network Architecture:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvk8mf41se5gaon2jwjg.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fyvk8mf41se5gaon2jwjg.png" alt=" " width="800" height="601"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key differences:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;EC2 in private subnet&lt;/strong&gt; - No public IP, no direct internet access&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;VPC Interface Endpoints&lt;/strong&gt; - Enable SSM access without internet gateway (~$21.60/month for 3 endpoints)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;NAT Gateway&lt;/strong&gt; - If EC2 needs outbound internet access (~$32.40/month + data transfer)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;CloudFront&lt;/strong&gt; - Global distribution and edge protection&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Why not include this in the current architecture?&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;VPC Interface Endpoints cost approximately &lt;strong&gt;$0.01/hour per endpoint&lt;/strong&gt; (~$7.20/month each). For Systems Manager to work in a private subnet, you need 3 endpoints:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;code&gt;com.amazonaws.region.ssm&lt;/code&gt; &lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.region.ec2messages&lt;/code&gt;&lt;/li&gt;
&lt;li&gt;&lt;code&gt;com.amazonaws.region.ssmmessages&lt;/code&gt;&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;strong&gt;Total cost: ~$21.60/month&lt;/strong&gt; just for the VPC Endpoints, plus additional complexity in setup and troubleshooting. For an educational article focused on WAF and application security fundamentals, this additional cost would make the architecture less accessible for learning purposes.&lt;/p&gt;

&lt;p&gt;The current implementation keeps the EC2 in a public subnet but eliminates SSH exposure entirely through Systems Manager Session Manager. While the instance has a public IP, the Security Group ensures no direct access is possible, only the ALB can reach it on port 80. This achieves approximately 90% of the security benefit of a private subnet at 0% of the additional cost, making it ideal for learning, development, and testing environments.&lt;/p&gt;

&lt;h3&gt;
  
  
  Why this matters for your security journey:
&lt;/h3&gt;

&lt;p&gt;This architecture demonstrates Layer 3, 4, and 7 protection fundamentals with encrypted transport, IAM based access control, and zero SSH exposure. It's an excellent educational starting point and works well for:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Development and testing environments&lt;/li&gt;
&lt;li&gt;Learning AWS security concepts and best practices&lt;/li&gt;
&lt;li&gt;Understanding the defense in depth model&lt;/li&gt;
&lt;li&gt;Proof of concepts and demos&lt;/li&gt;
&lt;li&gt;Small internal tools with limited exposure&lt;/li&gt;
&lt;li&gt;Personal projects and portfolios&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;However, for &lt;strong&gt;&lt;u&gt;production workloads handling real user data, PII, or serving public traffic at scale&lt;/u&gt;&lt;/strong&gt;, you need the additional layers mentioned above. Security is not a single solution, it's a layered approach where each control addresses specific attack vectors and threat models.&lt;/p&gt;

&lt;h3&gt;
  
  
  Recommended next steps:
&lt;/h3&gt;

&lt;ol&gt;
&lt;li&gt;
&lt;strong&gt;Enable Session Manager logging&lt;/strong&gt; to CloudWatch for audit trails (minimal cost)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Add rate-based rules&lt;/strong&gt; to WAF for basic request rate limiting&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable CloudWatch logging&lt;/strong&gt; for WAF and create alarms for blocked requests&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Implement GuardDuty&lt;/strong&gt; for threat detection ($4-$10/month for light usage)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Use Security Hub&lt;/strong&gt; for centralized security findings (additional cost)&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Evaluate private subnet migration&lt;/strong&gt; with VPC Endpoints when budget allows&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Consider CloudFront&lt;/strong&gt; for global distribution and additional edge protection&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Enable automated patching&lt;/strong&gt; with Systems Manager Patch Manager&lt;/li&gt;
&lt;/ol&gt;

&lt;p&gt;Each of these topics deserves its own deep dive, which I plan to cover in future articles as part of a comprehensive AWS security series.&lt;/p&gt;

&lt;p&gt;AWS WAF pricing is based on:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Number of Web ACLs&lt;/li&gt;
&lt;li&gt;Number of rules&lt;/li&gt;
&lt;li&gt;Number of requests inspected&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;A few things to know for this setup:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AWS WAF is &lt;strong&gt;not&lt;/strong&gt; included in the AWS Free Tier&lt;/li&gt;
&lt;li&gt;For low traffic environments, costs are typically minimal&lt;/li&gt;
&lt;li&gt;Costs stay predictable as long as you clean up resources after testing&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If you're just experimenting, expect single digit dollar amounts per month for WAF itself, assuming moderate traffic.&lt;/p&gt;

&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Security Groups and AWS WAF aren't competing solutions. They work at different layers and solve different problems.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Security Groups&lt;/strong&gt; control who can connect.&lt;br&gt;&lt;br&gt;
&lt;strong&gt;AWS WAF&lt;/strong&gt; controls what those connections are trying to do.&lt;/p&gt;

&lt;p&gt;Understanding which control applies at which layer is what makes it possible to build secure, scalable architectures, even for relatively simple workloads.&lt;/p&gt;

&lt;p&gt;Protecting a web application doesn't require complex tooling. It requires putting the right controls at the right layers.&lt;/p&gt;

&lt;p&gt;If you're running web applications on EC2 without WAF, this architecture is a solid starting point.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on this content
&lt;/h2&gt;

&lt;p&gt;This article documents my learning journey with AWS security architecture. I'm sharing what I've learned and built, not claiming to be an expert. If you spot something that could be improved, or if you have a better approach, I genuinely want to hear about it.&lt;/p&gt;

&lt;p&gt;Security architecture is complex, and there are often multiple valid ways to solve the same problem. The setup I've described here prioritizes learning and understanding core concepts over production grade completeness. Real production environments would require additional layers like GuardDuty, Security Hub, comprehensive logging, and a more robust network isolation strategy.&lt;/p&gt;

&lt;p&gt;If you have feedback, corrections, or suggestions, please reach out that's how we all get better.&lt;/p&gt;

&lt;p&gt;Thanks for reading.🙂&lt;/p&gt;

</description>
      <category>architecture</category>
      <category>aws</category>
      <category>devops</category>
      <category>security</category>
    </item>
    <item>
      <title>Securing Serverless APIs with Amazon Cognito and API Gateway JWT Authorizers</title>
      <dc:creator>Geovane Oliveira</dc:creator>
      <pubDate>Wed, 24 Dec 2025 13:41:36 +0000</pubDate>
      <link>https://dev.to/geovane_oliveira/securing-serverless-apis-with-amazon-cognito-and-api-gateway-jwt-authorizers-5819</link>
      <guid>https://dev.to/geovane_oliveira/securing-serverless-apis-with-amazon-cognito-and-api-gateway-jwt-authorizers-5819</guid>
      <description>&lt;h1&gt;
  
  
  Introduction
&lt;/h1&gt;

&lt;p&gt;Securing APIs is one of those topics that looks simple at first, but quickly becomes complex in real world scenarios.&lt;br&gt;
When you move to a serverless architecture, this challenge becomes even more critical.&lt;/p&gt;

&lt;p&gt;Many implementations end up pushing authentication logic into Lambda functions, creating custom token validation, extra code paths, and unnecessary operational overhead.&lt;/p&gt;

&lt;p&gt;In this article, I will walk through a practical and production ready approach to secure an AWS API Gateway endpoint using Amazon Cognito and the native JWT Authorizer, without Lambda authorizers or custom authentication logic.&lt;/p&gt;

&lt;p&gt;This setup is cost efficient, scalable, and relies entirely on managed AWS services.&lt;/p&gt;

&lt;p&gt;All the reference code and supporting artifacts used in this article are available in the following GitHub repository:&lt;br&gt;
&lt;a href="https://github.com/Geovane-Oliveira/aws-secure-serverless-api-cognito-jwt" rel="noopener noreferrer"&gt;aws-secure-serverless-api-cognito-jwt&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Architecture Overview
&lt;/h2&gt;

&lt;p&gt;The architecture used in this experiment is the following:&lt;/p&gt;

&lt;p&gt;Amazon Cognito User Pool for authentication&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway HTTP API&lt;/li&gt;
&lt;li&gt;Native JWT Authorizer&lt;/li&gt;
&lt;li&gt;AWS Lambda as the backend&lt;/li&gt;
&lt;li&gt;CloudWatch Logs for observability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;The key idea is to let Amazon Cognito handle authentication, while API Gateway is responsible for validating JWTs before the request ever reaches Lambda.&lt;/p&gt;

&lt;p&gt;This means invalid or expired tokens never invoke your backend code.&lt;/p&gt;

&lt;p&gt;The full request flow is illustrated in the diagram below:&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bfxw9r62921zpj5w370.png" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F4bfxw9r62921zpj5w370.png" alt=" " width="800" height="421"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 1: Creating the Cognito User Pool and App Client
&lt;/h2&gt;

&lt;p&gt;We start by creating a Cognito User Pool and an App Client.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Important configuration points:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Enable USER_PASSWORD_AUTH&lt;/li&gt;
&lt;li&gt;Disable client secret (required for public clients and Postman testing)&lt;/li&gt;
&lt;li&gt;Use email as a sign-in alias&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpfe9lzuxzawaarcp4ab.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fvpfe9lzuxzawaarcp4ab.jpeg" alt="Cognito User Pool used as the authentication provider" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This App Client will be responsible for issuing Access Tokens and ID Tokens.&lt;/p&gt;

&lt;p&gt;This corresponds to step 1 in the architecture, where the user authenticates directly with Amazon Cognito.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;User confirmation requirement:&lt;/strong&gt;&lt;/p&gt;

&lt;p&gt;When users are created manually or via the AWS Console, they may initially be in the &lt;code&gt;FORCE_CHANGE_PASSWORD&lt;/code&gt; or &lt;code&gt;UNCONFIRMED state&lt;/code&gt;.&lt;/p&gt;

&lt;p&gt;For this experiment, the user must be fully confirmed before authentication succeeds.&lt;/p&gt;

&lt;p&gt;To ensure this, the user account was confirmed using AWS CloudShell and the AWS CLI, setting a permanent password and moving the user to the CONFIRMED state.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5w90virvs75kthdgph9u.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F5w90virvs75kthdgph9u.jpeg" alt="CloudShell confirming the Cognito user status" width="800" height="426"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;This step is important because Cognito will reject authentication attempts for users that are not fully confirmed.&lt;/p&gt;

&lt;p&gt;Once the user is confirmed, authentication via Postman works as expected.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bmcmq80doyfg1c20drr.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F2bmcmq80doyfg1c20drr.jpeg" alt="App Client settings" width="800" height="189"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3qfknmuf7fs8vp9soq8.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fp3qfknmuf7fs8vp9soq8.jpeg" alt="App Client settings B" width="800" height="475"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 2: Authenticating with Cognito
&lt;/h2&gt;

&lt;p&gt;Authentication is performed using the InitiateAuth API.&lt;/p&gt;

&lt;p&gt;Using Postman, we send a request to the Cognito endpoint with:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AuthFlow: USER_PASSWORD_AUTH&lt;/li&gt;
&lt;li&gt;ClientId&lt;/li&gt;
&lt;li&gt;Username and password&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;If authentication succeeds, Cognito returns:&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;AccessToken&lt;/li&gt;
&lt;li&gt;IdToken&lt;/li&gt;
&lt;li&gt;RefreshToken&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;At this point, we already have everything needed to call a protected API.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38ubvarju4pecxasdq78.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F38ubvarju4pecxasdq78.jpg" alt="PRINT 4 – Postman InitiateAuth request" width="800" height="469"&gt;&lt;/a&gt;&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k4tksg9u56feupb0mth.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F3k4tksg9u56feupb0mth.jpg" alt="PRINT 5 – Successful token response" width="800" height="471"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 3: Configuring the API Gateway JWT Authorizer
&lt;/h2&gt;

&lt;p&gt;Instead of using a Lambda Authorizer, we configure a native JWT Authorizer directly in API Gateway.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Key settings:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;
&lt;strong&gt;Issuer:&lt;/strong&gt; Cognito User Pool issuer URL&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Audience:&lt;/strong&gt; Cognito App Client ID&lt;/li&gt;
&lt;li&gt;
&lt;strong&gt;Identity source:&lt;/strong&gt; Authorization header&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;This tells API Gateway exactly how to validate incoming JWTs automatically.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrdneq2b9gkw5ksvowew.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Flrdneq2b9gkw5ksvowew.jpeg" alt="PRINT 6 – JWT Authorizer configuration" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 4: Protecting the API Route
&lt;/h2&gt;

&lt;p&gt;Next, we attach the JWT Authorizer to the /secure route.&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Once attached:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;Requests without a token return 401 Unauthorized&lt;/li&gt;
&lt;li&gt;Requests with invalid or expired tokens are rejected&lt;/li&gt;
&lt;li&gt;Only valid tokens are allowed through&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;All of this is enforced by API Gateway itself, before Lambda is invoked.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjggkq50g3supgyqg84c5.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fjggkq50g3supgyqg84c5.jpeg" alt="PRINT 7 – Route authorization attached" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;


&lt;h2&gt;
  
  
  Step 5: Lambda Backend and Token Claims
&lt;/h2&gt;

&lt;p&gt;At this stage, authentication and token validation are already completed.&lt;/p&gt;

&lt;p&gt;The Lambda function does not validate JWTs, does not decode tokens, and does not contain authentication logic.&lt;/p&gt;

&lt;p&gt;API Gateway injects the decoded JWT claims directly into the request context, under:&lt;/p&gt;

&lt;p&gt;&lt;code&gt;event.requestContext.authorizer.jwt.claims&lt;/code&gt;&lt;/p&gt;

&lt;p&gt;Below is the complete Lambda function used as the secure backend:&lt;br&gt;
&lt;/p&gt;

&lt;div class="highlight js-code-highlight"&gt;
&lt;pre class="highlight plaintext"&gt;&lt;code&gt;import json

def lambda_handler(event, context):
    print("Lambda secure-api-backend INVOCADA")
    print("RequestId:", context.aws_request_id)

    claims = event["requestContext"]["authorizer"]["jwt"]["claims"]

    print("JWT claims recebidas:")
    print(json.dumps({
        "sub": claims.get("sub"),
        "email": claims.get("email"),
        "username": claims.get("cognito:username"),
        "issuer": claims.get("iss"),
        "token_use": claims.get("token_use"),
        "scope": claims.get("scope")
    }))

    response = {
        "message": "Authorized request",
        "user": {
            "sub": claims.get("sub"),
            "email": claims.get("email"),
            "username": claims.get("cognito:username"),
            "issuer": claims.get("iss"),
            "token_use": claims.get("token_use"),
            "scope": claims.get("scope")
        }
    }

    return {
        "statusCode": 200,
        "headers": {
            "Content-Type": "application/json"
        },
        "body": json.dumps(response)
    }

&lt;/code&gt;&lt;/pre&gt;

&lt;/div&gt;



&lt;p&gt;&lt;strong&gt;This keeps the backend extremely simple and focused:&lt;/strong&gt;&lt;/p&gt;

&lt;ul&gt;
&lt;li&gt;API Gateway handles security&lt;/li&gt;
&lt;li&gt;Lambda consumes already validated identity data&lt;/li&gt;
&lt;li&gt;Logs provide full traceability&lt;/li&gt;
&lt;/ul&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w2eyw4ql2njryh2k7xt.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2F6w2eyw4ql2njryh2k7xt.jpeg" alt="PRINT 8 – Lambda code" width="800" height="621"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Step 6: Validation and Results
&lt;/h2&gt;

&lt;p&gt;We tested three different scenarios:&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;No token&lt;/strong&gt;&lt;br&gt;
401 Unauthorized&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoq1h52wuuungfv456kr.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fqoq1h52wuuungfv456kr.jpg" alt="PRINT 10 – Postman unauthorized response" width="800" height="472"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Invalid token&lt;/strong&gt;&lt;br&gt;
401 Unauthorized&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpyljkme2mmlmyip7vt8.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Ftpyljkme2mmlmyip7vt8.jpg" alt="Invalid token" width="800" height="401"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;&lt;strong&gt;Valid Access Token&lt;/strong&gt;&lt;br&gt;
200 OK and Lambda executed&lt;br&gt;
&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2qrwq3e9wogrfpfqk4k.jpg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fi2qrwq3e9wogrfpfqk4k.jpg" alt="PRINT 11 – Postman authorized response + Lambda output" width="800" height="518"&gt;&lt;/a&gt;&lt;/p&gt;

&lt;p&gt;CloudWatch Logs confirm that the Lambda function is only invoked when the token is valid, proving that the security boundary is enforced at the API Gateway level.&lt;/p&gt;

&lt;p&gt;&lt;a href="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk205l085kkryqvbxja8v.jpeg" class="article-body-image-wrapper"&gt;&lt;img src="https://media2.dev.to/dynamic/image/width=800%2Cheight=%2Cfit=scale-down%2Cgravity=auto%2Cformat=auto/https%3A%2F%2Fdev-to-uploads.s3.amazonaws.com%2Fuploads%2Farticles%2Fk205l085kkryqvbxja8v.jpeg" alt="PRINT 9 – CloudWatch Logs showing JWT claims" width="800" height="409"&gt;&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Reference Implementation
&lt;/h2&gt;

&lt;p&gt;The complete reference implementation, including the Lambda function, Postman collection, and architecture diagram, is available on GitHub:&lt;br&gt;
&lt;a href="https://github.com/Geovane-Oliveira/aws-secure-serverless-api-cognito-jwt" rel="noopener noreferrer"&gt;aws-secure-serverless-api-cognito-jwt&lt;/a&gt;&lt;/p&gt;




&lt;h2&gt;
  
  
  Conclusion
&lt;/h2&gt;

&lt;p&gt;Using Amazon Cognito together with API Gateway JWT Authorizers is one of the cleanest and most efficient ways to secure serverless APIs on AWS.&lt;/p&gt;

&lt;p&gt;There is no custom authentication code, no Lambda Authorizers, and no additional infrastructure to manage.&lt;/p&gt;

&lt;p&gt;API Gateway becomes the security gate, and Lambda focuses only on business logic.&lt;/p&gt;

&lt;p&gt;This is a pattern I recommend for modern, scalable, and secure serverless applications.&lt;/p&gt;




&lt;h2&gt;
  
  
  A note on this content
&lt;/h2&gt;

&lt;p&gt;This article documents my learning journey with AWS security architecture. I'm sharing what I've learned and built, not claiming to be an expert. If you spot something that could be improved, or if you have a better approach, I genuinely want to hear about it.&lt;/p&gt;

&lt;p&gt;If you have feedback, corrections, or suggestions, please reach out that's how we all get better.&lt;/p&gt;

&lt;p&gt;Thanks for reading.🙂&lt;/p&gt;

</description>
      <category>aws</category>
      <category>security</category>
      <category>serverless</category>
      <category>awscommunitybuilder</category>
    </item>
  </channel>
</rss>
