New Ory Agent Security is now live! Claim your complimentary test drive. Get Started!

Skip to main content

Changelog for Ory Hydra OEL

26.3.18

This version contains only minor changes and improvements such as dependency updates.


v26.3.17​

Loopback redirect URIs now require a matching scheme​

OAuth 2.0 loopback redirects (RFC 8252 section 7.3) with a dynamic port are now accepted only when the registered redirect URI also uses the http scheme. Previously, a client registered with an https loopback redirect URI, such as https://127.0.0.1/callback, also accepted plain http redirects to any port on that host.

Breaking changes​

After upgrading, OAuth2 authorization requests fail redirect URI validation if the client registers only an https loopback URI but the native app requests an http loopback URI with a dynamic port.

To restore the flow, update the OAuth2 client's redirect_uris so the registered URI uses the same scheme, loopback address, path, and query as the URI requested by the app. The port may remain dynamic. For example, if the app requests http://127.0.0.1:<port>/callback, register http://127.0.0.1/callback. For IPv6, register http://[::1]/callback.


26.3.16

This version contains only minor changes and improvements such as dependency updates.


v26.3.15​

Oathkeeper adopts shared HTTP metrics and adds per-rule access decisions​

Oathkeeper uses the shared Ory HTTP metric schema. It also exposes ory_oathkeeper_access_decisions_total, which reports allowed requests, authentication failures, authorization failures, and pipeline errors by rule ID. Access decisions are independent of login redirects and upstream response codes.

Breaking changes​

For Oathkeeper dashboards and alerts, replace service with app and status_code with code. The request label is replaced by endpoint, which contains API router patterns such as /rules/{param} and /decisions/ and is empty for proxy traffic. The app values remain oathkeeper-api and oathkeeper-proxy. HTTP metrics also include version, hash, and buildTime. Metric names retain the configured prefix. Health requests are now included.

serve.prometheus.hide_request_paths and serve.prometheus.collapse_request_paths are deprecated and have no effect. Update path filters to use router patterns or the new per-rule counter.

In Oathkeeper, Kratos, Hydra, and Keto, the requests_statuses_total metric's method labels now follow the same normalization as other shared HTTP metrics: recognized methods use lowercase values, and other methods use other. Oathkeeper applies this normalization to all HTTP metrics.

Unsupported methods use other instead of unknown across all shared HTTP metrics. Update queries that filter on the previous method label.

Require user authorization for verifiable credentials​

POST /credentials issues credentials only to access tokens that represent an authenticated end user and carry both the openid and userinfo_credential_draft_00 scopes. The same requirement applies to nonce priming requests. Both scopes must be granted explicitly; a wildcard grant does not satisfy the requirement.

Breaking changes​

A token that does not carry both scopes receives scope_not_granted with HTTP 403. Scope names are matched exactly, so a scope that differs in case does not satisfy the requirement.

To use this draft endpoint, request both scopes in the authorization flow and grant both during consent. The token endpoint returns c_nonce_draft_00 only for tokens that meet these requirements. Other access token issuance is unaffected.

Device authorization errors are shown on the error page​

An OAuth 2.0 device authorization grant has no redirect URI, so an error at the device verification endpoint had nowhere to go and was written to the browser as a JSON response body. The most common case is a user who denies the request at the login or consent screen: they were left looking at raw JSON.

Those errors are now sent to the error page configured under urls.error, carried in the error and error_description query parameters, the same way an authorization request that has no valid redirect URI already is. A denial arrives there under the error code the rejection named, and the device still receives access_denied on its next poll.

If your error page distinguishes the errors it receives, expect device verification errors on it now. GET /oauth2/device/verify no longer answers any error with a JSON body: every error path redirects to the error page instead. The device polling behavior is unchanged.

A rejection also reads as one sentence now. Account Experience rejects a login or consent request with error_description set to "The request was rejected. The application was not granted access to your account." and no longer sends error_hint, so OAuth 2.0 clients of a redirect-based flow receive that text in their error redirect.

Hydra gains a FIPS 140-3 build​

Ory Enterprise License releases now include a dedicated FIPS build of Hydra (hydra_oel_fips_<version>_<os>_<arch>) next to the standard build. The FIPS build uses the Go Cryptographic Module and starts with GODEBUG=fips140=on by default, so Hydra's cryptography runs through the FIPS 140-3 validated module. The standard build is unchanged as long as it runs without GODEBUG=fips140=on.

A few things need attention before you switch. The FIPS build seals transient payloads with AES-256-GCM instead of XChaCha20-Poly1305, so the two builds cannot read each other's login, consent, device, and logout flows, verifiable credential nonces, unredeemed authorization codes, or admin API page tokens; plan a stop-then-start cutover. hydra serve refuses to start unless secrets.system, secrets.cookie, and secrets.pagination are each set explicitly with their own active secret, and unless oauth2.hashers.algorithm is pbkdf2 — bcrypt hashes client secrets outside the validated module, though client secrets already hashed with it keep verifying. And because AES-GCM draws a random nonce per payload, regular rotation of secrets.system is mandatory in FIPS mode.

For the full picture — obtaining and verifying the build, the required configuration, the rotation budget, and what each cipher changes to — see FIPS 140-3 build.

TLS configuration and unix socket recovery in Hydra, Keto and Kratos​

A configured certificate is now the only switch for TLS in all three services. serve.<endpoint>.tls.enabled is deprecated but still accepted so that an existing configuration keeps loading, but it has no effect. Each service logs a warning at startup naming the key to remove. To serve plain HTTP behind a proxy that terminates TLS, remove the certificate rather than the flag.

Hydra and Kratos also pick up two fixes from the shared serve configuration:

  1. configuring only one half of a certificate pair is now a startup error instead of a listener that silently falls back to plain HTTP
  2. a unix socket left behind by an unclean shutdown is replaced instead of blocking the next start.

Because the three services now configure their endpoints identically, the documentation for it is one page: Serving the APIs. It replaces the separate Hydra and Kratos HTTPS pages, which now redirect to it.


v26.3.14​

Do not skip this release when upgrading without downtime​

This release prepares an optimization to the OAuth 2.0 Device Authorization Grant. A later release completes it.

Deploy this release before that later one. If you run several Ory Hydra instances and upgrade them one at a time, skipping this release can leave a device that finishes signing in mid-upgrade with an access token but no ID token, and no error to explain it.

