Skip to content

Auth Engine

The Auth Engine is Lukeflow's consumer-facing authentication gateway. It sits in the request path between luke-consumer-ui and Core Engine: it verifies the caller's WorkOS token, mints a short-lived signed act-as-user token, and transparently reverse-proxies the request upstream — where the real authorization happens.

Production-ready · deployed

Repository: luke-auth-engine · Type: Platform service · Stack: Java 21 / Spring Boot 3.4

Overview

The Auth Engine solves one problem: keep the powerful act-as-user token out of the browser. The only credential a browser ever holds is its own WorkOS session token. Because the gateway is on the path for every request, it can exchange that browser token for an internal, short-lived RS256 token that vouches for the user to the engine — and that internal token never leaves the server-to-server hop.

It is a stateless translator. It holds no database and stores no session or user state of its own; all identity truth lives in WorkOS (authentication) and Core Engine (users, tenants, groups, and authorization). The gateway only verifies, mints, and forwards.

Its responsibilities are deliberately narrow:

  • Authenticate the caller by verifying their WorkOS access token (JWKS, strict issuer/audience, fail-closed).
  • Run the login flows server-to-server against the WorkOS User Management API (/auth/login, /auth/register, /auth/callback, social/SSO, refresh, logout).
  • Mint a short-lived signed act-as-user token (RS256, ~60s TTL, minted fresh per request).
  • Proxy the request verbatim to Core Engine (and, for the DOCUMENTS byte tier, optionally to luke-file-proxy).

Where authorization happens

The Auth Engine authenticates; it does not authorize. Core Engine verifies the gateway's token against its published JWKS and enforces the user's tenant/group permissions. See Authentication & Authorization for the full model.

luke-core-ui (the operator console) talks to the engine directly over HTTP Basic and does not pass through this service.

Architecture

The gateway performs a verify → mint → proxy exchange on every request. It parses no request bodies — Spring's multipart resolver is disabled so file uploads stream through untouched (Content-Type and boundary preserved), reaching the engine or file-proxy verbatim.

consumer-ui --WorkOS access token--> luke-auth-engine --act-as-user JWT--> luke-core-engine
                                            |                                     ^
                                    verify WorkOS (JWKS)                  verify gateway (JWKS)
                                    mint signed RS256 token               setAuthentication(user)
