Releases: authorizerdev/authorizer
Release list
2.4.0-rc.12
What's Changed
- security(deps): bump google.golang.org/grpc to v1.82.1 (GO-2026-6061) by @lakhansamani in #734
- chore(web/app): bump authorizer-react to 2.2.0-rc.5 by @lakhansamani in #735
Full Changelog: 2.4.0-rc.11...2.4.0-rc.12
2.4.0-rc.11
What's Changed
- fix(storage): mongodb unique-index batch + thread ctx through SQL GORM calls by @lakhansamani in #733
Full Changelog: 2.4.0-rc.10...2.4.0-rc.11
2.4.0-rc.10
What's Changed
- fix(dashboard): add SCIM/group events to webhook event dropdown by @lakhansamani in #724
- chore(web/app): bump authorizer-react to 2.2.0-rc.3 by @lakhansamani in #725
- chore(proto): regenerate stale client-SDK proto stubs + add CI staleness gate by @lakhansamani in #726
- fix(web/app): wire hasSmsOtp so WebOTP auto-fill actually activates by @lakhansamani in #727
- chore(proto): drop vendored Go/Python client stubs by @lakhansamani in #728
- docs: add release runbook for server/SDK version sync by @lakhansamani in #730
- chore(web/app): bump authorizer-react to 2.2.0-rc.4 by @lakhansamani in #731
Full Changelog: 2.4.0-rc.9...2.4.0-rc.10
2.4.0-rc.9
Bug fixes
GraphQL pagination schema standardized on a single-level shape (BREAKING). 6 List*Request input types (ListClients, ListTrustedIssuers, ListSAMLServiceProviders, ListOrganizations, ListOrgDomains, ListOrgMembers) and 3 operations (_verification_requests, _webhooks, _email_templates) required a PaginatedRequest wrapper (pagination: { pagination: { limit, page } }) that never existed on the proto/gRPC surface and that no strictly-typed SDK consumer could even construct correctly. PaginatedRequest is removed; all 9 affected fields/params now take PaginationRequest directly, matching every other GraphQL endpoint, the proto surface, and (coincidentally) the JS/Go/Python SDKs' existing type definitions for most of these endpoints.
- Coordinated SDK updates:
authorizer-js,authorizer-go, andauthorizer-pyall need matching updates for this release — see each SDK's own changelog.
Social login — 2 account-identity bugs, both found via live e2e testing:
- Discord:
_admin_meta's discovery flag (is_discord_login_enabled) was never populated, so no frontend could ever detect Discord login was configured, even though the OAuth flow itself worked. - Roblox: under the default scope configuration (no
emailscope), the raw numericsubwas stored directly inuser.Emailinstead of a synthesized address — not a duplicate-account risk, but a non-email string reaching anything that assumesuser.Emailis a valid address (magic-link resend, display text, webhook payloads).
Magic-link login: silent no-op when email service is misconfigured. MagicLinkLogin required EnableEmailVerification but never checked IsEmailServiceEnabled (unlike signup's identical guard) — with verification wanted but SMTP incomplete, the mutation created a verification request and fired a SendEmail guaranteed to fail, while still telling the caller a magic link was sent.
SCIM/group webhook events were unregisterable. IsValidWebhookEventName's allow-list was never updated when the SCIM provisioning-lifecycle webhooks (user.provisioned, user.deprovisioned, user.scim_updated, group.created, group.updated, group.deleted) shipped — an admin could never register a webhook for any of them, even though the SCIM service fired them correctly.
Verified via go build/go vet/make lint (clean), make test (SQLite, clean), and live round-trip verification against a locally built image for the pagination change across GraphQL, REST, and gRPC.
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.
2.4.0-rc.8
What's Changed
- fix(saml): resume SP-initiated IdP login after login, unblock ACS auto-submit by @lakhansamani in #713
Full Changelog: 2.4.0-rc.7...2.4.0-rc.8
2.4.0-rc.7
Security
MFA session-purpose scoping. Several MFA-adjacent flows shared one bare "does an MFA session exist for this user" check without distinguishing why that session was minted, letting a session obtained for one purpose be redeemed for a stronger one:
- Added a distinct
password_resetMFA-session purpose (alongside the existingverified/challenge), minted only byForgotPassword's mobile OTP leg. VerifyOTPno longer accepts apassword_resetsession — closes a path where a forgot-password OTP session could be redeemed for a full access token instead of only a password change.ResetPasswordnow requires thepassword_resetpurpose specifically — averified/challengesession from an unrelated login/signup flow can no longer complete a password change.EmailOTPMFASetup/SMSOTPMFASetupandWebauthnLoginOptionsnow require averified(or, for the webauthn login-alternative flow,verified/challenge) session — apassword_resetsession can no longer be used to enroll a brand-new MFA factor or enumerate an account's passkey credential IDs.refresh_tokenrequests now verify a presented client secret when one is sent (previously ignored for this grant), rejecting a wrong secret instead of authenticating onclient_idalone.signupand the new-user branch ofmagic_link_loginnow explicitly reject a client-requested protected role, instead of relying onRoles/ProtectedRolesstaying disjoint by operator convention (oauth_callbackalready defended this way).
OAuth token endpoint: storage errors no longer misreported as invalid credentials. A transient storage failure during client lookup (e.g. SQLITE_BUSY under concurrent load, a network blip to an external DB) was previously indistinguishable from "no such client," causing /oauth/token to intermittently return 400 invalid_client for a real, correctly-credentialed client under load.
- All 6 storage backends'
GetClientByClientIDnow return(nil, nil)for a genuinely absent client, never a wrapped driver error — codified as an explicit contract onstorage.Provider. - A real storage error now surfaces as a new
503 temporarily_unavailableresponse instead of400 invalid_client— correctly signaling a retryable failure instead of permanently-wrong credentials, and no longer counted as a security event. - Verified live: reproduced real SQLite contention under concurrent load and confirmed zero misleading
400s post-fix.
Admin _update_user: MFA flag stuck at nil on first explicit disable. Fixed a "did the flag change" check that compared false != false when a user's IsMultiFactorAuthEnabled had never been set, silently skipping the write and leaving the flag nil instead of the requested false.
Verified via make test (SQLite), make test-all-db (all 7 backing databases), make smoke (GraphQL + REST + gRPC + MCP end-to-end), and make lint — all clean.
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.
2.4.0-rc.6
Security
Critical: signup could bypass MFA enforcement entirely. SignUpRequest.is_multi_factor_auth_enabled — a field intended only for the authenticated admin _update_user override — was also honored on the public, unauthenticated signup mutation/RPC, letting a caller decide whether MFA applied to the account they were creating. This was confirmed live and reproducible in 2.4.0-rc.0 through rc.5:
- REST (
/v1/signup) and gRPC (Signup) were affected on every signup, unconditionally, with no attacker action needed. The proto field was a barebool(notoptional bool), so it always carried a value on the wire (defaulting tofalse); the gRPC handler forwarded that value straight through, silently disabling MFA for every account created via REST or gRPC regardless of server config. - GraphQL was exploitable by any caller explicitly passing
is_multi_factor_auth_enabled: false. - The flag persisted on the created user row, so every subsequent login for an affected account skipped MFA too, not just the initial signup response.
Fix, in two parts:
SignUp()no longer honors this field at all — new accounts always defer to the server's own--enable-*-mfa/--enforce-mfaconfig.- Breaking change: the field is removed entirely from
SignUpRequestin the GraphQL schema and proto (field 15reserved, not reused) — the bypass can no longer even be expressed in a request, on any transport. REST is grpc-gateway-generated from the same proto, so this one change covers GraphQL, REST, and gRPC together.
Nothing legitimate depended on this: the built-in login UI (web/app) never sent this field at signup. Its one real use — the authenticated admin _update_user path (web/dashboard's Users page) toggling MFA for an existing user — is untouched. The existing skip_mfa_setup mutation remains the correct, safely-guarded way for a user to decline an offered MFA setup (requires proof of the first factor via the MFA session cookie, and refuses when --enforce-mfa is set).
Action required: any client (custom integration, SDK, or hand-rolled REST/gRPC call) that sets is_multi_factor_auth_enabled on signup must remove it — the field no longer exists on the wire. If you rely on a generated client from the proto/GraphQL schema, regenerate it against this version.
Verified via make test (SQLite), make test-all-db (all 7 backing databases), make smoke (GraphQL + REST + gRPC + MCP end-to-end), and make lint/make proto-lint — all clean.
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.
2.4.0-rc.5
Highlights
Observability hardening across auth, FGA, and OAuth 2.1 rejection paths (#709) — a systematic pass closing silent failure/rejection points found by auditing every code path touched by the recent SCIM/FGA/SAML/OAuth-hardening/async work, verified through independent security, Go-idiom, Prometheus, and principal-engineer review passes before merge.
- OAuth 2.1/RFC 8707 rejection branches in
/authorizeand/oauth/token(repeated/invalidresourceindicator, unsupported JARrequest/request_uri, missingresponse_type, PKCE-plain and bare-token response_type under--oauth21-strict, token-exchange resource mismatch) now log and — for the genuinely attack-shaped rejections — emit aauthorizer_security_events_totalsample, instead of failing silently. - SCIM group provisioning's OpenFGA tuple writes/deletes are now logged on failure instead of a discarded error; FGA engine instrumentation (tuple writes/deletes/reads, list users, expand, write model, reset) moved down into the OpenFGA engine wrapper itself so every caller — including SCIM, which had none — gets it for free.
- Three previously-uninstrumented FGA decision call sites now report to
authorizer_fga_checks_total/authorizer_fga_check_duration_seconds: session/tokenrequired_relationsgating, SAML group assertion, and JWT role derivation. asyncutil.Go's panic recovery now captures a stack trace and increments a newauthorizer_panics_recovered_totalcounter — the single chokepoint every fire-and-forget background goroutine in the app routes through.- The gRPC auth interceptor's admin public-bypass tighten (a mis-annotated admin RPC still getting correctly denied) and rejected
X-Authorizer-URLheaders are now logged. - OTP/TOTP lockout logs are now
Warn-level with a security-event metric, rebuilt from a fresh logger carrying only the opaqueuser_id(not raw email/phone) to avoid promoting PII into logs that actually emit in production. - Cascade-delete transaction failures (
DeleteOrganization,DeleteClient,DeleteWebhook,DeleteUser) now log on rollback instead of failing silently. - Fixed an unrelated pre-existing data race in
AuthorizeHandlerwhere the request logger was captured outside its closure and mutated inside it across concurrent requests.
Also in this RC (since 2.4.0-rc.4):
- SCIM: filter operators, provisioning webhooks, and full user
PATCHsupport (#705). - Fire-and-forget background work (email/SMS sends, webhooks, audit logs) is now tracked and drained on graceful shutdown, with panics recovered instead of crashing the process (#696).
- Trusted base URL (
--url) closing a host-header-spoofing class, and per-user email/SMS OTP brute-force lockout (#698). UpdateUsersempty-ids guard enforced across all 13 database providers, and cascade deletes wrapped in transactions (#699).- gRPC admin service returns properly-typed errors instead of generic Internal, and the public-method bypass is scoped tightly (#700).
- Guarded two unguarded type assertions on untrusted input that could crash the process (#701).
- Dashboard: verified organization domains UI (#707), CSV import and InputField crash fixes, WCAG label association fixes (#702, #703, #708).
- New end-to-end regression test for signup webhook firing (#706).
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.
2.4.0-rc.4
Highlights
OAuth 2.1 authorization-server hardening for MCP (#693, closes #516) — Authorizer's /oauth/token and /authorize now support what the current MCP authorization spec actually asks of an AS, scoped against the real spec text rather than an older draft.
- Refresh-token reuse detection (OAuth 2.1 §6.1 / RFC 9700 §4.14.2), scoped to the specific compromised token's rotation lineage (
family_id) rather than the whole user — replaying one retired token can no longer force-log-out a user's other sessions. Includes a short grace window so a benign multi-tab/retry double-refresh doesn't self-revoke, and a guard against a transient session-store lookup blip being mistaken for a breach. - RFC 8707 resource indicators on the primary
authorization_codeflow (/authorize+/oauth/token), binding the issued access token'saudto the target resource server — previously only available on the token-exchange/delegation path. GET /.well-known/oauth-authorization-server(RFC 8414) metadata alias.- New opt-in
--oauth2-1-strictflag (default off, zero behavior change unless enabled): rejects the implicit grant and all front-channel-token response_type hybrids, and requires PKCE S256 overplain. - RFC 7591 Dynamic Client Registration and RFC 9728 Protected Resource Metadata deliberately not added — the current MCP spec downgrades DCR to optional, and RFC 9728 is the MCP resource server's responsibility, not the authorization server's (see
examples/with-mcp).
SCIM group provisioning → OpenFGA roles → SAML group assertions (#694, closes #692 and the pending piece of #512) — bridges SCIM directory sync, fine-grained authorization, and SAML SSO, previously three disconnected features.
- SCIM Groups (
/scim/v2/Groups, full CRUD + PATCH) across all 13 storage backends — the PATCH parser handles real-world Okta/Entra deviations from the RFC, not just the RFC's own examples. - Group membership and group→role bindings live as OpenFGA tuples (
group:<org>/<id>#member,role:<org>/<name>#assignee), reusing the engine's existing userset-as-subject support. - The SAML Identity Provider (from 2.4.0-rc.3) can now assert group membership as a SAML attribute, and inbound org SSO/SAML logins project FGA-derived roles into the JWT
rolesclaim — both gated by a two-layer cross-tenant containment check (FGA object namespace + the authoritative row) so a group/role in one org can never leak into another org's assertion or token. - displayName matching is case-insensitive and Unicode-consistent across all 6 database engines (SQL/Arango/Couchbase/Cassandra/DynamoDB/Mongo agree on the same fold, rather than each engine's own native, mutually-inconsistent case folding).
Also new in the examples repo: with-a2a-agent-card, a spec-conformant A2A (Agent2Agent) v1.0 Agent Card backed by Authorizer as the OAuth2 authorization server.
Both features went through multiple rounds of independent adversarial security review (family-scoped revocation redesign, cross-tenant containment verification, storage-provider-specific correctness passes) before merge.
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.
2.4.0-rc.3
Highlights
SAML 2.0 Identity Provider role (#691) — Authorizer can now act as a SAML IdP, issuing signed assertions to downstream SaaS Service Providers (Zendesk, Notion, Tableau, …), the inverse of the existing SP role.
- Per-org IdP metadata, SP-initiated and IdP-initiated SSO, RSA-SHA256 signed assertions with configurable NameID + attribute mapping.
- X.509 signing keys with overlap-window rotation (
current→active→retired); metadata publishes all active certs so SPs keep validating across a rotation. - New
SAMLServiceProvider+SAMLIDPKeystorage entities across all 6 DB providers; private keys AES-256-GCM encrypted at rest. - Admin surface on GraphQL and gRPC/proto (registered-SP CRUD, cert rotation, retire, SP-metadata XML import) + dashboard UI.
- Security: strict ACS/entity-ID binding, audience isolation, org-membership + verified-email gating on issuance (no cross-tenant identity), no-XXE metadata import, IDOR-safe admin auth. Two independent security-engineer audits; unit + end-to-end tests through the real handlers.
This is a release candidate (pre-release). Run make test-all-db cross-DB validation before promoting to a stable release.