- DATE:
- AUTHOR:
- Ory Team
Ory Hydra v26.3.10 released
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: falseWith 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
intervalin 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.