logo
▼
Projects
Collaborations
Resources
Our Partners
Our Community
Projects
Collaborations
Resources
Our Partners
Our Community
Account
Sign InJoin UsHelp & Support

The Cometbid
Technology Foundation

Empowering innovation through open-source collaboration. TCTF supports developers, organizations, and communities worldwide in building the future of technology with transparent, vendor-neutral governance and world-class open-source projects.


Follow Us

Our Community

  • About Us
  • Upcoming Events
  • Projects
  • Collaborations
  • Membership
  • TCTF Training
  • Corporate Sponsorship

Learn

  • FAQ
  • TCTF Incubator Programs
  • Brand Guidelines
  • Logo Specifications

Legal

  • Privacy Policy
  • Terms of Use
  • Compliance
  • Code of Conduct
  • Contribution Guidelines
  • Legal & Trademark
  • Manage Cookies

More

  • Report a Vulnerability
  • Report Bugs
  • Mailing Lists
  • Contact Us
  • Support
  • Support Tickets
  • TCTF Social Network

Subscribe to our Newsletter

Cognito Middleware: Building an Authentication Pipeline for Serverless APIs
Framework Deep DivesFramework Series #2

Cognito Middleware: Building an Authentication Pipeline for Serverless APIs

How we wrapped AWS Cognito into a reusable middleware layer — token extraction, JWT validation, permission checks with AND/OR logic, user context enrichment, and circuit breaker protection for Cognito API calls.

May 20, 2026· 12 min read
TCTF Editorials
TCTF Newsletter
Home›Newsletter›Cognito Middleware: Building an Authentication ...

In This Edition

  • Why Middleware Matters for Auth
  • The Five-Step Pipeline
  • Permission Checks: AND, OR, and Hierarchies
  • User Context: What the Handler Receives
  • Circuit Breaker for Cognito API Calls
  • How Services Use It
  • Lessons Learned
5Pipeline Steps
AND / ORPermission Logic
Auto-enrichedUser Context
Cognito APICircuit Breaker
Role + GroupRBAC
34Services Using

Every authenticated API endpoint at TCTF follows the same pattern: extract the JWT from the Authorization header, validate it against AWS Cognito, check the user's permissions, enrich the request with user context, and then — only then — run the business logic. Without a shared middleware layer, every Lambda function would implement this pattern independently. 34 services, hundreds of Lambda functions, each with its own token parsing, its own validation logic, its own permission checks. One inconsistency and you have a security hole. This article covers the Cognito middleware we built in tctf-utils — a composable authentication pipeline that handles the entire auth flow in a single, reusable layer.

01Why Middleware Matters for Auth

Authentication is the most security-critical code in any platform. A bug in a profile page shows wrong data. A bug in authentication gives unauthorized access to every feature behind it.

In a monolithic application, authentication middleware runs once — in the framework's middleware pipeline. Express has app.use(authMiddleware). Django has MIDDLEWARE settings. The auth logic is written once and applied to every route.

In serverless, there is no shared middleware pipeline. Each Lambda function is an independent entry point. Each function must authenticate the request independently. If you copy-paste the auth logic into every function, you have 200 copies of security-critical code. When you find a bug, you fix it in 200 places. When you miss one, you have a vulnerability.

The Cognito middleware in tctf-utils solves this by providing a composable pipeline that any Lambda function can use. Import it, configure it, and the auth flow is handled. The function's code starts after authentication — with a fully validated, permission-checked, context-enriched request.

🔐

Authentication is security-critical code. In serverless, every Lambda function is an independent entry point. The middleware ensures every function authenticates consistently — one implementation, 34 services.

02The Five-Step Pipeline

The authentication pipeline has five steps, each building on the previous one.

Step 1: Token Extraction. The middleware reads the Authorization header from the API Gateway event. It expects a Bearer token — the string after Bearer is the JWT. If the header is missing or malformed, the pipeline stops and returns a 401 with a MissingTokenError.