No action is needed if you run a single instance, or if you accept downtime during upgrades.


v26.3.13​

Hydra no longer generates a self-signed TLS certificate​

When TLS was enabled and no certificate was configured, Hydra generated a self-signed certificate at startup, stored it in the hydra.https-tls JSON Web Key Set, and served it. Hydra now starts only with a certificate you provide. Enabling TLS without one fails at startup with an error naming the settings to set.

What you must do​

You are affected if you set serve.tls.enabled (SERVE_TLS_ENABLED) to true without setting a certificate. Choose one:

  • Configure a certificate and private key, either as file paths (serve.tls.cert.path and serve.tls.key.path) or base64-encoded (serve.tls.cert.base64 and serve.tls.key.base64). Hydra reloads the certificate when the file changes.
  • Disable serve.tls.enabled if a load balancer, ingress controller, or service mesh terminates TLS in front of Hydra. Hydra then serves HTTP.

Deployments that already configure a certificate, and deployments that leave TLS disabled, are unaffected. Once Hydra starts, you can delete the now-unused key set with hydra delete jwks hydra.https-tls.

Why​

The generated certificate could not authenticate Hydra to anything, and generating it caused problems of its own:

  • The only name in it was localhost, with no IP addresses, so no client that verifies hostnames could accept it for a real deployment.
  • It was valid for 31 days and was never renewed. Because it was stored in the database and reused on every restart, any installation older than a month has been serving an expired certificate. Only peers that skip verification, such as load balancers that do not validate backend certificates, kept working — which is why this change can surface as a startup failure on an installation that appeared healthy.
  • It was a certificate authority certificate that could sign certificates. Adding it to a trust store, as our documentation used to suggest, made Hydra's database a trust anchor for any name.
  • Its private key was stored alongside your signing keys and could be read through the admin API.
  • Replicas starting at the same time against a fresh database each generated their own certificate, so nodes behind the same load balancer served different ones. Also, a replica that lost the race writing its certificate back could fail to start.

Generating a certificate nothing can verify is not something Hydra should do on your behalf. A deployment that needs a self-signed certificate is better served by one you create, distribute to the clients that must trust it, and rotate on your own schedule.

Hydra no longer supports the TLS termination allow-list​

The serve.tls.allow_termination_from setting is gone. A configuration file that still sets it fails validation at startup, with an error naming the key. Set through SERVE_TLS_ALLOW_TERMINATION_FROM, it is ignored.

What you must do​

You are affected if you set serve.tls.allow_termination_from in a configuration file. Remove the setting. Nothing else about how Hydra serves traffic changes.

If a load balancer, ingress controller, or service mesh terminates TLS in front of Hydra, leave serve.tls.enabled unset. Hydra then serves HTTP, and the proxy is responsible for HTTPS. Make sure the proxy is the only way to reach Hydra.

Why​

The setting was meant to name the CIDR ranges allowed to reach Hydra over plain HTTP and to require an X-Forwarded-Proto: https header from them. It never took effect. Hydra installed the check only when serve.tls.enabled was true, and a server that speaks TLS never receives the plain HTTP requests the check was written to reject. The deployments the setting was written for — the ones that terminate TLS at a proxy and therefore leave serve.tls.enabled unset — never ran it.

Deciding which callers may reach Hydra over plain HTTP belongs to the proxy or the network in front of it, which is where deployments that need it already enforce it.

Reusing a device code revokes its access and refresh tokens​

Presenting a device_code that has already been exchanged now revokes the access and refresh tokens issued from that code, including the ones the device obtained by refreshing. This is the handling RFC 6749 section 4.1.2 defines for a reused authorization code, applied to device codes. The reused request itself is still rejected with invalid_grant.

Ory can't tell whether the first or the second request came from your device, so it assumes the device_code leaked and withdraws the tokens issued under it. Only the client the code was issued to can trigger this: a request from another client_id is rejected without touching the tokens. Reuse is only recognized while the consumed device_code is still on record; a code presented after that is rejected as unknown and revokes nothing.

Breaking change​

Devices that retry the token request must stop reusing a device_code. A token request that timed out or whose response was lost may still have issued tokens. Retrying with the same device_code now revokes them, leaving the device without working credentials. Retry only after a failed request, treat invalid_grant as final, and start a new device authorization request instead of polling on.

Cleaning up consumed device codes​

Consumed device codes are retained briefly so reuse can be detected, then collected by hydra janitor --tokens. If you already run the janitor on a schedule, no action is required.

OAuth2 client key sets refresh at most once every 15 seconds​

When a client authenticates with private_key_jwt or sends a signed request object, Ory now starts at most one refresh attempt every 15 seconds per jwks_uri for the key set downloaded from the client's jwks_uri, even when the assertion names an unknown key ID. A failed attempt may be retried by the HTTP client within that refresh's timeout. A newly published signing key can stay unknown to Ory for up to 15 seconds after the previous refresh. Publish a new key at jwks_uri at least 15 seconds before signing assertions with it, and keep the retired key published while assertions signed with it can still arrive.

If refreshing the key set fails, Ory keeps serving the previously downloaded keys for up to one hour past their regular three-minute lifetime before requests fail with the refresh error. Publishing an empty key set ({"keys": []}) revokes every key: Ory adopts it on the next refresh and rejects all assertions immediately after.


v26.3.12​

Validate OAuth 2.0 client grant types on creation and update​

Ory Hydra now validates the grant_types field when you create or update an OAuth 2.0 client. Adding a grant type the server does not implement — for example a typo like authorize_code — is rejected with an invalid_client_metadata error instead of silently creating a client that cannot complete any flow. This applies to the admin API and to dynamic client registration. Matching folds case, mirroring how the token endpoint matches a client's grant types.

Existing clients keep their grant types and stay fully maintainable through the API: updates, patches, lifespan changes, and secret rotation continue to work for clients that already carry an unrecognized grant type. Only newly added values are validated.

The OpenID Connect discovery document now also advertises the JWT bearer grant (urn:ietf:params:oauth:grant-type:jwt-bearer) in grant_types_supported, matching what the server implements.

If you embed Hydra as a Go library and wire extra grant handlers through driver.WithExtraFositeFactories, also register their grant types with driver.WithExtraGrantTypes; otherwise new clients can no longer be registered with those grant types.

Device authorization grant returns access_denied when the user denies the request​