StepWhat happensEnforced by
1. VerifyThe incoming WorkOS access token is validated against WorkOS's JWKS. In prod, issuer + audience are required and any mismatch fails closed (all tokens rejected) rather than accepting expiry-only.WorkosTokenVerifier, AuthHardeningGuard
2. ResolveThe verified WorkOS subject is resolved to an engine userId. Service accounts (robots) instead present X-Service-Key at /service/token.IdentityResolver, ServiceKeyRegistry
3. MintA short-lived RS256 act-as-user token (issuer luke-auth-engine, audience luke-core-engine, ~60s TTL) is signed with the gateway's private key.GatewayKeys, SessionService
4. ProxyThe request is forwarded transparently to Core Engine (or file-proxy for /api/documents/** byte operations), carrying the minted token and a correlation id.EngineProxyController
5. PublishThe gateway exposes its signing key(s) at /.well-known/jwks.json so the engine can verify minted tokens; rotation overlaps old + new kid.JwksController

The act-as-user token is stateless and per-request — there is nothing to store, so a restart or a second instance behaves identically (modulo the in-memory rate-limit counters noted below).

Key features

Hardening is layered so dev/qa stay lenient while prod fails fast on any missing guard:

  • DevModeGuard — dev-mode (the X-Dev-User header / /dev/token backdoor) requires the dev Spring profile. The app refuses to boot if dev-mode is on without it, so a single stray env var can't open the backdoor in prod.
  • AuthHardeningGuard — under the prod profile (or luke.auth.require-hardened=true) the app refuses to start unless both strict WorkOS validation and a stable signing key are enforced.
  • Require-stable-key prod guardGATEWAY_REQUIRE_STABLE_KEY=true forbids the ephemeral-key fallback, so a prod restart never silently rotates the engine's trust anchor.
  • Per-IP rate limiting — a fixed-window limiter over the credential/token endpoints (/auth/login, /auth/register, /auth/password, /service/token), keyed by client IP + path.
  • Correlation-id filter — tags every log line (MDC correlationId) and forwards the id to Core Engine so a trace spans the gateway hop.
  • Audit logger — privileged/auth events on a dedicated luke.audit logger, always emitted at INFO for a filterable trail.
  • Service-key hashing + live revocation — service keys may be stored SHA-256-hashed at rest, carry ;exp= self-expiry and ;scope= metadata, and be revoked live via POST /service/keys/{keyId}/revoke with an operator token (no redeploy).
  • XFF trusted-proxy-hops — client IP is resolved from X-Forwarded-For honoring a configured number of trusted reverse-proxy hops, so a spoofed prepended header can't fool the rate limiter or audit source IP.
  • Key rotation with overlap — a previous public key can be published in the JWKS alongside the current one, so tokens signed by either kid verify during a zero-downtime rotation window (see docs/key-rotation.md).

Technology

ConcernChoice
Language / runtimeJava 21
FrameworkSpring Boot 3.4.13 (spring-boot-starter-web, -actuator)
JWT / JOSEspring-security-oauth2-jose (Nimbus) — verify WorkOS tokens, mint/sign act-as tokens
Authentication providerWorkOS User Management (login flows + JWKS verification)
PersistenceNone — stateless gateway
BuildMaven (wrapper), packaged as a fat jar
ContainerMulti-stage Dockerfile (Temurin 21 JRE, non-root, container-aware JVM sizing)
CIGitHub Actions — mvnw verify on every PR / push to develop; separate Semgrep + gitleaks + Trivy security scan
Size~25 Java source files, ~75 tests (JUnit / Spring Boot Test)

Local development

The service runs on port 8083 locally (the engine uses 8080). Ephemeral defaults let it boot with no secrets for local work.

bash
./mvnw spring-boot:run          # run locally on :8083
./mvnw clean package            # build the jar
./mvnw -B -ntp verify           # compile + tests + package (what CI runs)
docker build -t luke-auth-engine .

Key environment variables (see src/main/resources/application.yml for the full set):

Env varPurpose
CORE_ENGINE_URLUpstream engine base URL the gateway proxies to
FILE_PROXY_URLOptional — routes /api/documents/** bytes to luke-file-proxy instead of core
WORKOS_CLIENT_ID / WORKOS_API_KEYWorkOS credentials for the server-side login flows
WORKOS_ISSUER / WORKOS_AUDIENCEExpected token claims (required when strict validation is on)
WORKOS_STRICT_VALIDATIONProd: require issuer + audience, fail closed
GATEWAY_PRIVATE_KEYPEM PKCS#8 RSA key for signing act-as tokens (ephemeral if unset)
GATEWAY_REQUIRE_STABLE_KEYProd: forbid the ephemeral-key fallback
ALLOWED_ORIGINSCORS origin(s) of the consumer UI (local dev allows any localhost port)
SPRING_PROFILES_ACTIVE=prodActivates the fail-fast hardening guards

Dev-mode backdoor

LUKE_AUTH_DEV_MODE=true (with the dev profile) enables the X-Dev-User header and /dev/token for local testing. It must never be enabled in a shared or production environment — the DevModeGuard blocks boot if it's set without the dev profile.

Deployment

Deployed as a Docker web service on Render, defined by the blueprint in luke-platform (the platform-*-auth services). The active deployment branch is develop (matching the platform's develop/qa flow). Render injects PORT at runtime; the container runs as a non-root user with a container-aware JVM.

To wire the engine side, set LUKE_AUTH_GATEWAY_ENABLED=true and point LUKE_AUTH_GATEWAY_JWKS_URL at this service's /.well-known/jwks.json on Core Engine. See Deployment Topology for how the services fit together.

Status & gaps

Production-ready and deployed. Two known items to be aware of:

  • Stale Clerk reference in the README. The top of README.md still describes the flow in terms of Clerk (clerk:<sub> subjects, CLERK_* env vars), but the code and application.yml are fully on WorkOS — the pom.xml description, config, and verifier all reflect WorkOS. This is a documentation lag, not a behavioral one; the running service verifies WorkOS tokens.
  • In-memory, per-instance rate limiting. The credential/token rate limiter keeps its counters in process memory, so limits are enforced per instance and reset on restart. A single instance (or sticky routing) is fine; horizontal scale-out would want a shared store to enforce a global limit.

For how this service scores against the broader platform readiness checklist, see the Completeness Scorecard.

Lukeflow Manual · documentation snapshot July 2026