How moving auth to the load balancer with ALB’s authenticate_oidc made our UI simpler, our defaults safer, and our incidents rarer
The Day “Just Store the Token” Stopped Being Funny
At some point, every frontend team gets the same suggestion:
“Just do OAuth in the browser, store the token, and attach it on API calls.”
It works—until it doesn’t.
Because the moment your UI becomes responsible for token storage, refresh logic, callback routes, and logout semantics, your “frontend” quietly turns into an auth product.
We fixed this by doing something that feels almost illegal:
We let the load balancer handle the login.
Specifically: AWS Application Load Balancer (ALB) + authenticate_oidc + a serverless frontend target (Lambda).
TL;DR (If You Only Read One Section)
- Problem: App-level OIDC spreads secrets + token handling across every UI route and runtime.
-
Move: Put OIDC at the edge using ALB
authenticate_oidc. - Result: Less auth code in the app, fewer token footguns, and a “secure-by-default” perimeter.
- Tradeoff: Local dev + logout semantics require intentional design.
Why This Pattern Is Trending Right Now
Across dev communities lately, the popular themes are consistent:
- “Stop overbuilding auth in every app.”
- “Move concerns up the stack.”
- “Make security the default, not a checklist item.”
Edge-auth patterns (ALB OIDC, gateway authorizers, access proxies) are having a moment because they reduce the number of places a team can accidentally get auth wrong.
The Real Problem: Token Chaos Isn’t One Bug—It’s a Lifestyle
If you do OIDC inside the frontend, you almost inevitably accumulate:
- A callback route you must never break
- Token storage debates (
localStoragevs memory vs cookies) - Refresh token logic (and the day it fails in production)
- “Why did it log me out?” issues
- Security reviews that keep expanding scope
And the nastiest part is: it’s not one critical bug—it’s a hundred tiny sharp edges.
The Pivot: Authentication at the ALB
When you use authenticate_oidc, the ALB becomes the bouncer:
- Unauthenticated requests get redirected to your Identity Provider (IdP)
- The ALB completes the OIDC flow
- The ALB maintains an authenticated session (cookie-based)
- Only authenticated requests reach your target
Your serverless frontend (often a Lambda router / SSR / fallback handler) simply… serves pages.
The vibe shifts from:
“Did we implement OAuth correctly?”
to:
“If I got a 200, I’m logged in.”
The Request Flow in 30 Seconds
Browser
|
| GET /anything
v
ALB (authenticate_oidc)
|
| not logged in?
| 302 -> IdP
v
IdP (login)
|
| 302 -> ALB callback
v
ALB (sets session cookies)
|
| forward
v
Lambda target (serverless frontend router)
Notice what’s missing:
- No client-side token parsing
- No callback handler in your React app
- No refresh logic scattered across fetch calls
A Minimal, Anonymized CDK Snippet
This is intentionally “shape only” (no real URLs, no org names). The essence is:
1) forward to a Lambda target group
2) wrap it with authenticate_oidc
from aws_cdk import aws_elasticloadbalancingv2 as elbv2
from aws_cdk import aws_elasticloadbalancingv2_targets as targets
from aws_cdk import SecretValue
frontend_tg = elbv2.ApplicationTargetGroup(
scope,
"FrontendTg",
target_type=elbv2.TargetType.LAMBDA,
targets=[targets.LambdaTarget(frontend_router_lambda)],
)
listener.add_action(
"FrontendWithOidc",
priority=100,
conditions=[elbv2.ListenerCondition.path_patterns(["/*"])],
action=elbv2.ListenerAction.authenticate_oidc(
issuer="https://idp.example/",
authorization_endpoint="https://idp.example/oauth2/authorize",
token_endpoint="https://idp.example/oauth2/token",
user_info_endpoint="https://idp.example/oauth2/userinfo",
client_id="<client-id>",
client_secret=SecretValue.secrets_manager("/path/to/oidc-secret"),
next=elbv2.ListenerAction.forward([frontend_tg]),
),
)
Quick rules that save pain:
- Keep the OIDC secret in a secret manager, not env vars.
- Make sure listener priorities don’t collide.
- Default to protecting
/*unless you truly want public routes.
How This Changed Our Security Posture (In Plain English)
1) “Secure by default” stops being a slogan
With ALB OIDC, every path behind the listener rule becomes authenticated by default. You’re no longer relying on every route guard, every component, and every refactor to “remember auth.”
2) Less token exposure in the browser
The browser is a hostile environment. Reducing token handling in the UI reduces your exposure to:
- XSS turning into token theft
- accidental logging of sensitive values
- copy-paste auth bugs across micro-frontends
3) Fewer app secrets
If your frontend app doesn’t need to “be an OAuth client,” it also needs fewer secrets and fewer complicated deployment rules.
The Subtle but Important Split: Auth vs Authorization
ALB OIDC is excellent at authentication (“who are you?”).
But you still need strong authorization (“what can you do?”):
- RBAC: role-based permissions
- ABAC: tenant/env/resource scoping
The clean division:
- ALB: verify the user is logged in
- Backend: enforce permissions and data scope
If you try to do all authorization at the load balancer, you’ll end up with something brittle and hard to evolve.
Gotchas (A.K.A. The Part Everyone Learns in Production)
1) Callback path behavior
ALB uses a callback endpoint (often something like /oauth2/idpresponse). Make sure your routing rules don’t accidentally break it.
2) Claims can get huge
Too many groups/roles/claims can hit header/cookie limits. Mitigations:
- keep tokens/claims lean
- fetch richer profile data server-side
- store heavy identity in your own session store
3) Logout is three separate things
There’s:
- app logout
- ALB session cookie
- IdP session
Define what “Logout” means for your UX and compliance requirements.
4) Local dev can feel weird
Production has ALB OIDC; your laptop doesn’t.
Good local-dev patterns:
- inject mocked identity headers in dev
- run a lightweight local gateway that simulates “auth at the edge”
- keep backend authorization testable without a real IdP
A Practical Rollout Checklist
- Verify OIDC endpoints: issuer + authorize + token + userinfo
- Store the client secret in a secret manager
- Confirm listener rule priority ordering
- Ensure callback path is reachable through routing rules
- Enforce HTTPS everywhere
- Enable ALB access logs
- Document logout behavior (what it clears)
- Write down the local-dev story (seriously)
When You Should Not Use ALB OIDC
Avoid / reconsider if:
- you need complex per-request authorization decisions before forwarding
- you don’t have an ALB in the request path (pure CDN with no origin auth)
- your org mandates a different gateway or zero-trust access layer
Closing: Make the Safe Path the Easy Path
The benefit of this pattern isn’t novelty.
It’s that you can remove an entire category of mistakes:
- less auth code in the UI
- fewer ways to leak tokens
- consistent enforcement across routes
And when security is the default, teams move faster—because fewer changes require “special auth handling.”
If you’ve done edge auth (ALB OIDC, gateway authorizers, access proxies), what hurt most for you: local dev, logout, or claim size?
Resources
- AWS Docs: Application Load Balancer authentication actions (OIDC)
- AWS CDK:
ListenerAction.authenticate_oidc - OAuth 2.0 / OIDC basics (for understanding redirects, authorization code flow)
About the Author
Suraj Khaitan — Gen AI Architect | Building scalable platforms and secure cloud-native systems
Connect on LinkedIn | Follow for more engineering and architecture write-ups
Top comments (0)