When a user denied a device authorization request (RFC 8628) at the login or consent screen, the device polling the token endpoint never learned about the denial and kept receiving authorization_pending. The poll now returns access_denied as soon as the user denies the request, so devices fail fast instead of appearing to hang. A denied user code can't be used again; the device has to start a new flow.

Thanks to Pete Bacon Darwin for reporting the issue and contributing the consent-denial fix.


v26.3.11​

Choose the new secret when rotating an OAuth 2.0 client secret​

POST /admin/clients/{id}/secrets/rotate now accepts an optional request body with a client_secret field. Send a value to rotate to that secret instead of a generated one:

{ "client_secret": "your-client-secret" }

The body is optional. Omit it and the endpoint behaves exactly as before, generating a random secret. The supplied secret must be at least 6 bytes. Ory generates 26-byte secrets, so a shorter supplied secret is weaker than a generated one.

Use this when the same client has to carry the same secret in more than one environment. Rotating in each environment separately produces different secrets, and setting a secret through the update or patch client APIs clears the rotated secrets, so the environments disagree about which previous secrets still work. Rotate in one environment, then rotate in the others with the value it returned: every environment ends up with the same current secret and the same grace window.

Ory does not check the supplied value against the client's current or rotated secrets. Supplying the current secret is treated as an ordinary rotation.

The error hint for too-short client secrets on the create, update, and patch client APIs now reads "at least 6 bytes long" instead of "at least 6 characters long", matching how the length is measured.

If you self-host with oauth2.hashers.algorithm set to bcrypt, client secrets longer than 72 bytes now return 400 Bad Request explaining the limit. They previously returned 500 Internal Server Error. This affects creating, updating, patching, and rotating clients. The default pbkdf2 algorithm has no length limit and is unchanged.

Breaking change in the TypeScript SDK​

In @ory/client, the positional rotateOAuth2ClientSecret functions returned by OAuth2ApiFp and OAuth2ApiAxiosParamCreator take the optional request body as their second argument, before options. Callers passing Axios options as the second argument must move them to the third. The OAuth2Api class and OAuth2ApiFactory, which take a request-parameters object, are unchanged.

Device codes are now strictly single-use​

A device_code can be redeemed only once. When several token requests present the same device_code at the same time, exactly one receives tokens and the others are rejected with invalid_grant.

POST /oauth2/token also reports a device_code that can no longer be redeemed more accurately. It now answers 400 Bad Request with invalid_grant, where it previously answered 500 Internal Server Error with server_error in some cases. A device that retried on 5xx was retrying a device code that could never succeed. It now receives the terminal invalid_grant that RFC 8628 defines, and should start a new device authorization request. The same request also stops answering 500 Internal Server Error when the user rejects the device request while the token request is in flight; it now answers access_denied. No configuration changes are required.

Fix missing ID token in the device authorization grant​

The token endpoint did not include an id_token in its response for the OAuth 2.0 device authorization grant (RFC 8628), even when the openid scope was requested and granted. The response was otherwise successful, so the omission was easy to miss. Device flows granted the openid scope now receive a standard OpenID Connect ID token. No action is required.

Symmetric keys no longer break public JSON Web Key responses​

A JSON Web Key Set containing a symmetric key, for example an imported HS256 key, caused GET /.well-known/jwks.json to fail with a 500 error when the set was listed in webfinger.jwks.broadcast_keys, and caused hydra get jwk --public to fail with an encoding error.

A symmetric key has no public representation, so these public projections now omit it instead of failing: /.well-known/jwks.json and hydra get jwk --public return the public halves of the remaining keys. For a set whose keys are all symmetric, hydra get jwk --public prints an empty key list. No action is required.


v26.3.10​

Dynamic client registration omits optional client metadata​

The OpenID Connect dynamic client registration endpoints under /oauth2/register no longer return client_uri, logo_uri, policy_uri, tos_uri, contacts, grant_types, response_types, and jwks when those fields have no value. RFC 7591 marks them optional, and responses previously included them as an empty string, null, or an empty object.

This unblocks clients that check the registration response against a strict schema, where such a field is accepted only when it's absent or correctly typed: an empty string isn't a valid URI, null isn't a valid array, and an empty object isn't a valid JSON Web Key Set. Model Context Protocol (MCP) clients that register themselves through dynamic client registration failed on this.

Send a value for a field when you register or update a client to have it appear in the response.

The registration and update responses also no longer return metadata, matching the read response. metadata can't be set through dynamic client registration, so sending the field back to PUT /oauth2/register/{id} — as RFC 7592 has a client do with the values it received — was rejected with invalid_client_metadata.

The admin endpoints under /admin/clients, and the client object embedded in login, consent, and device request payloads, are unchanged and still return these fields as empty-string, null, or an empty object.

Upgrading from Hydra 1.0.0-rc or earlier needs a stepped upgrade​

hydra migrate sql up no longer converts the pre-1.0.0 migration tables (hydra_client_migration, hydra_jwk_migration, hydra_oauth2_authentication_consent_migration, and hydra_oauth2_migration) into the schema_migration table.

This only affects a database whose last migration ran on Hydra 1.0.0-rc or earlier, meaning before June 2019. Every Hydra release since 1.0.0 performed the conversion automatically on the first hydra migrate sql up and then dropped the old tables. If you have run hydra migrate sql up at any point since June 2019, you are unaffected and need to do nothing.

Breaking changes​

If your database still has the tables listed above, upgrading straight to this release fails during migration with an error such as relation "hydra_client" already exists, because Hydra no longer recognizes the old tables and tries to apply the 2019 migrations from the beginning. No data is lost, but the upgrade does not complete.

To upgrade, back up your database, then run hydra migrate sql up once with any Hydra release from 1.0.0 (June 2019) up to the previous release to convert the migration tables. Then upgrade to this release and run hydra migrate sql up again.

To check whether you are affected, confirm the old tables are gone:

SELECT table_name FROM information_schema.tables
WHERE table_name IN (
'hydra_client_migration',
'hydra_jwk_migration',
'hydra_oauth2_authentication_consent_migration',
'hydra_oauth2_migration'
);

An empty result means you are not affected.

Admin JWK endpoints can now withhold private key material​

The new configuration value jwks.admin_api.expose_private_keys controls whether admin /keys responses include private key material:

jwks:
admin_api:
expose_private_keys: false

