All notable changes to Authorizer will be documented in this file.
The format is based on Keep a Changelog, and this project adheres to Semantic Versioning.
Targets the 2.4.0 release. Significant additions include enterprise SSO (SAML IdP + verified domains + home realm discovery), WebAuthn/passkey login, SCIM 2.0 groups, redesigned MFA behavior, and OAuth 2.1/MCP hardening.
- Unified OAuth Client registry (machine & agent identity foundation): All clients (human, machine, agent) are registered in a single
authorizer_clientstable with akinddiscriminator (interactive|service_account). Service accounts can use theclient_credentialsgrant for machine-to-machine authentication, while agents can participate in delegation chains. Admin GraphQL/gRPC operations manage clients with secret generation (32-byte crypto/rand, bcrypt-12 at rest), scope-subset enforcement, and one-time secret reveal (#648). - Machine-to-machine (service-to-service) authentication: Service account clients use the RFC 6749 §4.4
client_credentialsgrant at/oauth/tokento mint access tokens for autonomous workloads. Tokens carrylogin_method: service_accountand resolve toservice_account:<client_id>FGA subjects instead of users. Scope-subset enforcement and timing-safe authentication prevent privilege escalation (#641, #642, #644, #645, #647). - Secretless workload identity (RFC 7523 + SPIFFE JWT-SVID + Kubernetes TokenReview): Service accounts can authenticate via
client_assertion(JWT-bearer) withprivate_key_jwtorjwt-spiffeassertion types. Trusted issuers validate assertion signatures, pin subject claims, and prevent replay via single-usejtiin a bounded-TTL cache. When enabled, Kubernetes TokenReview API validates projected ServiceAccount tokens before issuance. All authentication paths share constant-time comparison and SSRF-hardened external fetch for JWKS (#654, #659). - Registry-authoritative client authentication: All client-auth sites (
/oauth/introspect,/oauth/revoke,/graphqlclient-check middleware, OIDC discovery) now route through the unified client registry via a shared resolver instead of static config comparisons. Introspection no longer leaks cross-client information; revocation uses token-ownership guards;/graphqlclient-id validation is now abort-safe. Discovery endpoint (/.well-known/oauth-authorization-serveralias for MCP compliance) now advertisesclient_credentialsandprivate_key_jwtas supported grant/auth methods (#655). - Interactive client registry columns & reserved-client seed: Client registry schema extended with
interactive_kind(for future SSO/delegation profiles), and a reserved boot-seeded client with immutableClientIDmatching--client-idflag. Prevents accidental duplication and enables deterministic identity (#652). - Shared client-auth resolver & grant-matrix hardening: The token endpoint and OAuth client-check middleware now call a single
clientauthresolver to authenticate via Basic auth, form body, orclient_assertion. Grant-matrix validation ensuresclient_credentialscannot be issued tointeractiveclients and vice versa (#651). - Agent-to-agent (A2A) delegation (RFC 8693 token-exchange): Authenticated service account agents can exchange a user's access token + their own actor token for a resource-bound, attenuated access token carrying a nested
actactor chain.subremains the user;actencodes "agent acting on behalf of user" (multi-hop via recursion). Attenuation is monotonic — effective scope is the intersection of subject token scope, agent's allowed scopes, and requested scope (deny-all if empty). Single resource per request, hard depth cap on nesting, and reserved claims prevent forging (#658). - Organizations & user-org membership: Foundational entities for multi-tenant isolation. Organizations are created/managed via admin API; users are members of orgs with audit trails. Org membership and admin roles gate access to org-scoped resources (OIDC/SAML/SCIM connections, group bindings) (#653).
- Per-organization OIDC SSO federation: Organizations can configure upstream OIDC IdP connections (Okta, Entra, Google). When enabled,
/oauth/sso/:org_slug/logininitiates PKCE+nonce to the upstream IdP;/callbackexchanges the code, validates the ID token, and JIT-provisions users. User resolution is federated-identity-namespaced(org_id, issuer, subject), preventing account-takeover via email collision. Discovery of upstreamsso_oidcrows rejectsclient_assertionauth via CR1 kind discriminator (#657). - Per-organization SAML 2.0 SSO (Service Provider): Organizations can configure upstream SAML IdP connections. Authorizer acts as a SAML Service Provider, handling signed assertions with XML-DSIG validation, per-org audience/recipient/destination binding, NotBefore/NotOnOrAfter skew, and single-use AssertionID replay detection. JIT-provisioning namespaces users by
(org_id, IdP entity-id, NameID). Admin CRUD is super-admin gated (#660). - Per-organization SCIM 2.0 user provisioning: Organizations create scoped SCIM bearer tokens; IdP (Okta, Entra) auto-provisions users via
/scim/v2/UsersCRUD endpoints.active:falsePATCH/DELETE deactivates users and revokes sessions/refresh tokens. Org isolation is enforced by bearer token (never URL/body). ExternalID dedup is org-scoped. Three pre-existing storage bugs fixed: Couchbase silent deprovision no-op, Redis session revocation glob mismatch, and Cassandra async-index race on membership inserts (#656). - Fine-grained authorization (FGA) — embedded OpenFGA ReBAC engine: Replaces the non-released bespoke FGA with an OpenFGA-backed relationship-based access control (ReBAC) engine. Admin GraphQL
_fga_model,_fga_tuples,_fga_write_tuples,_fga_read_tuples,_fga_delete_tuplesmanage the authorization model and tuples. Public API:CheckPermissionsandListPermissions(both transport-agnostic, subject pinned to caller, fail-closed). Session/validate endpoints userequired_relations(FGA) instead ofrequired_permissions.--authorization-engine=fga,--fga-mode(embedded|external),--fga-store(memory|sqlite|postgres|mysql) configure deployment. SQLite driver unified onmodernc.org/sqlitefor both app and embedded OpenFGA datastore (#625). - Multi-protocol public API surface (GraphQL + gRPC + REST + MCP): Public GraphQL operations are now available on all four transports. gRPC (
port 9091,authorizer.v1.AuthorizerService, 20 RPCs); REST (/v1/{method}, gin router, same middleware); MCP (authorizer mcpCLI subcommand forclaude mcp add). Single proto source of truth with buf STANDARD lint enforced. Single service layer (RequestMetadata/ResponseSideEffects) backing all transports. gRPC reflection enabled (flag-gated). REST usesUseProtoNames=truefor snake_case parity with GraphQL. MCP schema derived from proto descriptors with cycle guard. Admin ops remain GraphQL-only (#620). - Dashboard admin pages for client management and trusted issuers: New
/identity/clientspage lists, creates, edits, and rotates service account clients;client_secretplaintext shown exactly once (create/rotate) in a copy dialog./identity/trusted-issuersprovides full CRUD of trusted issuers with field constraints per update request shape. Overview card relabeled Default Client ID with a link to the new Clients page (#662). - Dashboard admin pages for organization management: New
/identity/organizationspage lists, creates, edits, deletes organizations. Organization detail view includes Members (add/remove), per-org SSO connections (OIDC/SAML create/edit/delete), and SCIM endpoint management (create/delete/rotate token) (#663). - gRPC transport (port 9091): all 20 public auth operations and 32 admin operations are now served over native gRPC alongside GraphQL and REST. The listener binds to
--grpc-port(default9091), separate from the HTTP port. All three transports share the same service layer and return identical flat response shapes (#634, #635). AuthorizerAdminServicegRPC + REST: 32 admin operations — user management (Users,User,UpdateUser,DeleteUser,InviteMembers), verification requests, tokens, webhooks, email templates, audit logs, FGA model/tuples (_fga_model,_fga_tuples,_add_fga_tuple,_delete_fga_tuple), and admin session/meta — are now reachable over all three transports. Previously admin ops were GraphQL-only (#631).- gRPC auth interceptor: bearer-token / session-cookie authentication is applied uniformly by a gRPC server interceptor. The verified identity is attached as
authctx.Principalso all handlers share a single auth path with no per-handler duplication (#636). - Client metadata helpers (
transport.MetaFromGRPC): extract client ID, session token, and access token from gRPC incoming metadata using the same keys as the HTTP handlers, enabling consistent context propagation across transports (#636). - WebAuthn / passkey registration and login: users can register and authenticate with FIDO2 security keys or platform authenticators (Windows Hello, Touch ID, Face ID, etc.). Supports usernameless discoverable login, passkey as a second MFA factor, and multi-passkey management per user (#671).
- Enterprise SSO with SAML 2.0 Identity Provider: Authorizer can now act as a SAML IdP so downstream SaaS applications (Zendesk, Notion, Tableau, etc.) can federate against Authorizer. Includes signing key rotation with overlap windows, attribute mapping, and strict ACS/entity-ID binding. Service Provider role (consuming upstream Okta/Entra) continues to work in parallel (#691).
- Multi-tenant SSO with verified email domains and home realm discovery: Three-phase addition enabling tenant isolation and automatic IdP routing. Phase 1: org-scoped admin role (
authorizer:org_admin) lets tenant admins manage their org's SAML/OIDC/SCIM and members without platform super-admin access. Phase 2: verified email domains per organization via DNS TXT challenge or super-admin assertion, with first-writer-wins atomicity across all 13 database providers. Phase 3:/api/v1/org-discoveryendpoint routes login traffic to the appropriate tenant's IdP based on email domain, integrated into the/applogin page with--enable-org-discovery(off by default) (#672, #674, #675). - SCIM 2.0 group provisioning with OpenFGA role binding and SAML group assertions: SCIM
/Groupsendpoint (full CRUD + PATCH) with RFC 7644 §3.5.2 patch semantics and real-world Entra/Okta deviations handled. Membership flows through OpenFGA tuples (group:<org>/<id>#member@user:<uid>), and group→role bindings via the existing userset pattern. SAML IdP automatically asserts group membership as multi-valued attributes in issued assertions, with cross-tenant containment gates to prevent group-name leakage across organizations (#694). - Service accounts as first-class FGA subjects: registered
kind=service_accountclients inclient_credentialstoken flow now resolve toservice_account:<client_id>in authorization checks instead ofuser:<sub>, enabling autonomous machine authorization. Opt-in via modeling (engine denies if the model lacks theservice_accounttype), and scopes remain the issuance ceiling (#665). - Trusted-issuer token review config in admin API: new
_trusted_issuer_request_token_reviewand_revoke_trusted_issuer_token_reviewmutations allow admins to request and revoke token review certificates per trusted issuer without restarting the server (#667). - Server-side user search and org membership in dashboard:
_usersquery now accepts a search parameter supporting case-insensitive matching across email, name, and ID fields on all 13 database providers (native SQL/Mongo/Arango; O(n) scan with documented upgrade paths for DynamoDB/Cassandra).OrgMembertype now includes email and name fields, so admin UIs can display human-readable member lists (#678, #680). - Per-method MFA availability signals in meta: new
is_totp_mfa_enabled,is_email_otp_mfa_enabled,is_sms_otp_mfa_enabled, andis_webauthn_enabledfields on the publicmetaquery allow login UIs to show only available MFA methods (#681). authorizer_required_permissions_checks_total{endpoint, outcome}: per-endpoint Prometheus counter for FGA adoption + enforcement signal. Outcomes aregranted,denied,not_requested,error. Endpoints aresession,validate_session,validate_jwt_token. Alert onoutcome="error"rising; it indicates a storage/validation failure preventing checks from completing (#527).--rate-limit-fail-closed: when the rate-limit backend returns an error, respond with503instead of allowing the request (default remains fail-open).--metrics-host: bind address for the dedicated/metricslistener (default127.0.0.1). Use0.0.0.0when a scraper on another host/pod must reach the metrics port over the network; keep the metrics port off public ingress.- OIDC Discovery —
grant_types_supportedincludesimplicit: honestly reflects that/authorizeacceptsresponse_type=tokenandresponse_type=id_token. - OIDC Discovery caching: discovery document and JWKS are now cached server-side with strict expiry, reducing external provider request load during token validation for social logins like Twitter (#668).
- Graceful shutdown for background work: detached goroutines that fire request side effects (email/SMS sends, webhook events, audit log writes) are now tracked and drained on shutdown instead of being silently killed mid-flight, and a panic inside one is recovered and logged instead of crashing the whole process (#696).
- BREAKING — at-rest encryption key split from the JWT secret (
--encryption-key). The key used to encrypt secrets at rest (TOTP secrets, and the OTP digests behind email/SMS verification and password reset) is now its own input and no longer derives from--jwt-secret. A deployment using RS256/ES256 (--jwt-private-key/--jwt-public-key) without--jwt-secretwill refuse to start until--encryption-keyis set — HMAC deployments (HS256/384/512) are unaffected, as the JWT secret still resolves the key. This is a security fix, not a preference: in 2.2.1 through 2.4.0-rc.13, an asymmetric-JWT deployment with no--jwt-secretsilently fell back to a public constant compiled into the source, so anything encrypted at rest was protected by a key any reader of the repository already had. Operators on those versions must treat existing TOTP enrollments as compromised: rotate--encryption-key, then have affected users re-enroll (existing ciphertext was written under the old key and will not decrypt). Recovery codes are unaffected by the rotation — they are stored as unkeyed SHA-256 digests. HMAC deployments that leave--encryption-keyunset now log a startup warning: under the fallback, rotating--jwt-secretalso changes the at-rest key, and there is no re-encryption path — every enrolled TOTP user is locked out. Set a distinct--encryption-keybefore rotating. There is noENCRYPTION_KEYenvironment variable — v2 is flag-only (#742). - Admin dashboard UI migration from Chakra UI to shadcn/ui + Tailwind CSS: Dashboard (
web/dashboard/) completely modernized. Replaced Chakra UI v2 with shadcn/ui (Radix primitives) + Tailwind CSS v4. All TypeScriptanytypes and@ts-ignoredirectives eliminated; full type safety on GraphQL responses, component props, and data models. Dead dependencies removed (react-draft-wysiwyg, @emotion, framer-motion, react-icons, focus-visible). 17 shadcn/ui-style components built on Radix; Authorizer branding (logo + blue-500) applied throughout. Cleaner tables, Sheet panels for forms, sonner toast notifications, skeleton loading states (#605). - BREAKING — MFA behavior completely redesigned: on by default, optional per user, withheld token until setup complete. MFA methods (TOTP, Email OTP, SMS OTP, WebAuthn) are now enabled by default and opted out via new
--disable-totp-login,--disable-email-otp,--disable-sms-otp, and--disable-webauthn-mfaflags; the old--enable-totp-login,--enable-mfa,--enable-email-otp, and--enable-sms-otpflags are removed. Email and SMS OTP only take effect when their provider (SMTP / Twilio) is configured. Whether MFA is available is now derived from the enabled methods rather than a standalone flag, which fixes the case where MFA appeared "enabled" while every method was unavailable. New token-withholding behavior: when MFA is optional (--enforce-mfadefaultfalse), first-time users who haven't set up MFA no longer receive an immediate token followed by a setup offer — the token is withheld until the user completes enrollment or explicitly skips (remembered ashas_skipped_mfa_setup_at). This withheld-token model now applies uniformly to password login, passkey login, signup, and social login. When--enforce-mfais set, MFA is mandatory and un-skippable. Email/SMS OTP now require explicit enrollment (newemail_otp_mfa_setup/sms_otp_mfa_setupmutations) before they can be used for MFA verification, fixing the previous behavior where they fired automatically for any user with a phone/email on file. Admin recovery: newreset_mfaoperation on_update_userclears all MFA state and enrolled factors across all storage backends. User-initiated lockout: newlock_mfamutation prevents future MFA enrollment (admin-recoverable); lockout is refused if a verified Email/SMS OTP factor exists as a fallback.--disable-mfaone-way kill switch disables MFA entirely regardless of per-method flags (does not affect WebAuthn, which is a separate login recipe) (#682, #684, #685, #686). - License: relicensed from MIT to Apache License 2.0. Per the CNCF IP Policy (Charter §11(b)(iii)), Authorizer's outbound code is now distributed under the Apache License 2.0. Existing copies distributed under the MIT License remain valid under their original grant; this change applies to the project's outbound license going forward. See NOTICE for attribution.
- Fine-grained authorization is always enforcing. The previously-proposed
--authorization-enforcementflag and its dualpermissive/enforcingmodes were removed before shipping.required_permissionschecks against an unmatched or denied(resource, scope)pair returnunauthorized. There is no permissive "log but allow" mode. - Authz Prometheus shape:
authorizer_authz_checks_totalhas only aresultlabel (allowed|denied|unmatched|error);authorizer_authz_unmatched_totalhas no labels. - Prometheus
/metrics: always served on a dedicated HTTP listener (--metrics-host:--metrics-port, default127.0.0.1:8081).--http-portand--metrics-portmust differ;/metricsis not registered on the main Gin server. - HTTP metrics: unmatched Gin routes use the fixed path label
unmatchedinstead of the raw request URL (prevents cardinality attacks). - GraphQL metrics: the
operationlabel is nowanonymousorop_<sha256-prefix>so client-supplied operation names cannot explode time-series cardinality. - Health/readiness JSON: failure responses return a generic
errorstring; details remain in server logs. - OAuth callback JSON: generic OAuth-style error body on provider processing failure; details remain in logs.
/playgroundis subject to the same per-IP rate limits as other routes (health and OIDC discovery paths stay exempt)./metricsis not on the main HTTP router.- BREAKING:
/userinfonow strictly filters claims by scope per OIDC Core §5.4. The endpoint returns onlysubplus the claims permitted by the standard scope groups (profile,email,phone,address) encoded in the access token. Previously,/userinforeturned the full user object regardless of scopes. Clients that request only theopenidscope but read profile/email claims from/userinfomust now request those scopes explicitly. See https://docs.authorizer.dev/core/oauth2-oidc for the full scope→claim mapping. - OAuth 2.1 standards compliance: refresh-token reuse detection revokes the user's entire session family on replay (RFC 8707 compliance);
resourceparameter binding on authorization code flow (binds access tokenaudclaim); new--oauth21-strictflag (default off) gates implicit-grant and PKCE-plain removal behind opt-in. NewGET /.well-known/oauth-authorization-serverthin alias of OIDC discovery for MCP compliance (#693).
- OIDC/OAuth2 specification compliance for Enterprise IdP integration:
/authorizenow returns RFC 6749 error codes (invalid_request, unauthorized_client, unsupported_response_type) instead of freeform strings; errors afterredirect_urivalidation redirect to the RP per spec instead of returning JSON. ID tokens now includeauth_timeclaim on all issuance paths (OIDC Core §2 requirement formax_age). Discovery endpoint advertises"none"intoken_endpoint_auth_methods_supportedfor PKCE-only public clients.token_typenormalized to"Bearer"(capitalized). In-memory state store enforced with 10-minute TTL; DB state store enforces 600-second read-time TTL.Cache-Controlcaching added to discovery endpoint (#604). - RFC-compliant PKCE and redirect_uri security hardening: S256
code_challengenow tolerates base64url padding (Auth0 compatibility).client_secretvalidation enforced whenever provided, even when PKCE is used (prevents secret bypass).code_verifierrejected when nocode_challengewas registered (prevents PKCE bypass).redirect_uriURL-encoded in state to prevent@@-delimiter injection./oauth/tokennow validatesredirect_urimatches the/authorizeregistration (RFC 6749 §4.1.3). Authorize state removal is synchronous (prevents code reuse). Constant-timeredirect_uricomparison (#603). - Introspection authentication & backchannel SSRF hardening:
/oauth/introspectnow requiresclient_secretwhen configured (previously omitting secret bypassed auth entirely). Timing-safecrypto/subtle.ConstantTimeCompareused for all secret validation. Backchannel logout SSRF fixed by routing throughSafeHTTPClient(upfront DNS, IP pinning, rejects private/loopback). Session rollover goroutine errors now logged instead of silently discarded (#606). - Session revocation on password reset: when a user resets their password via email verification link or the recovery flow, all of their active sessions are immediately revoked, preventing unauthorized account access after a compromised password (#669, #673).
- Per-user TOTP brute-force lockout: failed TOTP verification attempts are now tracked per user with temporary lockout after 5 failures (matching the existing per-user email/SMS OTP lockout), and recovery codes are hashed at rest using bcrypt (never stored plaintext) (#670).
- SAML ACS CSRF-origin exemption: the SAML Assertion Consumer Service endpoint is now correctly exempted from strict CSRF Origin checking, since browser-based SAML POSTs from a different origin (the IdP) are expected and legitimate (#666).
- Trusted base URL + email/SMS OTP lockout: new
--urlflag (config.AuthorizerURL) sets the single trusted source for the server's own URL used in email verification links, JWTissclaim, and OIDC discovery, preventing header-spoofing attacks that could redirect users to attacker-controlled sites while carrying single-use tokens. Email/SMS OTP verification now gets the same per-user brute-force lockout that TOTP already had (#698). - Type-safe error handling in gRPC admin service: admin service methods now return properly-typed errors (400 for validation, 409 for conflicts, etc.) instead of generic Internal errors (500), and public-method bypass is tightly scoped to only the public service and
AdminLogin(#700). - Atomic storage operations with transaction guards:
UpdateUsersempty-ids filter is now enforced across all 13 database providers (preventing silent full-table updates on Mongo/Arango/Cassandra/Couchbase/DynamoDB); cascade deletes (DeleteOrganization,DeleteClient,DeleteWebhook,DeleteUser) are now wrapped in transactions, rolling back on partial failure (#699). - Delegated tokens are gated per operation by their
scopeclaim: an RFC 8693 delegated token may only reach operations cleared for delegated callers, and only while itsscopecarries the scope that operation requires. Refusals areinsufficient_scope(RFC 6750 §3.1). Until this existed the attenuation the token endpoint computes —subject_token.scope ∩ agent.allowed_scopes— was returned to the caller and then never consulted, so a delegated token reached every first-party operation: an agent an operator grantedopenidfor a downstream MCP server could read the delegating user's profile, mutate the account, and deactivate it. Read-only identity and permission queries (check_permissions,list_permissions,profile,meta— exactly the built-in MCP tool set) require onlyopenidand are unaffected. Mutating operations require a scope no client requests by default:authorizer:profile:writeforupdate_profile,authorizer:account:deletefordeactivate_account. Because a delegated scope is the intersection of the user's and the agent's, a sensitive operation needs both halves — the user's own token must carry the scope and an admin must have granted the agent a ceiling including it — so neither party can widen an agent alone. Fails closed: any operation not explicitly cleared is denied to delegated callers whatever scope they hold, so new operations are unreachable by agents until deliberately added. First-party tokens are deliberately not gated —loginaccepts a caller-suppliedscopewith no allow-list, making it a hint rather than a boundary, so enforcing it there would break existing clients while granting no security. Enforced identically on GraphQL and on gRPC (which also covers the REST gateway and the MCP server) (#742). - Agent authority is the intersection of agent and user permissions: a delegated (RFC 8693) caller's effective authority on
check_permissionsandlist_permissionsis nowperms(agent) ∩ perms(user), evaluated per action at request time, rather than the delegating user's full authority. This is the Confused Deputy fix: an agent can no longer act on anything its user happens to be able to reach, and equally cannot exceed what its user could have done itself. Enumeration intersects too — an agent that cannot act on an object must not see it listed, or the user's resource names leak. Only the immediate actor participates; prior hops in theactchain are audit-only. The subject can never be widened by a request parameter: an explicituseris honoured only as the caller's own subject and never sheds the agent half, and a delegated token naming any other subject is refused outright — including when an admin credential rides along on the same request. Opt-in is declaringtype agentin the authorization model, with no flag: checkingagent:<id>against a model lacking the type errors rather than returning false, so a flag switched on against an unprepared model would deny every delegated request. Deployments without the type keep today's behaviour byte-for-byte and are counted asauthorizer_fga_delegated_checks_total{outcome="not_enforced"}so the unenforced state is visible. Fails closed throughout: a model-read failure, a malformed agent id, or an inactive subject denies. See Agent Identity & Permissions. - Delegated tokens are revocable at Authorizer's own API: a delegated token now carries an opaque
sidnaming the session it was derived from, so logout, password reset, email change and admin session wipes stop it on the next call. Previously nothing a user or admin could do stopped one — it stayed valid for its full TTL, and the only working lever was revoking the user outright. A downstream resource server validates offline against the JWKS and still cannot see this, so the short TTL remains the only bound there; do not build a resource server that assumes otherwise. Fails closed: a delegation whose origin cannot be verified does not authenticate here. - Agent actions are attributed to the agent in the audit log: an action taken by an agent on a user's behalf is recorded with the agent as
actor_id,actor_type: agent, no actor email, and the delegating user preserved in metadata (delegated_user_id,delegated_user_email). Previously the delegating user was recorded as the actor on the GraphQL surface — the actor was read from a request principal that only the gRPC interceptor constructs — making an agent's actions indistinguishable from the human's, which cannot be reconstructed after the fact. RFC 8693 §1.1 draws exactly this line between delegation and impersonation.
- Public client_id now exposed in Client API type:
ClientGraphQL type and proto now includeclient_id(distinct from surrogateid). Dashboard Clients page displays correct "Client ID" (the configured client_id, not the internal id). Seeded interactive client has immutableclient_idfrom--client-idflag (#664). - Nil-pointer panics in claim/header type assertions: two unguarded type assertions on untrusted map values (email-verify redirect-uri and webhook-event headers) could panic and crash the process; now guarded with safe type coercion (#701).
- Dashboard and login UI crashes: CSV file import error handling in dashboard, non-null assertion guards in InputField component, logout button event handling, and WCAG label association for home realm discovery email input (#702).
- OIDC ID token
at_hash: now correctly set tobase64url(sha256(access_token)[:16])for all flows. Previously the implicit/token branch incorrectly setat_hashto the nonce value (OIDC Core §3.2.2.10). - OIDC ID token
nonce: now echoed in the ID token whenever it was supplied in the auth request, regardless of the flow used (OIDC Core §2). - Admin service error mapping and InviteMembers: gRPC error responses now use proper status codes (not all
codes.Internal); the gRPCpublicbypass is scoped correctly;InviteMembersno longer has redundant re-fetches, missingcontinuestatements, or unbounded batch sizes (#700).
authorizer_client_id_not_found_total: replaced byauthorizer_client_id_header_missing_total, which matches the actual behavior (header omitted, request still allowed). Update dashboards and alerts accordingly.- OIDC Discovery —
registration_endpoint: previously pointed to the signup UI rather than an RFC 7591 dynamic client registration endpoint. It will return when RFC 7591 is implemented.
Pre-release. See 2.2.1-rc.0 on GitHub.
- CSRF protection (middleware).
- Per-IP rate limiting with Redis and in-memory backends.
- GraphQL query complexity limit.
- 5-second execution timeout for custom access token scripts.
- Crypto: AES-GCM with HKDF key derivation (replaces AES-CFB); RSA 4096, improved
DecryptRSAerror handling and base64-related naming;crypto/randfor HMAC key generation. - JWT / tokens: Verify JWT algorithm in parse keyfunc; safe type assertions for claims; bearer extraction case-sensitivity fix; shorter session and refresh token lifetimes; reserved claim blocklist for custom token scripts.
- Cookies:
HttpOnlyon all cookies; reduced cookie max-age;SameSiteon admin cookie (with broader security-header and CORS credential fixes). - OAuth / redirects: Apple ID token signature verified via OIDC;
redirect_urivalidation hardened against open redirects and wildcard abuse. - GraphQL: SSRF protection for
_test_endpoint; constant-time admin secret comparison; user enumeration mitigated via generic error messages. - HTTP / parsers: Host header validation to reduce injection risk.
- Storage / DB: Parameterized AQL in ArangoDB
UpdateUsers; Cassandra client TLS verification enabled; GORMAllowGlobalUpdatedisabled;DeleteSessionimplemented for SQL and ArangoDB. - Email / templates: Explicit TLS
ServerNamefor SMTP;html/templatefor email rendering (SSTI mitigation);template.JSXSS-related fix. - Webhooks: SSRF protection, HMAC signatures, and response size limits.
- Data exposure: Password hash excluded from JSON serialization; JWKS no longer leaks HMAC keys.
- Operational: Sanitized errors, panics replaced with errors where appropriate; Dockerfiles hardened (defaults, signals, healthcheck); client ID audit logging and CSRF origin validation tightened.
- GitHub OAuth display name handling and POST logout behavior.
- MongoDB driver update and related compilation issues.
- Tests: custom script timeout coverage, client-ID metric behavior, and ArangoDB-related test hardening.
Full changelog: 2.2.0...2.2.1-rc.0
See 2.2.0 on GitHub.
- Prometheus metrics, health checks, and readiness HTTP endpoints (#528).
Full changelog: 2.1.0...2.2.0
See 2.1.0 on GitHub.
- Structured audit logging system.
- Audit logging consolidated behind an
internal/auditprovider.
- Open redirect: stricter validation for
redirect_uri.
Full changelog: 2.0.1...2.1.0
- CLI-based configuration: All configuration is now passed at server start via CLI root arguments. No env store in cache or database.
- New security flags:
--disable-admin-header-auth: Whentrue, server does not acceptX-Authorizer-Admin-Secretheader; only secure admin cookie is honored. Recommended for production.--enable-graphql-introspection: Controls GraphQL introspection on/graphql(defaulttrue; setfalsefor hardened production).
- Metrics endpoint: Metrics server on port 8081 (configurable via
--metrics-port). - Restructured project layout:
- Root-level
main.goandcmd/for CLI internal/for core packages (config, graph, storage, etc.)web/appandweb/dashboardfor embedded UIsweb/templatesfor HTML templates
- Root-level
- Build outputs: Binary named
authorizer; output tobuild/<os>/<arch>/authorizer. - Docker improvements:
- Multi-arch builds (linux/amd64, linux/arm64)
ENTRYPOINT [ "./authorizer" ]for passing CLI args at runtime- Alpine 3.23 base images
- Makefile targets:
make dev,make bootstrap,make build-local-image,make build-push-image.
- BREAKING: Configuration is no longer read from
.envor OS environment variables. Pass config via CLI flags. - BREAKING:
--client-idand--client-secretare required; server exits if missing. - BREAKING: Deprecated mutations
_admin_signup,_update_env,_generate_jwt_keysnow return errors directing users to configure via CLI. - BREAKING: Dashboard cannot update server configuration. Admin secret, JWT keys, and all env must be set at startup.
- BREAKING: Flag names use kebab-case (e.g.
--database-urlinstead ofdatabase_url). - BREAKING: Some inverted boolean flags (e.g.
DISABLE_LOGIN_PAGE→--enable-login-pagewithfalseto disable). - BREAKING: Go version requirement: >= 1.24 (see
go.mod). - BREAKING: Node.js >= 18 for web app and dashboard builds.
- Database provider template path:
internal/storage/db/provider_template(wasserver/db/providers/provider_template). - GraphQL schema and resolvers moved to
internal/graph/. - Tests moved to
internal/integration_tests/; run withgo test -v ./...from repo root.
database_url,database_type,log_level,redis_urlflags (use kebab-case--database-url, etc.).env_fileflag (no longer supported).
- Corrected Makefile
generate-db-templateand DB-specific test targets to use current project structure. - Docker build and release workflow updated for v2 layout and binary name.
See MIGRATION.md for a detailed guide from v1 to v2.
Authorizer v1 used environment-based configuration stored in cache/DB and configurable via dashboard or _update_env mutation. For v1 documentation, see docs.authorizer.dev and the v1 release branch.