Auth and OIDC
In dev mode, Quayside mints JWTs via a dedicated endpoint that anyone can call. In production, you replace this with a real OIDC integration.
What v1 ships
The minimum auth surface needed to run the proxy end-to-end:
POST /api/v1/auth/dev/mint-token— takes(principal_id, class_slug, tenant)and returns a signed JWT. Gated byQUAYSIDE_DEV_MODE=true.- Proxy validates the JWT on every call. Issuer, expiry, signature, required claims all enforced.
This is enough for developer laptops, scripts, and CI. It’s not enough for production — anyone who can hit the mint endpoint can mint any token they want.
What you need for production
Two changes from the dev-mode shape:
- Disable the dev mint endpoint by setting
QUAYSIDE_DEV_MODE=false. The endpoint returns 403; the proxy’s X-Quayside-* header fallback also turns off. - Mint JWTs via your own authenticated flow, then drop them into
x-api-keyasqsk_<JWT>.
The proxy doesn’t care where the JWT came from. It cares that:
- the signature verifies against
QUAYSIDE_JWT_SECRET issisquayside- it’s not expired
- the required claims (
sub,class_slug,tenant) are present
Two production patterns
A. Quayside-mints-from-OIDC sidecar (recommended)
Run a small auth service alongside Quayside that:
- Authenticates the user via OAuth/PKCE against your IdP (Entra, Okta, Keycloak, etc.)
- After OIDC returns, mints a Quayside JWT using the same
QUAYSIDE_JWT_SECRET - Returns the
qsk_<JWT>to the caller
The flow looks like this:
Engineer's CLI / dashboard │ │ OAuth/PKCE redirect ▼ Your IdP (Entra / Okta / ...) │ │ authorization code ▼ Quayside auth sidecar │ │ validates OIDC ID token, mints Quayside JWT ▼ Engineer's machine ← qsk_<JWT> cached │ │ x-api-key: qsk_<JWT> ▼ Quayside proxy → LLM providerThe proxy stays simple. The OIDC integration is a separate concern in a separate process.
B. Your IdP mints directly
If your IdP can sign JWTs with a shared key (or via JWKS that quayside trusts), you can skip the sidecar:
- Configure your IdP to mint JWTs with the right shape (iss, sub, class_slug, tenant, exp)
- Configure Quayside to trust your IdP’s signing key (HS256 with shared secret today; RS256 + JWKS is a roadmap item)
- The IdP’s normal token-issuance flow drops a Quayside-shaped token into the user’s hands
Trade-off: more coupling to your IdP, fewer moving parts.
How class_slug gets into the JWT
This is the design question for any production auth integration: how does the user end up with a JWT that authorises them to call as one specific class?
Some patterns we’ve seen:
- One class per user role — your IdP maps the user’s group membership to a class slug.
engineeringgroup ⇒eng/dev-tools. Simplest. - Class picker at login — the user picks “I’m calling as X” from a list of classes they’re allowed to use. More flexible, more UX.
- Per-process tokens — each agent process has its own service-account token with a baked-in class slug. Useful for batch jobs.
Quayside doesn’t dictate this. Pick the pattern that fits your IdP’s primitives.
What the proxy validates
Once x-api-key: qsk_<JWT> arrives:
- Strip
qsk_prefix - Decode JWT, verify signature against
QUAYSIDE_JWT_SECRET - Verify
iss == "quayside" - Verify not expired
- Verify required claims (
sub,class_slug,tenant) are present - Resolve
class_slugagainst the registry (404 + shadow log if unknown) - Auto-claim instance for
(class, sub)
The auth integration’s only job is to put a properly-signed JWT in the right shape into the user’s hands. Quayside does the rest.
What’s on the roadmap
- RS256 + JWKS — public/private key pairs with key rotation
- Multi-tenant signing keys — different signing keys per tenant
- Token revocation — a deny-list mechanism for the rare case
- Service-account class types — distinct from human-owned classes
For v1, plain HS256 + the dev mint endpoint cover the test-and-pilot case. Most customers will want to replace the mint endpoint before going to production.