A design review of the architecture decisions behind a real-time access policy engine that evaluates live Okta identity against configurable policy rules — and shows its full reasoning, not just a verdict.
When an employee is blocked from a resource, two things almost never happen: the user doesn't get a clear reason why, and the security team doesn't get an audit trail explaining what specifically triggered the denial. The decision happens inside a policy engine — Okta's, Cloudflare's, an internal IAM system's — and the output is usually a single bit: allowed or denied. Everything that led to that bit stays inside the platform.
This creates two distinct operational problems. The first is a support burden: when a legitimate user is blocked, the helpdesk has no way to quickly explain why without escalating to whoever owns the policy configuration, because the decision logic isn't visible to anyone outside that team. The second, more consequential problem, is rollout risk: a security team wanting to tighten a policy — requiring MFA everywhere, blocking BYOD from sensitive resources — has no reliable way to know how many real users that change will affect before they ship it. Organisations experience this because access policy is usually configured in a vendor console with no simulation layer in front of it; the first time anyone learns the blast radius of a policy change is when the support tickets start arriving.
The intended customer is a mid-size to large organisation in the early-to-mid stages of a Zero Trust or adaptive access rollout — typically the point where a security or platform team has an identity provider deployed (Okta, Entra ID) but is making policy decisions manually, console by console, with no way to preview impact before enforcement.
The realistic operational picture: a security architect or platform lead is under pressure from a CISO or compliance mandate to tighten access controls — require MFA universally, restrict BYOD from critical systems, add step-up authentication for after-hours admin access — but is wary of breaking access for legitimate users in the process, particularly contractors, executives travelling internationally, or service accounts with non-standard authentication patterns. The business driver is rarely abstract; it's usually a specific upcoming audit, a board-level Zero Trust mandate, or a near-miss security incident that made leadership ask "how would we even know if this policy is safe to turn on?"
This system is deliberately simpler than a typical three-tier web app. The policy engine itself runs entirely in the browser as JavaScript — there is no backend service evaluating access decisions. The only backend dependency is a shared LLM endpoint used purely to generate a plain-English explanation of a decision after the fact, never to make the decision itself.
The decision to run policy evaluation entirely client-side, rather than as a backend service, is the most consequential architectural choice in this system — and it's the subject of the first entry in the next section.
amr claim), and the session validity are all genuinely real, not simulated.prompt=login parameter on the authorization request rather than relying on default behaviour./api/explain endpoint hosted on the same backend as the other two portfolio projects, using the identical Bedrock → Groq → Gemini → OpenRouter fallback chain — rather than asking a visitor to paste in their own OpenRouter API key.Walking through what happens when a signed-in user evaluates an access request against a critical resource.
amr (Authentication Methods Reference) claim, which determines whether the displayed MFA method is hardware key, TOTP, SMS, or none./api/explain endpoint generates a plain-English summary of the decision, appearing after the verdict rather than delaying it.prompt=login to force Okta to re-authenticate rather than silently reusing a cached session — necessary for demoing multiple personas credibly in one sitting.This is a single-user, single-session demo by design, so "scale" here means something narrower than typical backend throughput — it means what would need to change for the underlying decision model to support a real organisation.
Server-side enforcement. The most important evolution would be moving the same policy logic to a backend service that actually gates access to real resources, with the client-side version becoming a preview/simulation layer in front of it rather than the only implementation. This is the single biggest gap between "demo" and "product."
Additional identity providers. The claims-extraction logic is currently written specifically against Okta's token shape (the amr claim format, the groups claim configuration). Supporting Entra ID or another IdP would mean abstracting that extraction behind a common interface rather than assuming one provider's token format.
Real device posture signals. Moving from user-configurable device context to genuine MDM integration (Jamf, Intune) would require a backend component polling those APIs and feeding real-time posture into the policy engine — a meaningfully larger scope than anything in the current implementation.
Multi-tenant policy configuration. The seven policies are currently hardcoded. A real multi-customer version would need policies to be configurable per organisation, stored, and versioned — closer in shape to how the discovery copilot's gap catalogue is structured than to anything in this project today.
Server-side policy enforcement first. Before anything else, the same rule logic needs a backend implementation that's actually in the request path for real resources — the client-side version becomes a what-if simulator sitting in front of it, not the system of record for decisions.
Observability. There is currently no logging of decisions anywhere outside the browser session that produced them. A production version needs every decision persisted with its full trace, queryable by user, resource, and time range — which is exactly the gap the seeded CISO dashboard is currently standing in for with illustrative data.
Multi-tenancy and RBAC. Policy configuration needs to move from hardcoded JavaScript to a per-organisation, role-gated configuration store, with audit logging on every policy change — not just every access decision.
Reliability of the explanation feature. The AI explanation depends on a separate project's backend being up. In production, this dependency would need its own dedicated, independently-scaled service rather than piggybacking on infrastructure built for a different system.
Real device posture integration. Moving from user-selected device context to a genuine MDM integration is probably the single highest-value next step for credibility — it's the gap most likely to be noticed by a technical reviewer.
The groups claim appearing empty despite correct Authorization Server configuration, traced eventually to a stale cached token from before the claim existed, is exactly the kind of integration problem that only shows up against a real identity provider. Building against a mock would have produced a demo that looked correct without ever proving the claims-handling logic actually worked.
The original AI explanation panel required visitors to paste in their own API key, and showed exactly that instruction by default. It would have been better, in hindsight, to build the shared-backend version from the start rather than shipping a feature whose default state looks broken. The fix — routing through infrastructure already built for the other two portfolio projects — took less time than the original client-key implementation had, which says something about over-engineering the first version.
Demoing multiple personas (admin, contractor, engineer) against the same policies requires actually forcing Okta to re-prompt for credentials, which doesn't happen by default once a session exists. This needed an explicit prompt=login parameter — a small technical detail, but one that directly determined whether the demo could show contrast between user types in a single recording session.
Given the chance to rebuild, policy evaluation would be written once as a shared module and run in two places — client-side for the instant preview, and server-side as the actual enforcement point — rather than living only in the browser. The current single-location design was the right choice for a transparency-focused demo, but it's a design that doesn't extend cleanly toward becoming a real product without a meaningful rewrite, which is worth knowing going in rather than discovering later.