Step 2: JWT Validation. The extracted token is validated against AWS Cognito. The middleware checks the signature (is this token from our Cognito User Pool?), the expiration (has the token expired?), the issuer (does the iss claim match our User Pool URL?), and the audience (does the aud claim match our app client ID?). If any check fails, the pipeline returns a 401 with a TokenValidationError.

Step 3: Permission Check. The middleware extracts the user's groups from the JWT claims (cognito:groups) and checks them against the required permissions for the endpoint. Permissions can use AND logic (user must have all listed permissions) or OR logic (user must have any listed permission). If the check fails, the pipeline returns a 403 with an AuthorizationError.

Step 4: User Context Enrichment. The middleware constructs a UserContext object from the JWT claims: userId (from the sub claim), email, groups, userGroup (primary group), subRole, and permissions. This object is passed to the Lambda handler so it does not need to parse the JWT itself.

Step 5: Handler Execution. The Lambda handler runs with the authenticated, authorized, context-enriched request. It can access the user's identity, groups, and permissions without any auth code.

🔄

Five steps: extract token → validate JWT → check permissions → enrich context → run handler. Each step can fail independently with a specific error. The handler only runs if all steps pass.

Cognito authentication middleware pipeline showing the five steps: API Gateway request → Token Extraction → JWT Validation → Permission Check (RBAC) → Context Enrichment → Handler, with failure paths (401/403) and the UserContext object structure.
Fig. 1 — Cognito authentication middleware pipeline showing the five steps: API Gateway request → Token Extraction → JWT Validation → Permission Check (RBAC) → Context Enrichment → Handler, with failure paths (401/403) and the UserContext object structure.

03Permission Checks: AND, OR, and Hierarchies

The permission system supports flexible access control patterns.

The simplest case: require a single permission. requirePermissions({ required: 'users:read' }) checks that the user has the users:read permission. If they do not, the request is denied.

OR logic: requirePermissions({ required: ['users:read', 'users:write'] }) checks that the user has either users:read or users:write. Any one permission is sufficient. This is the default behavior.

AND logic: requirePermissions({ required: ['users:read', 'users:write'], requireAll: true }) checks that the user has both permissions. Both are required.

The checkPermissions function handles the actual check. It extracts the user context from the event, resolves the user's permissions from their groups, and evaluates the required permissions against the user's actual permissions.

The permission model is role-based. Users belong to Cognito groups (admin, moderator, member). Each group maps to a set of permissions defined in the role-permissions module. An admin has users:read, users:write, users:moderate, content:moderate. A member has users:read, content:read. The middleware resolves the group to permissions and checks against the required set.

The skipCheck option bypasses permission checking entirely — useful for testing and for endpoints that are authenticated but not authorized (any logged-in user can access them).

04User Context: What the Handler Receives

After the middleware pipeline completes, the Lambda handler receives a UserContext object with everything it needs to know about the authenticated user.

userId is the Cognito sub claim — a UUID that uniquely identifies the user across the platform. This is the primary key used in DynamoDB for user-related data.

email is the user's email address from the JWT claims. It is available for logging, notifications, and display purposes.

groups is an array of Cognito group names the user belongs to — admin, moderator, member, etc. A user can belong to multiple groups.

userGroup is the primary group — the first group in the array, used for quick role checks without iterating the full list.

subRole is an optional sub-role within the primary group — for example, a moderator might have a sub-role of content-moderator or user-moderator.

permissions is the resolved permission set — the union of all permissions from all groups the user belongs to. This is what the permission check evaluates against.

The extractUserContext and getCurrentUser functions provide two ways to access this context. extractUserContext parses the JWT claims from the API Gateway event. getCurrentUser is a convenience wrapper that returns the same UserContext object.

05Circuit Breaker for Cognito API Calls