With false, GET /admin/keys/{set}, GET /admin/keys/{set}/{kid} and POST /admin/keys/{set} omit private key material and return the public key instead. Set this for defense in depth if you do not read private keys through the admin API, so that admin API access alone does not expose the keys that sign your tokens.

The default is true, which keeps the previous behavior of returning private key material for software-backed keys.

Keys are always stored in full. A key with no public representation — a symmetric key — is omitted from the response entirely, so a response can come back with an empty keys array.

The two PUT endpoints are unaffected by the setting: they echo the key material the request carried, which discloses nothing the caller did not already hold.

Keys backed by a Hardware Security Module are always returned as public keys, regardless of this setting. The public /.well-known/jwks.json endpoint is unaffected. It has always returned public keys only.

Because reads then return public keys only, writing such a response back unchanged would replace your signing keys with their public halves. With false, PUT /admin/keys/{set} and PUT /admin/keys/{set}/{kid} therefore reject a request that would leave the key set without any private key, and answer 400. Retiring one key to its public half still works while the set keeps another private key, and DELETE /admin/keys/{set} still removes a set.

Concurrent client secret rotations no longer hand back a dead secret​

Rotating an OAuth 2.0 client secret twice at the same time could return a secret that never worked. Both requests read the client, added their own secret, and wrote the whole record back, so whichever request finished last erased the secret the other had just handed to its caller. The caller received a 200 OK with a secret that failed every token request from then on.

Rotations of the same client are now serialized in the database, so every secret a rotation returns stays valid until it ages out of the five-secret retention window or you replace the client's secrets explicitly.

If many rotations pile up on the same client at once, a request may now fail with 409 Conflict instead of succeeding with an unusable secret. Retry the request.

Revoking consent sessions with DELETE /admin/oauth2/auth/sessions/consent could time out when the OAuth2 token tables were large. The revocation now uses index-backed queries whose cost scales with the number of sessions being revoked instead of the size of the token tables. No action is required.


v26.3.9​

Limit request bodies to 10 MB​

Ory Hydra now rejects requests whose body is larger than 10 MB, on both the public and the admin API. Most endpoints respond with 400 Bad Request; endpoints that read the access token or client credentials from the request body, such as /userinfo, respond with 401 Unauthorized instead.

Most endpoints were already bounded at the same magnitude, because Go's HTTP server caps form-encoded bodies at 10 MB. This change makes the limit explicit and closes the remaining gaps: multipart form data and JSON documents on the admin API were previously unbounded.

OAuth 2.0 and OpenID Connect requests are a few kilobytes at most, and so are the documents the admin API accepts, so this does not affect normal traffic. The largest payloads you are likely to send are a private_key_jwt client assertion, a signed request object, or an OAuth 2.0 client with an embedded JSON Web Key Set — all far below the limit.

Requests that exceed the limit now report the size as the reason. Previously an oversized form body was reported as an empty body, which pointed at the wrong cause.

Breaking changes​

Integrations that send request bodies larger than 10 MB must reduce the body size before upgrading.

Expired JWT bearer grants no longer issue access tokens​

The token endpoint no longer accepts urn:ietf:params:oauth:grant-type:jwt-bearer assertions for a trusted issuer grant whose expires_at has passed. Previously, such assertions could still be accepted after the grant expired.

If an integration still exchanges assertions against an expired grant, those token requests now fail with invalid_grant. Re-establish the trust relationship with a future expires_at to restore access. Deleted grants and unexpired grants behave as before.

Self-hosted Ory Hydra and Ory Enterprise License users should upgrade to a version containing this fix and can revoke a trusted issuer immediately by deleting the grant. The fix is deployed across the Ory Network, and Ory Network users do not need to take action.

Reject browser-executable URL schemes on OAuth2 clients​

Creating or updating an OAuth2 client now rejects redirect_uris, post_logout_redirect_uris, and frontchannel_logout_uri values whose scheme a browser executes as script (javascript:, data:, vbscript:). Custom native-app schemes such as com.example.app:/callback keep working.

Requests with such values now fail with 400 Bad Request, on the admin client APIs and on OpenID Connect dynamic client registration. A rejected redirect_uris value returns an invalid_redirect_uri error; post_logout_redirect_uris and frontchannel_logout_uri return invalid_client_metadata. These values never worked as OAuth2 endpoints, so no functioning integration is affected.


26.3.8

This version contains only minor changes and improvements such as dependency updates.


v26.3.7​

An attacker who obtained a live consent verifier for another user's accepted flow could redeem it for a different OAuth 2.0 client. This could yield an authorization code for the attacker's client carrying the user's subject.

Exploitation required an accepted, unexpired, unconsumed consent verifier and the matching browser and CSRF context. An attacker could not use a verifier from their own flow. Obtaining the required verifier therefore depended on a separate compromise or leak in the trusted handoff between Hydra, the consent application, and the user's browser.

Hydra now binds the verifier to the OAuth 2.0 client resolved for the authorization request. Self-hosted Ory Hydra and Ory Enterprise License users should upgrade to a version containing this fix. The fix is deployed across the Ory Network, and Ory Network users do not need to take action.

Hydra also rejects a mismatched login verifier before recording authentication session state. The login-verifier path did not yield an authorization code because Hydra rejected the client mismatch later in the flow, but it could apply session or subject-mapping side effects before that rejection.

This change also allows POST authorization requests to carry all parameters in the URL-encoded request body. Hydra previously rejected such requests with invalid_client after a login or consent verifier was issued.


26.3.6

This version contains only minor changes and improvements such as dependency updates.


v26.3.5​

Add oauth2.grant_types_disabled config key​

Ory Hydra's configuration schema now supports oauth2.grant_types_disabled: a list of OAuth 2.0 grant type identifiers clients may not be registered with. An absent or empty list disables nothing, so deployments that never set the key are unaffected.

The key can only disable grant types; it cannot enable grant types the server does not implement. In particular, the Resource Owner Password Credentials grant remains unavailable in Ory Hydra OSS — it is implemented only in Ory Network and Ory Enterprise License (OEL) deployments.

Registering a new client with a disabled grant type, or adding a disabled grant type to an existing client, is rejected with invalid_client_metadata (HTTP 400). Clients that already carry the grant type are unaffected: they keep working at runtime and remain fully maintainable through the API — updates, secret rotation, and lifespan changes still succeed.

