Skip to content

Releases: authorizerdev/authorizer

2.4.0-rc.12

2.4.0-rc.12 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 28 Jul 09:43
6718629

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

2.4.0-rc.11 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 28 Jul 08:30
0ac0545

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

2.4.0-rc.10 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 24 Jul 11:56
0c228d6

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

2.4.0-rc.9 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 23 Jul 22:44
9c0ecac

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, and authorizer-py all 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 email scope), the raw numeric sub was stored directly in user.Email instead of a synthesized address — not a duplicate-account risk, but a non-email string reaching anything that assumes user.Email is 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

2.4.0-rc.8 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 23 Jul 05:41
99b419b

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

2.4.0-rc.7 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 22 Jul 17:02
ca628ce

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_reset MFA-session purpose (alongside the existing verified/challenge), minted only by ForgotPassword's mobile OTP leg.
  • VerifyOTP no longer accepts a password_reset session — closes a path where a forgot-password OTP session could be redeemed for a full access token instead of only a password change.
  • ResetPassword now requires the password_reset purpose specifically — a verified/challenge session from an unrelated login/signup flow can no longer complete a password change.
  • EmailOTPMFASetup/SMSOTPMFASetup and WebauthnLoginOptions now require a verified (or, for the webauthn login-alternative flow, verified/challenge) session — a password_reset session can no longer be used to enroll a brand-new MFA factor or enumerate an account's passkey credential IDs.
  • refresh_token requests now verify a presented client secret when one is sent (previously ignored for this grant), rejecting a wrong secret instead of authenticating on client_id alone.
  • signup and the new-user branch of magic_link_login now explicitly reject a client-requested protected role, instead of relying on Roles/ProtectedRoles staying disjoint by operator convention (oauth_callback already 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' GetClientByClientID now return (nil, nil) for a genuinely absent client, never a wrapped driver error — codified as an explicit contract on storage.Provider.
  • A real storage error now surfaces as a new 503 temporarily_unavailable response instead of 400 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

2.4.0-rc.6 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 22 Jul 12:31
baf08fe

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 bare bool (not optional bool), so it always carried a value on the wire (defaulting to false); 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:

  1. SignUp() no longer honors this field at all — new accounts always defer to the server's own --enable-*-mfa/--enforce-mfa config.
  2. Breaking change: the field is removed entirely from SignUpRequest in the GraphQL schema and proto (field 15 reserved, 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

2.4.0-rc.5 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 22 Jul 10:57
ed46981

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 /authorize and /oauth/token (repeated/invalid resource indicator, unsupported JAR request/request_uri, missing response_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 a authorizer_security_events_total sample, 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/token required_relations gating, SAML group assertion, and JWT role derivation.
  • asyncutil.Go's panic recovery now captures a stack trace and increments a new authorizer_panics_recovered_total counter — 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-URL headers are now logged.
  • OTP/TOTP lockout logs are now Warn-level with a security-event metric, rebuilt from a fresh logger carrying only the opaque user_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 AuthorizeHandler where 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 PATCH support (#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).
  • UpdateUsers empty-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

2.4.0-rc.4 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 21 Jul 15:15
3b35919

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_code flow (/authorize + /oauth/token), binding the issued access token's aud to 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-strict flag (default off, zero behavior change unless enabled): rejects the implicit grant and all front-channel-token response_type hybrids, and requires PKCE S256 over plain.
  • 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 roles claim — 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

2.4.0-rc.3 Pre-release
Pre-release

Choose a tag to compare

@lakhansamani lakhansamani released this 20 Jul 11:59
b26401d

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 (currentactiveretired); metadata publishes all active certs so SPs keep validating across a rotation.
  • New SAMLServiceProvider + SAMLIDPKey storage 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.