Releases: doorkeeper-gem/doorkeeper
Release list
v5.9.6
- Reject requests that present more than one client identity (e.g. an
Authorization: Basicheader for one client and aclient_idparameter naming another) with aninvalid_requesterror, instead of authenticating the first extracted identity and silently discarding the other one. Aclient_idsent alongside another authentication method keeps working when it identifies the same client (RFC 7521 §4.2). Like the RFC 6749 §2.3 check released in 5.9.5, this validation does not apply whenclient_credentialsis configured with a callable extractor, since the credentials the remaining extractors would return are never evaluated — theclient_credentialsoption documents that now.
6.0.0.beta2
Please make sure you read the Upgrade guides
and changelog below before the update since this version includes breaking changes.
- [#1865] Revoke the token issued for an authorization code when the code is exchanged more than once, per RFC 6749 §4.1.2 / §10.5. Active when the
oauth_access_grants.access_token_idcolumn exists: new installs get it from the generated migration, existing apps can add it withrails generate doorkeeper:grant_reuse_revocation. Closes [#1713]. - [#1871] [BREAKING]
redirect_uriis now compared to the registered redirect URIs with the simple string comparison required by RFC 6749 §3.1.2.3 (the RFC 8252 §7.3 loopback port exception is kept). Clients relying on the previous lenient matching must send the exact registered URI, closes [#1718]. - [#1874] Fix
force_pkcerequiring acode_challengefrom response types that never issue an authorization code (e.g.token, or an OIDC extension'sid_token/id_token token). PKCE (RFC 7636) protects the authorization code exchange, so for code-less response types there is no token-endpoint step where acode_verifiercould ever be checked — such requests were rejected over a parameter that cannot be validated.force_pkcenow only enforces the challenge for response types that issue a code (codeand code-carrying hybrid types such ascode id_token). - [#1876] Fix: reject a non-string
scopeparameter (e.g.scope[a]=b, which Rack parses into a Hash) withinvalid_request(RFC 6749 §3.3) instead of an unhandled 500.Scopes.from_stringnow raisesErrors::InvalidScopeParameterfor a non-string argument — turned intoinvalid_requestby the token endpoint'srescue_from, so every grant type is covered — and the authorization endpoint rejects it up front in pre-authorization validation. The crash was reachable unauthenticated, before client authentication. - [#1877] Fix: let the
noneclient authentication strategy match a request whoseAuthorizationheader carries a Bearer token. A bearer credential authorizes access to the endpoint itself (e.g. a bearer-protected introspection endpoint per RFC 7662 §2.1, or a revocation request) rather than authenticating the client, so it must not suppress the public-clientnonestrategy when the client identifies itself with a bodyclient_id. Any other non-blankAuthorizationvalue — Basic, or aBearerwith no token — is still treated as header-based client authentication and continues to bypassnone. The 6.0.0.beta1 workaround of dropping theAuthorizationheader from such a request is no longer needed. - [#1878] Fix: the loopback redirect URI exception (RFC 8252 §7.3) now varies by port only, not by userinfo. The port was cleared with
URI#port=, which on Ruby >= 4.0 also drops the userinfo, sohttp://attacker@127.0.0.1/cbmatched a registeredhttp://127.0.0.1/cb. The match is now made component-by-component. The destination host is always the loopback interface, so this was not a cross-origin open redirect, and non-loopback hosts were never affected. - [#1879] Fix: with
reuse_access_tokenenabled, replaying an authorization code no longer revokes an access token that another grant still shares. The single-use revocation added in [#1865] followed the grant'saccess_token_id, which a reused token shares across grants, so a replay could collaterally revoke a token another valid session still held. The revocation now skips a token referenced by another grant and only reaches one unique to the replayed code; a token unique to the code is still revoked as before. - [#1881] Fix:
Doorkeeper::ApplicationsControllerno longer 500s oncreate/update/destroyinapi_onlymode, whereActionController::APIprovides noflash. Confiningflashto the HTML path is not sufficient on its own, because a client that does not name JSON explicitly still negotiates its way into that path — an absentAcceptheader and a browser-like list such asapplication/json, text/plain, */*both resolve totext/html, and a bare*/*resolves to the first registered format — soapi_onlymode now pins the response format to JSON. - [#1883] Internal: merge
CHANGELOG.mdwith git'suniondriver, so two pull requests that each add an entry no longer conflict on the line above "Please add here". - [#1884] Fix:
/oauth/introspectand/oauth/revokeextend the token lookup across both token types when the lookup bytoken_type_hintfinds nothing (RFC 7662 §2.1 / RFC 7009 §2.1), so a wrong hint no longer hides a token the server knows about ([#1882]) - [#1885] Index
oauth_access_grants.access_token_idin the migration templates: since [#1879] the code-replay revocation filters access grants by that column, the "never used to filter queries" premise behindindex: falseno longer holds. - [#1886] Add support for Resource Indicators for OAuth 2.0 (RFC 8707). Clients can include a
resourceparameter in authorization and token requests to indicate the target protected resource(s). The authorization server validates resource URIs, enforces audience restriction on tokens, and includesaudin introspection responses. Enable by configuringresource_indicator_validatorwith a callable. Requires newresourcecolumns on access grants and tokens — runrails generate doorkeeper:resource_indicatorsto add the migration. - [#1887] [test] Pin that a requested non-default scope reaches the authorization grant and the exchanged token, and that the authorization strategy shares the controller's pre-authorization — the mismatch reported in [#1576] does not reproduce. Test-only change, closes [#1576].
- [#1888] Document custom grant flow registration (
Doorkeeper::GrantFlow.register) in the README with a SAML 2.0 bearer assertion (RFC 7522) walkthrough, and pin URN-shaped custom grant types with an end-to-end request spec. Docs/test-only change, closes [#764]. - [#1890] [test] Pin that the authorization endpoint answers
invalid_redirect_urifor a client registered without a redirect URI (allow_blank_redirect_uri), whether or not the request supplies one — the behavior required by RFC 6749 §3.1.2.3. Test-only change, closes [#1682]. - [#1898] Fix: with
reuse_access_tokenenabled, theclient_credentialsgrant no longer reuses a token whoseresourcediffers from the one requested (RFC 8707), so the audience restriction the client asked for is always applied. Follow-up to [#1886]. - [#1899] Document the client authentication methods registry (
Doorkeeper::ClientAuthentication.register) in the README with a walkthrough for registering a custom method, and pin it with an end-to-end request spec. Docs/test-only change, closes [#1894]. - [#1891] Fix: an authorization request carrying a
redirect_urifor a client registered without one (redirect_uriisnilunderallow_blank_redirect_uri) now answersinvalid_redirect_uriinstead of crashing with an unhandled 500. - [#1896] Add an opt-in
private_key_jwtclient authentication method (RFC 7523 / OIDC Core §9, requires thejwtgem >= 2.7) that verifies assertions against the client's published public keys —jwks/jwks_uriattributes you define on your Application model, the latter fetched with an SSRF-hardened HTTP client. The jti replay guard and the fetched-JWKS cache are process-local by default and can be replaced with shared stores via theprivate_key_jwt_replay_guard/private_key_jwt_jwks_cacheconfig options. Part of [#1875]. - [#1901] [test] Pin the answered behaviors behind [#984], [#1554], [#1600], [#1663], [#1759] and [#1787] with regression specs — the built-in client authentication methods, the unmodified echo of long
statevalues, DB persistence with custom token generators, refresh token rotation/expiry semantics and the introspection asymmetry. Test-only change, closes those issues along with [#1291], [#1756] and [#1764]. - [#1902] Fix: requests that omit
scopenow compute the same default scopes at the authorization and token endpoints (Scopes#common, symmetric). With dynamic scopes enabled, a scope pattern in eitherdefault_scopesor the application's scopes grants the matching concrete scope at both endpoints, closes [#1889]. - [#1903] Fix:
revoke_previous_client_credentials_tokenno longer revokes a client's live access token issued for a differentresource([#1886]), so a client can keep one audience-restricted token per resource server. - [#1905] [test] Cover
private_key_jwtclient authentication on theclient_credentialsgrant, the one flow where an assertion is the client's only credential end to end. Test-only change.
v5.9.5
- [#1901] Reject requests that authenticate the client with more than one method (RFC 6749 §2.3) with an
invalid_requesterror, instead of silently authenticating with the first method that matched and discarding the other credentials. - [#1853] Fix
reuse_access_tokenreusing a token that was created withcustom_access_token_attributesvalues when the new request doesn't specify any custom attributes. Such requests now only match tokens without custom attributes.
v6.0.0.beta1
Please make sure you read the Upgrade guides and changelog below before the update since this version includes breaking changes.
- [#1816] Fix: redirect
unauthorized_clienterrors per RFC 6749 Section 4.1.2.1; validate redirect_uri beforeclient_supports_grant_flowto prevent open redirect - [#1867] Fix: use
access_deniedinstead ofinvalid_clientforresource_owner_authorize_for_clientvalidation per RFC 6749 Section 4.1.2.1.invalid_clientis a token endpoint error (Section 5.2), not an authorization endpoint error. - [#1838] Add OAuth 2.0 Authorization Server Metadata endpoint (RFC 8414) served at
/.well-known/oauth-authorization-server. The response is built from your Doorkeeper configuration and advertises the authorization, token, revocation and (when token introspection is enabled) introspection endpoints, supported scopes, response/grant types and PKCE code challenge methods. Two new config options are available:issuer(defaults to the request base URL) andcustom_metadata(a Hash merged into the response, e.g. to advertise an OIDCuserinfo_endpoint). The controller/response use the RFC 8414 "Metadata" naming so they don't collide with a future OpenID Connect Discovery (.well-known/openid-configuration) implementation. Endpoints disabled throughskip_controllersare omitted from the response instead of raising a route-generation error. - [#1839] Send client credentials in the request body (not the query string) in the token endpoint specs, per RFC 6749 §2.3.1. Test-only change: the
*_endpoint_urlhelpers now return a path plus a matching*_endpoint_paramsbuilder so flow specs post credentials through the body. - [#1840] Introduce a pluggable client authentication registry (RFC 6749 §2.3).
- New
client_authenticationconfig option declares which methods are accepted and in which order. Built-in strategies:client_secret_basic,client_secret_postandnone. - Custom strategies can be registered with
Doorkeeper::ClientAuthentication.register. - [BREAKING] Client credentials are no longer read from the query string — send them in the request body or via HTTP Basic. This applies to every endpoint that authenticates clients: token, revocation and introspection.
- [BREAKING] The
nonestrategy rejects requests that carry a non-blankAuthorizationheader. A common casualty is a public client POSTing to/oauth/revokewith a bodyclient_idwhile still sending itsAuthorization: Bearer <token>header — drop the header from that request. - [BREAKING] The
Doorkeeper::OAuth::Client::Credentialsclass methods.from_request,.from_basicand.from_paramsare removed — extensions extracting credentials from a request should register a client authentication method instead. - Deprecated: the
client_credentialsoption — useclient_authenticationinstead. - Deprecated: the
Doorkeeper::OAuth::Client::Credentialsconstant itself — it remains as an alias ofDoorkeeper::ClientAuthentication::Credentials(the plain uid/secret struct). - See the Upgrade Guide for detailed breaking changes, deprecations and migration steps.
- New
- [#1841] Reject requests that use more than one client authentication method (RFC 6749 §2.3: "The client MUST NOT use more than one authentication method in each request") with an
invalid_requesterror instead of silently using the first match. The request payload is validated against every registered method before the configured one is selected, so a client sending e.g. both Basic and body credentials is rejected even when only one of those methods is enabled. This applies to every endpoint that authenticates clients — token, revocation and introspection. Only real authentication mechanisms count: a bareclient_id(thenonemethod) is not a mechanism of its own, and deprecatedclient_credentialscallable extractors never count towards the limit (they keep the historical first-extractor-wins selection). - [#1842] [BREAKING]
force_pkcenow requires PKCE for all clients, including confidential ones, in line with the OAuth 2.0 Security BCP (RFC 9700) and OAuth 2.1. Previously confidential clients were exempt. If you enableforce_pkceand have confidential clients that do not yet send acode_challenge/code_verifier, their authorization requests will start to be rejected. - [#1845] Fix
NameErrorwhen the config option DSL (Doorkeeper::Config::Option) is extended into a class that does not defineself.builder_class. The guard raisedDoorkeeper::MissingConfigurationBuilderClass, a constant that was never defined, so callers sawuninitialized constantinstead of the intended message. The error is now defined asDoorkeeper::Errors::MissingConfigurationBuilderClass(aDoorkeeperError) and referenced correctly. - [#1846] Document and pin with regression specs that a
scopeparameter sent to the token endpoint is ignored for theauthorization_codegrant (RFC 6749 §4.1.3 does not define one): the access token always inherits the scopes of the authorization grant, and the response reports the actual grantedscope. No behavior change. - [#1847] Fix
Doorkeeper.config.enabled_grant_flows(andcalculate_grant_flows) not listing therefresh_tokengrant flow whenuse_refresh_tokenis configured, so consumers (e.g. RFC 8414 metadata) no longer need to append it manually — the built-in metadata endpoint now relies on this too. The flow is no longer duplicated intoken_grant_flowswhenrefresh_tokenis also listed ingrant_flowsexplicitly, a configuration-time warning is logged whengrant_flowsenablesrefresh_tokenwithoutuse_refresh_token(no refresh tokens would ever be issued), and the initializer template documents the flow. - [#1848] Derive
token_endpoint_auth_methods_supportedin the RFC 8414 metadata response from the effective client authentication configuration (including a deprecatedclient_credentials-only setup) instead of hardcoding the default methods. Servers that customizeclient_authentication(including extension-registered methods likeprivate_key_jwt) now see their metadata reflect what the server actually accepts; unregistered names are not advertised. - [#1849] Support RFC 9207 (Authorization Server Issuer Identification): when
issueris configured, theissparameter is added to the authorization responses redirected back to the client (successful and error responses alike) andauthorization_response_iss_parameter_supportedis advertised in the server metadata. Clients that parse the authorization redirect will start seeing the newissparameter. A configuredissuerthat is not RFC-compliant (not an https URL, or containing a query/fragment) now logs a warning at boot, as does a path-bearing issuer, which RFC 8414 clients would not discover through Doorkeeper's root-only well-known metadata route. - [#1850] Fix
reuse_access_tokenreturning a refresh token that doesn't match the request: token reuse now requires the candidate's refresh token presence to match what the request asks for, in both directions. A request that expects a refresh token (e.g.use_refresh_tokenenabled) no longer reuses a token issued without one (previously the refresh token was silently omitted), and a request that does not expect one no longer reuses a token that carries one (previously an unrequested refresh token was returned, reachable via a per-requestrefresh_token_enabledcallable). The requirement participates in the token matching itself, so an older matching token that satisfies it is still reused; a fresh token is created only when none does. API change for ORM extensions:matching_token_forandfind_matching_tokennow take an optional block (&filter) that a token must satisfy to count as a match — extensions overriding either method must accept the block and honor it (accept and yield). - [#1851] Fix duplicate query parameter in the authorization callback when a client's registered
redirect_urialready contains a parameter with the same name as a response parameter (e.g.state). The redirect query was merged with string keys on one side and symbol keys on the other, so a collision emitted the parameter twice (?state=fixed&code=...&state=user); the response parameter now overrides the registered one and appears exactly once. A blank response parameter (e.g. nostatesent with the request) leaves the registered parameter untouched, per RFC 6749 §3.1.2. - [#1852] Fix the
pkce_code_challenge_methodsconfig validator using line anchors (^/$) instead of string anchors (\A/\z), so a multi-line value such as"plain\ngarbage"passed validation and was retained as a (never-matching) challenge method instead of being rejected and reset to the default. - [#1853] Fix
reuse_access_tokenreusing a token that was created withcustom_access_token_attributesvalues when the new request doesn't specify any custom attributes. Such requests now only match tokens without custom attributes. - [#1854] Fix the RFC 8414 metadata endpoint raising
ActionController::UrlGenerationError(HTTP 500) whenuse_doorkeeperconfigures a custom controller whose namespace depth differs fromdoorkeeper/metadata(e.g.controllers tokens: "custom_tokens"). - [#1855] Perform the fallback secret upgrade-on-access write (plain → hashed token or application secret) through the primary database role, so
enable_multiple_database_rolessetups no longer attempt the write on a read replica when the lookup happens in a request routed to the reading role. - [#1857] Pin with regression specs that a
+between scopes in a form-encoded token request is decoded as a space (soscope=public+writerefreshes fine), while a percent-encoded literal+(%2B) name...
v5.9.3
- [#1834] Fix default
allow_token_introspectionreturningfalsewhen a customapplication_classis configured. The default proc compared application objects with==, which fails when the authorized client and the introspected token's application are resolved as different classes (e.g. a baseDoorkeeper::Applicationvs. a configured subclass) even though they reference the same record. It now compares application ids instead. - [#1832] Fix confusing
belongs_to :ownerside effect:Doorkeeper::Models::Ownershipis now included only whenenable_application_owner?is set (read at include time), so models no longer expose a misleadingownerassociation/reflection when the application owner feature is disabled and the schema lacks the owner columns.
v5.9.2
- [#1822][#1823][#1825] Update Rubocop config, auto-corrections and codebase cleanup.
- [#1830] Fix
NameError: uninitialized constant ApplicationRecordonrails db:seed(and other non-eager-loading flows) caused byon_load(:active_record)firing re-entrantly duringApplicationRecordautoload. The orm hooks no longer depend onActiveSupport.on_load(:active_record); model concerns (Ownership,PolymorphicResourceOwner::ForAccessGrant,PolymorphicResourceOwner::ForAccessToken) are now wired up from eachMixins::*includedblock, which fires at parent-class autoload time — afterDoorkeeper.configurehas applied user settings and without re-entering the AR load chain.- Upgrade note: fully custom model classes that don't include
Doorkeeper::Orm::ActiveRecord::Mixins::{Application,AccessToken,AccessGrant}will no longer auto-receiveOwnership/PolymorphicResourceOwnerconcerns (previously injected byrun_orm_hooksvia the configured class name). Either inherit from the Doorkeeper default model, include the correspondingMixins::*module, orincludethe concerns directly.
- Upgrade note: fully custom model classes that don't include
v5.9.1
-
[#1781] Honor
handle_auth_errors :raiseinAuthorizationsController#authorize_response -
[#1795] Fix: detailed error 'insufficient_scope' in protected resources 403s
-
[#1797] Fix
doorkeeper:db:cleanuprake task failure on PostgreSQL -
[#1800] Set
@grant_typeinClientCredentialsRequestandRefreshTokenRequestconstructors sorequest.grant_typereturns
the correct value in hooks likebefore_successful_strategy_response. -
[#1802] Fix
filter_parametersnot applied whenDoorkeeper.configureis called inside to_prepare. -
[#1804] Use
ActiveSupport.on_load(:active_record)in ORM hooks to prevent loading ActiveRecord models too early -
[#1806] Fix token revocation bypass for public clients (RFC 7009)
-
[#1815] Expose
current_resource_owneras a view helper inDoorkeeper::ApplicationController. -
[#1818] Fix token introspection returning
exp: 0for non-expiring tokens. -
[#1784] Remove hardcoded colons from view templates, move punctuation to i18n translation strings.
[IMPORTANT]: if you have customized Doorkeeper views (
authorizations/new,authorizations/show,
applications/show) or overridden the defaulten.ymltranslations, you may need to update them.
Colons are no longer hardcoded in the views — they are now part of the translation strings.
Update the doorkeeper-i18n gem to get the
updated translations for all locales. -
[#1820] Remove dead wildcard presence check in
Scopes#dynamic_scope_match?(internal cleanup, no behavior change). -
[#1822] Update Rubocop config, auto-corrections.
-
[#1823] Update Rubocop config, part 2.
-
[#1825] Update Rubocop config, part 3.
-
[#1821] Fix noisy
Could not find command "no_previous_refresh_token_column?"Thor output during the
PreviousRefreshTokenGeneratorspec by stubbing the underlying DB column check instead of the generator's
private method (test-only change).
v.5.9.0
- [#1791] Add support for Rails read replicas with automatic role switching via
enable_multiple_database_rolesconfiguration option - [#1792] Consider expires_in when clear expired tokens with StaleRecordsCleaner.
- [#1790] Fix race condition in refresh token revocation check by moving InvalidGrantReuse check inside the lock block
- [#1788] Fix regex for basic auth to be case-insensitive
- [#1775] Fix Applications Secret Not Null Constraint generator
- [#1779] Only lock previous access token model when creating a new token from its refresh token if revoke_previous_refresh_token_on_use is false
- [#1778] Ensure that token revocation is idempotent by checking that that token has not already been revoked before revoking.
v5.8.2
- [#1755] Fix the error message for force_pkce
- [#1761] Memoize authentication failure
- [#1762] Allow missing client to trigger invalid client error when force_pkce is enabled
- [#1767] Make sure error handling happens on a controller level opposed to action level to account for the controller being extended