Disabled grant types are also omitted from grant_types_supported in the OpenID Connect discovery and OAuth 2.0 authorization server metadata documents, so new integrations only see grant types they can register.

Hydra warns at startup about list entries that do not match a registered OAuth 2.0 grant type and are not custom extension grant URIs — for example device_code instead of urn:ietf:params:oauth:grant-type:device_code, or a misspelled grant type URN — since unknown entries disable nothing.

Set the key through an environment variable as a comma-separated list (OAUTH2_GRANT_TYPES_DISABLED='password,urn:ietf:params:oauth:grant-type:device_code') or as a JSON array.


v26.3.4​

hydra update oauth2-client no longer resets fields you did not pass​

The hydra update oauth2-client command now updates only the fields you provide as flags. Previously, the command replaced the entire client: any field not passed again on the command line was silently reset to its default value, which could drop redirect URIs, secrets-related settings, CORS origins, and other configuration.

The command now sends a partial update (PATCH /admin/clients/{id}) built from exactly the flags you set. For example, running hydra update oauth2-client <id> --name "new name" changes only the client name and leaves all other settings untouched.

Updating from a file (--file client.json or --file -) keeps the previous behavior and replaces the entire client, since a file represents a complete client configuration.

Breaking changes​

Scripts that relied on hydra update oauth2-client resetting unspecified fields to their defaults must now pass a complete client definition with --file to get replace semantics.

Ory CLI​

The equivalent change in the Ory CLI (ory update oauth2-client) will land in CLI v1.3.2.

Faster OAuth2 token issuance under database latency​

Ory Hydra no longer serializes JSON Web Key reads behind a global lock. Token issuance, the authorize flow, and the OpenID Connect discovery endpoint now read signing keys concurrently, so a slow database query no longer queues all other requests behind it.

Generating a missing key set is now deduplicated per network and key set: concurrent requests that race on a fresh installation trigger exactly one key generation per instance and share its result. A request that is canceled while waiting no longer aborts the generation for other waiting requests.

The Ory Hydra OEL key-set cache now scopes cache entries by network ID.

Fix OIDC session leak in the device authorization grant​

The OpenID Connect device grant now deletes its stored OIDC session after issuing an id_token. Previously the session was looked up by the device-code signature but deleted using the raw device_code, so the delete never matched and every successful device-flow id_token issuance left one OIDC session behind. On storage backends without a TTL this grew without bound.

This is an availability fix only. Device codes could still not be replayed, so there was no token-reuse or authorization impact.

Hydra CLI reports the key set name when getting JSON Web Keys​

hydra get jwks (and ory get jwk in the Ory CLI) now reports which key set each key belongs to. Previously the set attribute was missing from JSON and YAML output for single-key sets, and empty in every output format for sets with more than one key.

Each key in the output now carries its own set attribute. When keys from several sets are fetched in one command, the keys are attributed to their correct sets instead of an empty name.

The API reference for creating a JSON Web Key now documents that generating a key into an existing set keeps the existing keys, except on deployments using a Hardware Security Module (HSM), where the set is replaced and contains only the new key.

Metrics endpoints now support OpenMetrics and trace exemplars​

The Prometheus metrics endpoints of Kratos, Keto, Hydra, Oathkeeper, and Talos now additionally serve the OpenMetrics exposition format. The format is selected automatically through standard HTTP content negotiation: scrapers that ask for OpenMetrics receive it, and all other scrapers keep receiving the classic text format. No configuration or scraper change is necessary.

OpenMetrics enables exemplars, which attach a trace reference to a metric sample. Two sets of metrics use this:

  • The HTTP request duration histograms and request counters attach an exemplar when a request runs under a sampled trace, so latency dashboards can link slow-request outliers and error spikes to the traces that caused them.
  • The ory_x_popx_cockroach_transaction_retries_total counter attaches an exemplar whose value is the transaction's retry count when a transaction is retried during a traced request, and the trace records a db.transaction.retry span event. Dashboards can link retry spikes directly to the traces that caused them.

That counter's caller label now also more reliably attributes automatic CockroachDB transaction retries to the business operation that opened the transaction, for example the OAuth2 refresh token flow, instead of a generic persistence Transaction wrapper method. This makes it possible to see which operation is causing database contention. Dashboards or alerts that match specific caller label values may need updating, because existing label values change with this release.

OAuth2 client secret rotation​

Hydra now supports zero-downtime client secret rotation. See the documentation for details.

