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

