This guide covers Lambda authorizers with production-focused examples and trade-offs.
LLM-flavored authorizers are a trap when the model becomes the gate. The unfair pattern: keep hard allow/deny in IAM, Cedar, or a deterministic policy engine, and use Bedrock only to explain why a decision happened for humans and audit UIs. Prototype explanations inside Lambda authorizers; never let token probabilities decide production access.⚡ TL;DR: Evaluate Cedar/IAM first; call Bedrock only after a deny (or allow) is final. Stream a grounded explanation citing policy statements and request context. Fail closed if the model times out — the decision already exists. Wire with Bedrock Guardrails and AppSync+Bedrock tool safety.
Split decision from narration — Lambda authorizers
// authorizer.ts — decision is deterministic; Bedrock is optional narration
import { evaluate } from "./cedar-engine.js";
import { explainDeny } from "./bedrock-explain.js";
export async function handler(event: APIGatewayRequestAuthorizerEvent) {
const principal = verifyJwt(event.headers?.authorization);
const decision = evaluate({
principal,
action: event.requestContext.routeKey,
resource: event.path,
context: { tenantId: principal.tenantId },
});
// ✅ Hard gate — model never flips this
const effect = decision.allowed ? "Allow" : "Deny";
let explanation: string | undefined;
if (!decision.allowed && process.env.EXPLAIN_DENIES === "1") {
try {
explanation = await explainDeny({
statements: decision.matchedStatements,
request: { action: event.requestContext.routeKey, path: event.path },
});
} catch {
// ❌ Never: deny because Bedrock failed — decision already Deny
explanation = "Denied by policy; explanation unavailable";
}
}
return {
principalId: principal.sub,
policyDocument: buildIamPolicy(effect, event.methodArn),
context: {
tenantId: principal.tenantId,
explanation: explanation?.slice(0, 1024) ?? "",
policyVersion: decision.policyVersion,
},
};
}
❌
if (await bedrockSaysAllow(event)) return Allow.
Ground explanations in matched statements
# bedrock_explain.py — illustrative
SYSTEM = """You explain authorization denials.
Only cite the provided policy statements and request fields.
Never invent permissions. Never suggest bypasses.
Output <= 80 words for the API Gateway authorizer context map."""
def explain_deny(statements: list[dict], request: dict) -> str:
user = {
"request": request,
"matchedStatements": statements, # from Cedar residual
}
# Use Converse with guardrails; low temperature
return converse_text(system=SYSTEM, user=json.dumps(user), temp=0.1)
Latency and fail-closed UX
Authorizers sit on the hot path. Budget explanation calls separately from the decision:| Path | Budget | Behavior |
|---|---|---|
| Decision (Cedar) | ≤ 20 ms p95 | Always runs |
| Bedrock explain | ≤ 800 ms, async optional | Skip on timeout |
| Cache | Hash(principal, action, resource, policyVersion) | Reuse narratives |
// ✅ Optional async explain after Deny response already returned to edge
void queueExplanationJob({ requestId, statements }).catch(() => {});
// ❌ Await 5s Bedrock call inside every authorizer invoke
Closing checklist
✅ Dos– ✅ Deterministic policy engine is the only effect source
– ✅ Bedrock cites matched statements only
– ✅ Authorizer context carries
policyVersion for audits– ✅ Timeouts skip explanation, never flip Allow/Deny
– ✅ Guardrails on explanation prompts ❌ Don’ts
– ❌ Don’t let the model be the final authorization gate
– ❌ Don’t put Allow/Deny tokens in free-form model output you parse
– ❌ Don’t block requests waiting on Bedrock health
– ❌ Don’t log raw JWTs into model prompts
– ❌ Don’t skip idempotent agent tool patterns if explanations trigger side effects
Related reading
- Bedrock Guardrails: Block Prompt Injection Inside Internal Dev Tools
- AppSync With Bedrock: GraphQL Resolvers That Call Tools Safely
- Bedrock Retrieval Filters: Tenant Isolation for Multi-SaaS Code RAG
- AI Coding in VPC: Private Bedrock Endpoints and Secret Hygiene
Related: Day 31: Lambda + Bedrock: Sync, Stream, and Batch · Lambda Plus Bedrock: Stream Tokens Without API Gateway Caps · Bedrock Custom Model Import: Guardrails and IAM Still Must Wrap
Related: Day 56: Supervisor Patterns on Bedrock Agents · Day 43: Distillation for Edge and Lambda · Bedrock Prompt Management: Versioned System Prompts for Production
Watch: 100 Days of AI on YouTube
Short videos from CheatCoders, one AI engineering topic per day.
Most viewed
- Python Decorators Explained: From Simple Wrappers to Production Patterns
- Claude Code vs Cursor vs GitHub Copilot: Choose by Workflow, and Set Up One Repo That Works With All Three
- AI Agent Frameworks in 2025: LangGraph vs CrewAI vs AutoGen vs Raw API
- Python String Methods: Every str Method With Real Production Examples
- Css Animation Performance: Performance-Optimized Motion for Modern UIs
Newly added
- AWS Systems Manager Parameter Store: Hierarchical Runtime Config for Coding Agents
- Amazon Cognito Identity Pools: Federate Coding-Agent Tool Callers Without Long-Lived Keys
- Amazon Athena for Coding Agents: Query Tool Audit Logs and Eval Traces on S3 Without a Warehouse
- AWS Batch for Coding Agents: Parallel Overnight PR Reviews and Long-Running Jobs Without Lambda Timeouts
- Amazon Bedrock Prompt Management: Version and A/B Test Coding-Agent System Prompts
Deep-dive PDF
Get the expanded guide for this post — extra diagrams-style checklists, failure modes, and a production walkthrough. Free when you subscribe to CheatCoders.
Already subscribed? or open the subscribe page.
Discover more from CheatCoders
Subscribe to get the latest posts sent to your email.

Pingback: Chaos for IAM Boundaries: Prove Apps Fail Closed on Denied Actions - CheatCoders