The middleware makes API calls to AWS Cognito — to validate tokens, to look up user details, to check group membership. These calls can fail. Cognito has rate limits. Cognito has outages. Network issues can cause timeouts.

The Cognito middleware integrates with the circuit breaker pattern from tctf-utils. Every Cognito API call goes through a circuit breaker. If Cognito fails repeatedly, the circuit opens and subsequent auth attempts fail fast with a clear error instead of hanging on timeouts.

The circuit breaker configuration is specific to Cognito: a dedicated service key (COGNITO_AUTH), configurable failure threshold and reset timeout, and storage in DynamoDB so the circuit state persists across Lambda invocations.

The handleCognitoError function classifies Cognito errors. NotAuthorizedException becomes an AuthenticationError. UserNotFoundException becomes a UserNotFoundError. Throttling exceptions are retryable. Service unavailable errors trip the circuit breaker. Each error type gets the appropriate handling.

This means a Cognito outage does not cascade through the platform. The circuit breaker opens, auth requests fail fast, and the platform can degrade gracefully — showing cached content, allowing read-only access, or displaying a maintenance message — instead of hanging on every request.

🛡

️ Cognito API calls are circuit-breaker protected. A Cognito outage does not cascade — the circuit opens, auth fails fast, and the platform degrades gracefully instead of hanging.

06How Services Use It

Using the middleware is straightforward. A Lambda handler imports withErrorHandling and the permission utilities, wraps the handler function, and declares the required permissions.

The withErrorHandling wrapper (from the error handling module) provides the try-catch, correlation ID, and timing. Inside the handler, getCurrentUser extracts the authenticated user context. If the endpoint needs specific permissions, requirePermissions is called before the business logic.

For endpoints that need authentication but not specific permissions — like a user viewing their own profile — the handler just calls getCurrentUser. The JWT is validated by the API Gateway authorizer, and the middleware extracts the user context.

For admin endpoints that need specific permissions — like managing other users — the handler calls requirePermissions with the required permission set. The middleware checks the user's groups against the required permissions and throws AuthorizationError if the check fails.

The pattern is consistent across all 34 services. Every authenticated endpoint uses the same middleware. Every permission check uses the same RBAC model. Every error is handled by the same error handling architecture. The security posture is uniform because the implementation is shared.

07Lessons Learned

Building the auth middleware taught us several lessons.

First, never parse JWTs in business logic. The middleware handles all JWT parsing, validation, and claim extraction. The handler receives a typed UserContext object. If a handler is importing jsonwebtoken, something is wrong.

Second, permission checks should be declarative, not imperative. requirePermissions({ required: ['users:write'], requireAll: true }) reads like a policy declaration. An if-else chain checking group names reads like spaghetti. Declarative permissions are easier to audit, easier to test, and harder to get wrong.

Third, circuit breaker protection for the auth provider is not optional. Cognito is a managed service, but managed services have outages. Without a circuit breaker, a Cognito outage means every request in the platform hangs until timeout. With a circuit breaker, the outage is detected in seconds and the platform degrades gracefully.

Fourth, the UserContext should be the only way handlers access user identity. No parsing headers. No reading JWT claims. No calling Cognito directly. One object, one source of truth, one place to add new user attributes when the auth model evolves.

📋

Key lessons: never parse JWTs in business logic, make permission checks declarative, protect the auth provider with a circuit breaker, and use UserContext as the single source of user identity.

The Cognito middleware is the security boundary of the platform. Every request passes through it. Every user identity is validated by it. Every permission is checked by it. And because it is a shared library in tctf-utils, every service gets the same security guarantees without writing a single line of auth code. That is the point of middleware — handle the cross-cutting concern once, correctly, and let the business logic focus on what it does best.

Editor's Note: This is Framework Series #2 in the TCTF Newsletter. Next in the series: DynamoDB Framework Part 2 — Building a Fluent Query Builder in TypeScript.

Never miss an edition