Thanks to @SamuelDev (https://github.com/SamuelDev) for the initial implementation!

YugabyteDB support for Kratos, Hydra, and Keto (OEL)​

Ory Enterprise License deployments can now run Ory Kratos, Ory Hydra, and Ory Keto against YugabyteDB.

Enabling YugabyteDB​

Point the dsn at your YugabyteDB cluster using the yugabyte:// scheme (the synonym yugabytedb:// also works):

yugabyte://user@host:5433/database?sslmode=disable

Recovering an interrupted migration​

YugabyteDB applies DDL with autocommit. Old PostgreSQL migrations inherited by YugabyteDB can contain multiple statements, so an interruption may leave the first statements applied without a row in schema_migration. Current versions of Ory no longer use multi-statement migrations and are safe.


26.3.3

This version contains only minor changes and improvements such as dependency updates.


v26.3.2​

Enforce a DNS-label boundary on wildcard return URLs and CORS origins​

A wildcard in a return URL or CORS origin is now honored only when the * is confined to a subdomain label and the host part after it is a registrable domain (an eTLD+1, such as https://*.example.com or https://*.example.co.uk). The wildcard then matches only hosts under that one customer-owned domain.

These forms are no longer honored, because each can match an attacker-registrable host:

  • dot-less, e.g. https://*foo.com (matches https://evilfoo.com);
  • public-suffix base, e.g. https://*.com, https://*.co.uk, https://*.vercel.app;
  • trailing or bare, e.g. https://www.example.*, https://*.

Only the host is checked: wildcards in the URL path are unaffected. Exact origins, the catch-all *, and Hydra's https://* ("all hosts for a scheme") are unchanged.

What enforces it​

  • Return URLs: Kratos drops unsafe wildcard allowed_return_urls entries when it loads its config, and they never match.
  • CORS origins: matching across Kratos, Hydra, Keto, Oathkeeper, and Talos enforces the same boundary at request time.

Breaking changes​

Replace any unsafe wildcard with the labelled https://*.example.com form, or list exact origins. Update serve.*.cors.allowed_origins and Kratos allowed_return_urls before upgrading; otherwise cross-origin requests and post-flow redirects from those hosts will be blocked.


v26.3.1​

JWT bearer grant no longer copies the assertion audience by default​

The urn:ietf:params:oauth:grant-type:jwt-bearer grant no longer copies the aud (audience) claim of the assertion JWT into the resulting access token by default. Per RFC 7523, the assertion's aud identifies the authorization server as the intended recipient; it is not meant to define the audience of the issued access token.

A new setting, oauth2.grant.jwt.omit_assertion_audience, controls this behavior. It defaults to true (omit). Set it to false to restore the previous behavior of copying the assertion audience into the access token.

Breaking changes​

  • Self-hosted Ory Hydra (OSS) and Ory Enterprise License (OEL): after upgrading, the assertion audience is omitted from access tokens by default. Set oauth2.grant.jwt.omit_assertion_audience: false if you rely on the old behavior.
  • New Ory Network projects: created with the assertion audience omitted.
  • Existing Ory Network projects: unchanged — they keep copying the assertion audience. To adopt the new behavior, set the option explicitly.

26.3.0

This version contains only minor changes and improvements such as dependency updates.


v26.2.22​

Much faster OAuth2 logout​

This change improves performance of OAuth2/OpenID Connect logout by moving to a database-free implementation. Logout challenges and verifiers are now self-contained encrypted tokens instead of database rows, and the lookup of clients registered for front- and back-channel logout uses a single indexed query.

The behavior of APIs and user interactions are unchanged. The admin endpoint GET /admin/oauth2/auth/requests/logout no longer returns 410 Gone for already-handled logout requests, because handled state no longer exists.

Upgrade notes:

  • Run SQL migrations before rolling out this version. The new client lookup query requires the index added by this release; on CockroachDB the query fails if the index is missing.
  • Logout flows that are in flight while the upgrade rolls out fail with "logout request expired". Users retrying complete normally on the new version.

Revoke login sessions by subject in batches​

Revoking all login sessions for a subject through DELETE /admin/oauth2/auth/sessions/login?subject=... now deletes the sessions in bounded batches instead of a single statement.

Previously, a subject with a very large number of login sessions produced a single, very large delete that could exceed the database's transaction limits or time out, failing with a "context canceled" error and leaving the sessions in place. Deleting in batches keeps each transaction small, so the request completes reliably regardless of how many sessions a subject has.

The endpoint's behavior is unchanged for callers: all matching login sessions are removed. If the request is canceled partway through, the sessions deleted so far stay deleted and the call can be safely retried to finish the rest.


26.2.21

This version contains only minor changes and improvements such as dependency updates.


26.2.20

This version contains only minor changes and improvements such as dependency updates.


26.2.19

This version contains only minor changes and improvements such as dependency updates.


26.2.18

This version contains only minor changes and improvements such as dependency updates.


26.2.17

This version contains only minor changes and improvements such as dependency updates.


v26.2.16​

Accept the OpenID Connect prompt=select_account value​

OAuth 2.0 authorization requests that include prompt=select_account are now accepted instead of being rejected with an invalid_request error. This brings support for all OpenID Connect prompt values defined in the specification.

Because a login session is tied to a single account, select_account is treated like login: the user is always sent to the login screen, where they can authenticate with the account of their choice.


26.2.15

This version contains only minor changes and improvements such as dependency updates.


26.2.14

This version contains only minor changes and improvements such as dependency updates.


v26.2.13​

Detect mismatched database dialect at startup​

Kratos, Hydra, Keto, and backoffice now verify at startup that the configured database dialect matches the actual database server. The migrate sql and serve commands run a single SELECT version() query and refuse to start if the DSN scheme does not match the server.

Previously, pointing a service at a CockroachDB cluster with a postgres:// DSN (or the reverse) silently applied the wrong migrations and produced broken schemas. This is easy to do because CockroachDB speaks the PostgreSQL wire protocol and advertises a postgres:// DSN itself.

If your DSN scheme is wrong, you will now see a clear error such as:

DSN scheme postgres:// declares a PostgreSQL database but the server is
CockroachDB. Replace the scheme with cockroach:// so that the service
picks the correct migrations and SQL dialect.

To fix it, change the DSN scheme from postgres:// to cockroach:// (or the reverse) and restart. MySQL and SQLite are unaffected.


26.2.12

This version contains only minor changes and improvements such as dependency updates.


26.2.11

This version contains only minor changes and improvements such as dependency updates.


v26.2.10​

Don't log spurious errors in Hydra OAuth 2.0 flows​

This change removes spurious "the OAuth 2.0 Authorization request must be aborted" messages from Hydra's logs, which do not indicate an error but simply that the user has been redirected.


v26.2.9​

Patch security vulnerabilities in dependencies​

Bump several dependencies to patched versions to address security advisories reported by Dependabot.

Notable updates:

  • github.com/jackc/pgx/v5 to v5.9.2 across all Go modules (SQL injection via placeholder confusion in dollar-quoted string literals).
  • github.com/moby/spdystream to v0.5.1 (denial of service on container runtime interface).
  • go.opentelemetry.io/otel to v1.41.0 (remote DoS amplification via multi-value baggage header).
  • postcss to >=8.5.10 (XSS via unescaped </style> in CSS stringify output).
  • uuid to >=14.0.0 (missing buffer bounds check in v3/v5/v6 generators).
  • @xmldom/xmldom to >=0.8.13 (XML node injection and uncontrolled recursion).
  • axios, follow-redirects, lodash, picomatch, brace-expansion, serialize-javascript, yaml, file-type, i18next-fs-backend, @nestjs/core to their respective patched versions.

v26.2.8​

Fix 409 Conflict errors on fresh CockroachDB v26.1 installs​

Fresh Hydra installs on CockroachDB v26.1 returned a 409 Conflict: Unable to insert or update resource because a resource with that value exists already error on the first request to /.well-known/jwks.json after running migrations. The error blocked Hydra from auto-generating its JSON Web Key Sets, which in turn prevented OAuth token verification by relying parties.

Only fresh installs are affected. Existing deployments that ran the initial migrations on an earlier CockroachDB version and later upgraded their cluster to v26.1 are not affected, because the problematic behavior happens at migration time rather than at cluster upgrade time. Deployments on PostgreSQL, MySQL, or SQLite are also unaffected.

A new CockroachDB-only migration drops both phantom indexes if they are present. No operator action is required beyond applying migrations.

SSRF protection improvements​

Error messages originating from the SSRF protection mechanism no longer leak IP addresses if the hostname resolves to an internal IP address. This prevents SSRF recon through user-supplied URLs and hostnames.


v26.2.7​

New indexes on Hydra OAuth 2.0 token tables​

Hydra adds (nid, subject, client_id) indexes to hydra_oauth2_access and hydra_oauth2_refresh. The indexes are created online on CockroachDB and PostgreSQL, so applying the upgrade does not block reads or writes against the token tables during the schema change.

On Hydra OEL and Hydra Cloud, the new indexes accelerate consent session revocation, complementing recent code-level improvements to the OEL revoke flow.


26.2.6

This version contains only minor changes and improvements such as dependency updates.


v26.2.5​

Fix shared mutable state in error handling​

Error globals such as herodot.ErrNotFound were package-level variables shared across all requests. Calling methods like WithReason or WithDetail mutated these variables in place and returned the same pointer, so any request that added context to an error — reason text, details, etc, modified the global. The next request to reach an error path using the same error inherited those stale details.

As a consequence, observability (logs, traces) for requests resulting in an error suffered from the same issue: some errors were reported with details belonging to an unrelated request, or with fields missing that should have been present.

The new API creates a fresh error instance on each call, so each request gets its own copy.

The following values were at risk of leaking into unrelated error responses:

  • HTTP cookie names (Kratos CSRF flow)
  • Entity UUIDs (identity, organization, etc)
  • OAuth2 error hints (Hydra and Kratos Hydra bridge)
  • OIDC provider URLs and raw upstream error responses (Kratos OIDC strategy)
  • External schema fetch URLs and HTTP status codes (Kratos schema handler)
  • JWT claims and issuers (Oathkeeper JWT authenticator)

No data was written to persistent storage or transmitted outside the error response. Any two requests hitting the same error path on the same node — even back-to-back with no concurrency — could exchange error details.

Under concurrent load, the shared writes also constitute a true data race, which can additionally produce errors in an inconsistent or partially written state.

This change has no externally observable effect other than fixing the information leak in error paths.


v26.2.4​

Add oauth2.preserve_ext_claims configuration option​

When mirror_top_level_claims is disabled, custom claims not promoted to the top level of the access token were silently dropped. The new oauth2.preserve_ext_claims setting (default false) keeps those claims in the ext object instead.

Migrate Helm chart repository URL from k8s.ory.sh to k8s.ory.com​

The Helm chart repository URL has been updated from k8s.ory.sh to k8s.ory.com. The old URL will continue to work with redirects for a limited time. Update your Helm repository configuration to use the new URL.


v26.2.3​

Remove CGO requirement from Ory by switching to modernc.org/sqlite​

Migrates SQLite support from mattn/go-sqlite3 (CGO) to modernc.org/sqlite (pure Go) and fixes several timestamp comparison bugs that caused incorrect pagination results. This migration is fully backwards compatible. Going forward, the -tags sqlite tag is no longer required to compile Ory.


26.2.2

This version contains only minor changes and improvements such as dependency updates.


v26.2.1​

Accept custom domain issuer as valid audience in JWT Bearer Grant​

When using a custom domain (CNAME) as the OAuth2 issuer URL, Hydra now accepts the issuer-derived token URL as a valid audience in JWT Bearer Grant assertions. Previously, only the internal public URL was accepted, causing JWT Bearer Grant requests to fail when clients set the audience to the custom domain token endpoint.


26.2.0

This version contains only minor changes and improvements such as dependency updates.


26.1.18

This version contains only minor changes and improvements such as dependency updates.


26.1.17

This version contains only minor changes and improvements such as dependency updates.


26.1.16

This version contains only minor changes and improvements such as dependency updates.


26.1.15

This version contains only minor changes and improvements such as dependency updates.


26.1.14

This version contains only minor changes and improvements such as dependency updates.


26.1.13

This version contains only minor changes and improvements such as dependency updates.


26.1.12

This version contains only minor changes and improvements such as dependency updates.


v26.1.11​

Breaking changes​

Upgrading from <26.1.7 will break pagination tokens. To ensure smooth upgrade, you must upgrade to 26.1.7 first.


26.1.10

This version contains only minor changes and improvements such as dependency updates.


26.1.9

This version contains only minor changes and improvements such as dependency updates.


26.1.8

This version contains only minor changes and improvements such as dependency updates.


v26.1.7​

Optimized SQL migrations for PostgreSQL​

We have improved SQL migrations for PostgreSQL in Ory Hydra when upgrading from Open Source release v2.2.0 or later. This change reduces the need to acquire exclusive table locks, and removes some unnecessary other work. Exclusive table locks in PostgreSQL block all writes to that table, thus stalling any in-flight requests.

Automatic retries for serialization failures on PostgreSQL​

Ory Hydra OEL now automatically retries transactions failing with serialization errors in PostgreSQL. In high-throughput scenarios, this decreases the client error rate substantially in certain circumstances.


26.1.6

This version contains only minor changes and improvements such as dependency updates.


26.1.5

This version contains only minor changes and improvements such as dependency updates.


26.1.4

This version contains only minor changes and improvements such as dependency updates.


26.1.3

This version contains only minor changes and improvements such as dependency updates.


26.1.2

This version contains only minor changes and improvements such as dependency updates.


26.1.1

This version contains only minor changes and improvements such as dependency updates.


26.1.0

This version contains only minor changes and improvements such as dependency updates.


25.4.12

This version contains only minor changes and improvements such as dependency updates.


25.4.11

This version contains only minor changes and improvements such as dependency updates.


v25.4.10​

Remove the audience: audit field in some log entries​

An experiment was made in the past to use logs for auditing, by having a certain field (i.e. key-value) present in the log entry, e.g. in JSON: "audience": "audit".

However, this approach was never used everywhere (only in some places in Hydra) and it was decided to stop the experiment.

As a result, the "audience": "audit" field is removed from these log entries. The rest of these log entries is unchanged.

Logs not using this field are unchanged.

Example before:

{"audience":"audit","level":"info","msg":"something happened","service_name":"Ory Hydra","service_version":"master","time":"2025-12-19T09:55:39.647379+01:00"}

Example after:

{"level":"info","msg":"something happened","service_name":"Ory Hydra","service_version":"master","time":"2025-12-19T09:55:39.647379+01:00"}

Breaking changes​

None.

If you relied on the presence of this field in the logs, we recommend you use events or OpenTelemetry traces instead, which carry much more information.

Stricter URI validation for client metadata​

Ory Hydra now enforces stricter validation rules for certain client metadata URIs when creating, updating or patching clients. These fields include policy_uri, tos_uri, client_uri, and logo_uri. The URIs must now be valid URLs with either http or https schemes. This change enhances security and ensures compliance with OAuth 2.0 and OpenID Connect specifications.


v25.4.9​

Optionally Discard Skipped Consents​

When enabled, the new feature_flags.discard_skipped_consents configuration option instructs Hydra to not store the full consent (SQL table hydra_oauth2_flow). Instead, only an identifier is stored for a short period of time to prevent reuse attacks. It is possible to skip consents for trusted OAuth2 clients per default by setting skip_consent to true for the trusted clients. Otherwise, only consents that were already granted will be skipped.

Limitations​

When this feature is enabled, OAuth2 client front- and back-channel logouts will not work anymore.

Example​

feature_flags:
discard_skipped_consents: true

v25.4.8​

Hard to revert SQL migration​

This release includes an SQL migration for Ory Hydra that drops multiple constraints. Due to this, reverting the migration is not very performant and may cause downtime. There should be no reason to revert this migration, as the schema itself is fully backwards compatible. This version should not be skipped during an upgrade. If a version revert is necessary, we discourage running the down migrations at all.


25.4.7

This version contains only minor changes and improvements such as dependency updates.


25.4.6

This version contains only minor changes and improvements such as dependency updates.


v25.4.5​

Critical SQL Migrations​

This release adds SQL migrations that should be executed separately. We highly discourage skipping this version, as it might cause downtime during future upgrades.


25.4.4

This version contains only minor changes and improvements such as dependency updates.


v25.4.3​

Add feature flag to disable access token persistence for stateless JWT tokens​

Introduces new feature flags strategies.jwt.stateless.enabled and strategies.jwt.stateless.claim_name for Hydra that skips database writes for JWT access tokens. When enabled, access tokens are issued as self-contained JWTs with a top-level claim indicating to be stateless, and operations that require token state (introspection, revocation, userinfo) return 501 Not Implemented. This optimization significantly improves performance for workloads that use JWT access tokens and don't require token revocation or introspection capabilities.

Internal Bugfixes​

This release includes some internal bugfixes that are required to ensure zero-downtime upgrades of future versions. Therefore, it is highly recommended to not skip this version.


25.4.2

This version contains only minor changes and improvements such as dependency updates.


25.4.1

This version contains only minor changes and improvements such as dependency updates.


25.4.0

This version contains only minor changes and improvements such as dependency updates.


v25.3.9​

Fix regression in refresh token UsedTimes calculation​

Fixed a regression in Hydra OAuth2 refresh token handling where the UsedTimes validation was incorrectly checking the Valid flag before comparing against the graceful count limit. This caused refresh tokens to be incorrectly rejected during the grace period when the UsedTimes field was not explicitly set, even when the usage count was within acceptable limits.

The fix ensures that refresh tokens are properly validated based on the usage count alone, without requiring the UsedTimes field to be explicitly marked as valid in the database.


25.3.8

This version contains only minor changes and improvements such as dependency updates.


25.3.7

This version contains only minor changes and improvements such as dependency updates.


25.3.6

This version contains only minor changes and improvements such as dependency updates.


v25.3.5​

Change of base image​

The base image for OEL images is now set to "gcr.io/distroless/static-debian12:nonroot". Previously, it was "gcr.io/distroless/static-debian12:debug-nonroot", which included BusyBox (a minimal shell and basic debugging utilities). Debug images are still available using the "-debug" tag suffix.

JWK Cache​

Ory Hydra OEL now supports in-memory caching of JWKs to improve performance and reduce latency. The cache is enabled by setting the db.jwk_cache.enabled configuration option to true. Other available configuration options are db.jwk_cache.size, which is the maximum number of keys to be cached, and db.jwk_cache.ttl, which is the time-to-live for cached keys. It is recommended to set a cache TTL to force regular refreshes of the keys. Updates to keys are not actively propagated to the cache, as clients are expected to accept old keys for a certain period of time.


v25.3.4​

Increased Refresh Token Grace Period​

The maximum duration for the Refresh Token Rotation Grace Period has been extended from 30 days to 180 days in Ory Hydra with the Ory Enterprise License (OEL). For Ory Network projects and non-OEL Hydra, the maximum grace period remains 5 minutes.

The extended 180-day period is only available when a reuse count (rotation_grace_reuse_count) is configured. Without a reuse count, the limit remains 5 minutes.

Important: The 180-day grace period is considered insecure for general use. It is offered exclusively to support legacy migration scenarios and is not recommended for production deployments.


v25.3.3​

Improved tracing and metrics for the high-performance SQL connection pool​

This change adds distributed tracing and advanced metrics for the high-performance SQL connection pool in all Ory OEL products.


2025-06-11​

Tag: 290abca8469dc46c1ba07708849fed28fdbc1b69

Add hydra debug commands​

Changes:

  • the hydra debug challenge <challenge> command is available in the Ory Hydra Enterprise build and offers the ability to decode various consent challenge and verifiers. Use hydra debug challenge --help for detailed documentation.
  • the hydra debug encrypted-session <session> command for Ory Hydra Enterprise offers the ability to decrypt encrypted session data which is kept in the database.

2025-05-09​

Update OSS dependency of Hydra OEL​

Changes:

2025-03-19​

Reduce memory usage of Ory Hydra OEL​

This change addresses high memory consumption issue by correctly initializing the sessionCheckCache

2024-12-17​

Handle concurrent refreshes and improve graceful refreshing​

This patch improves Ory Hydra's ability to deal with refresh flows which, for example, concurrently refresh the same token. Furthermore, graceful token refresh has been improved to handle a variety of edge cases and scenarios.

This will remove/reduce serializable errors in CockroachDB during OAuth2 token exchanges.

https://www.ory.com/docs/hydra/guides/graceful-token-refresh

2024-07-17​

Disable pre-configured multi-region config in Ory Hydra​

Disables the pre-configured multi-region configuration for self-hosting Ory Enterprise License customers.