DATE:
AUTHOR:
Ory Team
Ory Hydra Ory Enterprise License

Ory Hydra v26.3.10 released

DATE:
AUTHOR: Ory Team

Ory Hydra

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.

Consent revocation no longer times out on large databases

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.

Device code lifespan and polling interval now take effect

Projects that configure how long device and user codes stay valid, or how often a device should poll the token endpoint, now get the value they configured. These two settings were accepted and stored, but the device authorization flow kept using the defaults of 10 minutes and 5 seconds.

If your project sets either value, the OAuth 2.0 device authorization grant changes behavior at your next request:

  • Device and user codes expire after the lifespan you configured, instead of after 10 minutes. Shorter values mean users have less time to enter the user code before the device has to start a new request.

  • The interval in the device authorization response reports the polling interval you configured, instead of 5 seconds. This value is advisory and Ory does not reject faster polling, but devices that honor it poll at your rate.

Projects that leave both settings unset are unaffected and keep the 10 minute and 5 second defaults. You can change either value in the Ory Console under OAuth 2.0 configuration, or through the project configuration API.


This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.


Ory Network, Ory Kratos, Ory Keto, Ory Oathkeeper, Ory Polis, Ory Elements, and Ory Terraform

No significant changes in this release.

Powered by LaunchNotes