Subscribe to get TCTF newsletters delivered to your inbox.

Subscribe
PreviousBuilding TCTF's DynamoDB Query Framework, Part 1: Single-Table Design Patterns
NextHow to Stay Motivated in the Face of Uncertainties: Faith Beyond Doubt

In This Edition

  • Why Middleware Matters for Auth
  • The Five-Step Pipeline
  • Permission Checks: AND, OR, and Hierarchies
  • User Context: What the Handler Receives
  • Circuit Breaker for Cognito API Calls
  • How Services Use It
  • Lessons Learned

Browse by Month

May
  • The Struggles of Timelines and Schedules: When Building Gets Real
  • How to Stay Motivated in the Face of Uncertainties: Faith Beyond Doubt
  • Cognito Middleware: Building an Authentication Pipeline for Serverless APIs
  • Building TCTF's DynamoDB Query Framework, Part 1: Single-Table Design Patterns
April
  • Built to Last: Why Sustained Collaboration Is the Future of Tech Teams
  • Q2 2026 Roadmap: What's Next for the TCTF Portal
March
  • How We Built a Real-Time Messaging System with AWS Lambda and WebSockets
  • From Forum to Social Network: The Origin Story of Cometbid Social
  • Agentic AI: What It Means for Software Development and Why We're Paying Attention
February
  • Platform Update: Social Network Architecture, Achievement Engine, and What's Next
  • How We Built 34 Serverless Microservices: Architecture Patterns Behind the TCTF Platform
January
  • How We Secure the TCTF Platform: Principles Every Developer Should Know
  • New Year, New Projects: TCTF 2026 Roadmap

More From TCTF Newsletter

Built to Last: Why Sustained Collaboration Is the Future of Tech TeamsVol. 1, Issue 4

Built to Last: Why Sustained Collaboration Is the Future of Tech Teams

Most platforms optimize for transactions — post a job, hire, move on. TCTF is built around sustained collaboration: long-term teams, milestone-driven projects, language support that breaks barriers, and a community where everyone — not just developers — has a seat at the table.

April 15, 2026
Q2 2026 Roadmap: What's Next for the TCTF PortalQ2 2026

Q2 2026 Roadmap: What's Next for the TCTF Portal

Our quarterly roadmap for Q2 — what shipped in April, the origin of Cometbid Social, and the plan for May and June as we build toward user accounts, authentication, and the social network launch.

April 1, 2026
How We Built a Real-Time Messaging System with AWS Lambda and WebSocketsTech Series #3

How We Built a Real-Time Messaging System with AWS Lambda and WebSockets

Inside the architecture of TCTF's messaging platform — three services handling real-time chat, campaign delivery, and transactional notifications, all built on Lambda, API Gateway WebSockets, SQS, and multi-provider email with automatic failover.

March 15, 2026

Browse by Month

2026

May
  • The Struggles of Timelines and Schedules: When Building Gets Real
  • How to Stay Motivated in the Face of Uncertainties: Faith Beyond Doubt
  • Cognito Middleware: Building an Authentication Pipeline for Serverless APIs
  • Building TCTF's DynamoDB Query Framework, Part 1: Single-Table Design Patterns
April
  • Built to Last: Why Sustained Collaboration Is the Future of Tech Teams
  • Q2 2026 Roadmap: What's Next for the TCTF Portal
March
  • How We Built a Real-Time Messaging System with AWS Lambda and WebSockets
  • From Forum to Social Network: The Origin Story of Cometbid Social
  • Agentic AI: What It Means for Software Development and Why We're Paying Attention
February
  • Platform Update: Social Network Architecture, Achievement Engine, and What's Next
  • How We Built 34 Serverless Microservices: Architecture Patterns Behind the TCTF Platform
January
  • How We Secure the TCTF Platform: Principles Every Developer Should Know
  • New Year, New Projects: TCTF 2026 Roadmap