- DATE:
- AUTHOR:
- Ory Team
Ory Network, Ory Hydra, Ory Kratos, Ory Keto, Ory Oathkeeper, Ory Polis v26.4.4 released
Ory Network
Match event stream events to your users by external ID
Identity, login, registration, recovery, settings, verification, and session
events delivered through a live event stream now carry the identity's external
ID in the ExternalID key of eventAttributes. Use it to match events to user
records in your own system without reading the identity from the Ory API.
The key is absent when the identity has no external ID. Ory delivers it to your event stream only and does not keep it in its own analytics. Existing consumers keep working without changes.
Refresh tokens with consent-added audiences
Refreshing tokens now checks only the audiences that the client requested and the consent app granted against the client's current audience allow list. Audiences added by the consent app no longer cause refresh to fail when they are outside that list. Refreshed tokens retain the full granted audience.
For example:
The client allows and requests audience A.
The consent app grants A and B, adding B outside the client's allow list.
The client refreshes its token.
Previously, refresh failed because granted audience B was not allowed. Now, refresh succeeds because requested audience A is still allowed. The refreshed access token retains A and B.
Removing a requested and granted audience from the client's allow list still
makes refresh fail with invalid_request. To withdraw a consent-added audience,
revoke the grant or refresh token.
This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.
Ory Hydra
Deleting an OAuth 2.0 client with many tokens now succeeds
Deleting an OAuth 2.0 client that had issued a large number of tokens or collected many consent sessions could fail, because everything belonging to the client was removed in a single database operation that ran out of time. Such a client could not be deleted at all.
Deletion now takes effect immediately and removes the associated data afterwards, in batches:
The client stops working the moment the request returns. It can no longer obtain tokens, its existing access and refresh tokens stop being valid, and remembered consent no longer applies to it. Stateless JWT access tokens are the exception: they are not stored, so they cannot be revoked and stay valid until their
expclaim passes.Its tokens and consent sessions are removed after that. Remembered login sessions aren't client-specific, so they remain after a client is deleted. If a deletion is interrupted, repeating the request continues where it stopped.
If you run Hydra yourself, the janitor takes a new --clients flag, which
finishes any pending deletion and frees the deleted client's ID:
hydra janitor --clients {database-url}Run it alongside the cleanups you already run. Without it, a deleted client keeps a row until its ID is reused, and its remaining tokens and consent sessions are not reclaimed.
Breaking changes
Upgrading from a version earlier than v26.3.17 lets a deleted client keep working during the rolling upgrade. To upgrade safely, upgrade to v26.3.17 first.
The client ID is reserved after deletion until the client's remaining rows are
gone, and for at least five minutes. Creating a client with that ID during the
window fails with 409 Conflict and the error code client_id_reserved.
If you delete a client and immediately create a new one with the same ID — when resetting a client from a script, for example — retry the create until it succeeds instead of waiting a fixed time. A client that issued many tokens stays reserved until its cleanup finishes.
Revoking consent revokes all of the subject's tokens
Revoking a subject's OAuth2 consent sessions now revokes every access and refresh token issued to that subject, or to that subject and client when you revoke consent for a single client. Before, it only revoked tokens issued for a consent session that still existed.
Tokens that never belonged to a consent session are now revoked too. This includes tokens from the resource owner password credentials grant and the JWT bearer grant, and tokens whose consent session was already removed.
Breaking changes
If you revoke consent for a subject and rely on that subject's password grant or JWT bearer tokens staying valid, those tokens now stop working. Issue new tokens after revoking consent, or use a different subject for tokens that must survive a consent revocation.
Writing a key uses the key ID of the URL path
PUT /admin/keys/{set}/{kid} now creates or updates the key with the key ID from
the URL path. Before, it used the kid of the request body and ignored the path,
so a request could create or overwrite a key that the path didn't name. A
request body without a kid stored a key without a key ID, which
GET /admin/keys/{set}/{kid} and DELETE /admin/keys/{set}/{kid} couldn't
address. Ory Hydra Authorization now stores such a key under the key ID of the
path.
Breaking changes
If the request body has a kid that differs from the key ID in the URL path,
the request now fails with 400 Bad Request and the key set stays unchanged.
Send the key ID of the key you write in the URL path.
Configure the Kratos integration under identity_provider.kratos
Hydra now accepts admin_url, public_url, and headers under
identity_provider.kratos. The existing urls.identity_provider settings
remain supported as fallbacks for omitted fields. New headers replace the legacy
header map. To stop sending legacy headers, remove the legacy setting.
Janitor request cleanup no longer scans the whole flow table
hydra janitor --requests selects expired login and consent requests with a
query that had no matching index, so every run read the entire
hydra_oauth2_flow table. On large databases this took minutes.
A new partial index on hydra_oauth2_flow now serves that query. It is created
by the hydra migrate sql up migration 20260921000001000000 and covers only
rows the janitor can delete, so it stays small even when the table is large.
On PostgreSQL, CockroachDB, and YugabyteDB the index is built without locking
the table, but the build reads the whole table once and can take a while on
large installations. If you already created an index named
hydra_oauth2_flow_janitor_requests_idx with the same definition, the migration
leaves it in place. If a PostgreSQL build is interrupted, it leaves an invalid
index behind that a later migration run does not rebuild. Check
pg_index.indisvalid and run
REINDEX INDEX CONCURRENTLY hydra_oauth2_flow_janitor_requests_idx to repair
it. MySQL does not support partial indexes, so the migration is a no-op there.
Refresh tokens with consent-added audiences
Refreshing tokens now checks only the audiences that the client requested and the consent app granted against the client's current audience allow list. Audiences added by the consent app no longer cause refresh to fail when they are outside that list. Refreshed tokens retain the full granted audience.
For example:
The client allows and requests audience A.
The consent app grants A and B, adding B outside the client's allow list.
The client refreshes its token.
Previously, refresh failed because granted audience B was not allowed. Now, refresh succeeds because requested audience A is still allowed. The refreshed access token retains A and B.
Removing a requested and granted audience from the client's allow list still
makes refresh fail with invalid_request. To withdraw a consent-added audience,
revoke the grant or refresh token.
This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.
Ory Kratos
Webhooks and claims hooks can read labels of the caller's IP address
Every Kratos webhook, the password migration hook, and the session JWT claims
hook now receive ctx.ip_labels, a sorted array of labels for the caller's IP
address. It's always present, so a template written against the Ory Network
keeps working on a self-hosted deployment.
Kratos reads the labels from request headers. Each of these headers set to 1
adds its label:
HeaderLabelX-Ory-Ip-Reputation-Anonymizerory:anonymizerX-Ory-Ip-Reputation-Open-Proxyory:open_proxyX-Ory-Ip-Reputation-Vpnory:vpnX-Ory-Ip-Reputation-Malwareory:malwareX-Ory-Ip-Reputation-Botnet-Ccory:botnet_cc
Without a reverse proxy that sets these headers, ctx.ip_labels is an empty
array. If your proxy sets them, it must also remove them from every incoming
request, or a caller can add labels to their own request.
Webhooks and claims hooks can read what is known about the caller's IP address
Every Kratos webhook, the password migration hook, and the session JWT claims
hook now receive ctx.ip_labels. It lists the known services that the caller's
IP address belongs to: ory:anonymizer, ory:open_proxy, ory:vpn,
ory:malware and ory:botnet_cc. Ory labels the address at the edge, so you
don't have to configure anything.
ctx.ip_labels is always present and is an empty array when the address is on
none of the lists. To block a flow, raise an error in the template. To require a
second factor, send the labels to a webhook on the login after position and
let the webhook respond with required_aal. This template blocks sign-ins from
known malware sources and sends the labels of all other sign-ins to the webhook:
function(ctx)
if std.member(ctx.ip_labels, 'ory:malware') then
error 'sign-in is not available from this network'
else
{ identity_id: ctx.identity.id, ip_labels: ctx.ip_labels }The webhook then responds with { "required_aal": "aal2" } when the labels
contain ory:vpn. See
Accessing the caller's IP labels
and
Require a second factor.
This is now available on Ory Network, for the Ory Enterprise License, and will be part of the next Ory Open Source release.
Ory Keto
Pass Keto configuration files explicitly
Ory Keto Permissions no longer loads ~/keto.yml automatically. Without
--config, it uses configuration defaults and environment variables.
If you rely on the default file, add --config "$HOME/keto.yml" to your
commands, for example keto serve --config "$HOME/keto.yml".
This is now available for the Ory Enterprise License and will be part of the next Ory Open Source release.
Ory Oathkeeper
Configure outbound HTTP clients per pipeline step and access rule
The cookie_session and bearer_token authenticators and the hydrator mutator reject upstream responses larger
than 5 MiB and fail the request by default. Session check requests time out after 30 seconds by default.
Set top-level http_client settings for shared defaults, then override individual fields in each handler's
config.http_client or each access rule's config.http_client. Settings cover timeout, response size limit,
retries, and private IP restrictions. They apply to session checks, OAuth token and introspection calls, remote
and Keto authorization, hydrator calls, and HTTP JWKS fetching for JWT authentication and ID-token mutation.
Use unlimited to disable the timeout or response size limit.
The hydrator's api.retry.give_up_after and api.retry.max_delay settings are deprecated and remain supported.
Use http_client.timeout for the timeout and http_client.retry.max for the retry count.
Breaking changes
All pipeline HTTP clients limit response bodies to 5 MiB by default. Upstreams that return responses above 5 MiB
require a larger http_client.max_response_body_size or unlimited.
A hydrator give_up_after of 0s uses the HTTP client timeout, which defaults to 30 seconds, instead of disabling it.
Oathkeeper bounds how it fetches JSON Web Key Sets
The jwt authenticator, the id_token mutator, and the
/.well-known/jwks.json endpoint fetch JSON Web Key Sets through one shared
cache.
A key ID that no configured location contains triggers at most one fetch per location every 15 seconds, or every half
jwks_ttlwhen that is shorter. A rotated key is picked up within that interval.Past
jwks_ttl, cached keys keep serving while Oathkeeper refreshes them in the background. If a location stays unreachable, its last keys are served for up to one hour pastjwks_ttland are then rejected until the location recovers.A fetch runs for at most 15 seconds. Responses larger than 1 MiB and documents without a
keysproperty are rejected and leave the cached keys in place.
Breaking changes
Oathkeeper no longer follows HTTP redirects when it fetches a JSON Web Key Set.
If a jwks_urls or jwks_url entry redirects to another URL, replace it with
the URL the redirect points to.
This is now available for the Ory Enterprise License and will be part of the next Ory Open Source release.
Ory Polis
Authorization codes, access tokens, and SSO sessions now expire on time
Ory Polis now checks whether a record has expired every time it reads one.
Authorization codes, access tokens, and SSO sessions stop working as soon as
their lifetime ends. Previously, expiry took effect when the periodic cleanup
job removed the record. /oauth/token rejects an expired code with
403 Invalid code, /oauth/userinfo rejects an expired access token with
403 Invalid token, and an expired SSO session can no longer resume a login.
Lifetimes are still set by DB_TTL, and the expires_in value that
/oauth/token returns is unchanged.
Before you deploy this version, run the database migrations if you manage them
yourself — on PlanetScale, or with DB_MANUAL_MIGRATION=true. This release adds
an expiresAt column to the jackson_store table and every read uses it, so
reads fail until the column exists. The migration changes only table metadata,
so it is quick to apply to a live table. Deployments that let Ory Polis manage
the schema add the column automatically at startup.
Batched directory sync events arrive in the order they happened
With DSYNC_WEBHOOK_BATCH_SIZE set, a connection's queued events could reach
the webhook out of order, for example a group.user_added before the
group.created of its group. This happened on DynamoDB once more events were
queued than fit on one page of the database, and between events queued close
together on SQLite, SQL Server, Redis and MongoDB. Each connection's events now
go out in the order they were queued on every database engine, and a batch holds
up to DSYNC_WEBHOOK_BATCH_SIZE events even when that is larger than
DB_PAGE_LIMIT.
On DynamoDB a batch run now reads the whole event queue with strongly consistent reads, rather than each connection's events through an index that DynamoDB updates a moment after each write, so it sees every event queued before the ones it sends. Reads of the event queue use twice the read capacity they did.
Faster paged lists on SQL databases
Paged lists read from SQL databases, such as the lists of connections and
directory sync users, sorted every record of their kind on each page, so a page
took longer the more records there were. A new index on
jackson_store (namespace, createdAt) lets the database read a page in order
instead. It replaces the index on jackson_store (namespace), which the new one
covers. On PostgreSQL, CockroachDB and YugabyteDB, lists read through a
secondary lookup get the same treatment: jackson_index (key, id) replaces
jackson_index (key).
If you run migrations yourself (DB_MANUAL_MIGRATION), run them before you
upgrade. On PostgreSQL the migration builds the index with
CREATE INDEX CONCURRENTLY, and MySQL, MariaDB, CockroachDB and YugabyteDB
build it without blocking writes. Otherwise Polis creates the index when it
starts, and on PostgreSQL, SQL Server and SQLite writes to jackson_store wait
until it is built: about a second per million rows on PostgreSQL.
On YugabyteDB whose tables Polis creates when it starts, create the indexes
yourself before you upgrade, with
CREATE INDEX CONCURRENTLY IF NOT EXISTS "_jackson_store_namespace_created" ON "jackson_store" ("namespace", "createdAt")
and
CREATE INDEX CONCURRENTLY IF NOT EXISTS "_jackson_index_key_id" ON "jackson_index" ("key", "id").
Polis creates them inside a transaction, where YugabyteDB cannot build an index
concurrently, and a nonconcurrent build is not safe while Polis writes to the
table.
Setup links can give their SAML connections their own ACS URL
With ACS_URL_HOST configured, an SSO setup link can now carry an ACS URL ID,
so the SAML connections created through it get their own ACS URL. Tick the new
option on the create Setup Link page, or pass acsUrlId to
POST /api/v1/sso/setuplinks. The setup link's instructions show that ACS URL
and a matching SP metadata link, so your customer can configure their IdP before
the connection exists. Regenerating or renewing the link keeps the ID. Links
created without it work as before.
This is now available for the Ory Enterprise License and will be part of the next Ory Open Source release.
Ory Elements and Ory Terraform
No significant changes in this release.