From ef56fac1d50a6c1f428376c2a677636547f868b2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <323649642+oc-tmueller@users.noreply.github.com> Date: Thu, 17 Sep 2026 23:24:42 +0200 Subject: [PATCH 1/3] docs: document the OIDC audience parameter MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The openidconnect app gains an optional "audience" key naming the value the IdP puts into the access token's "aud" claim. It defaults to client-id, and is needed by IdPs that address the resource server instead - RFC 9068 §3 defines an access token's "aud" that way, and Microsoft ADFS follows it, prefixing the application identifier with "microsoft:identityserver:" unless that identifier is already a URL. Documented for 11.0 and 10.16, the two versions the app change ships on. The entry covers the operational consequences rather than just the syntax, because each of them is a way to lock an instance out: the audience becomes authoritative once set, so a token issued to another client of the same IdP is accepted when its "aud" matches, an introspection response that omits "aud" can no longer be used at all, and with token-exchange mode the first list entry is what gets requested from the IdP. Values that cannot be an audience are discarded, and if none is left every access token is rejected. For finding the ADFS value, both cmdlets are named: Get-AdfsWebApiApplication for an OpenID Connect application group, Get-AdfsRelyingPartyTrust for a legacy WS-Federation or SAML trust. Naming only the latter would strand admins whose OIDC registration is an application group, which is the modern default. No app version numbers are claimed: neither release carrying the fix is tagged yet, so the entry points at the issue instead. See owncloud/openidconnect#373 and owncloud/openidconnect#374 Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com> --- .../config_apps_sample_php_parameters.adoc | 30 +++++++++++++++++++ .../config_apps_sample_php_parameters.adoc | 30 +++++++++++++++++++ 2 files changed, 60 insertions(+) diff --git a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index dc12276..8fb8cf9 100644 --- a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -400,6 +400,36 @@ allowed-user-backends:: Limit the users which are allowed to login to a specific user backend - e.g. LDAP (`'allowed-user-backends' ⇒ ['LDAP']`) +audience:: +The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access +token is accepted only if this names the resource ownCloud is being addressed as. Takes a single +non-empty string or a list of them, and *replaces* `client-id` as the expected value rather +than adding to it. The default is `client-id`, which is what most IdPs send, so set this +only if your IdP names the resource server instead. Microsoft ADFS does: it prefixes the +identifier of the application the token was issued for with `microsoft:identityserver:`, +unless that identifier is already a URL, in which case it is sent verbatim. Read the +identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group +registration, or with `Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML +relying party trust. It is often configured to the same GUID as the `client-id`, which is +why the prefixed value frequently repeats it - but it is a *resource* identifier, not the +client, and the two are free to differ. Whatever your IdP sends must be reproduced here +exactly, including case: the comparison is strict, so a difference in casing, or a numeric +`aud` against a string here, will not match. ++ +Setting this key makes `aud` authoritative, which has three consequences worth planning for. +A token issued to a *different* client of the same IdP is accepted whenever its `aud` +matches, because the `client_id` of RFC 7662 is no longer consulted - so choose a value that +only ownCloud can be issued for, and do not reuse a tenant-wide resource identifier here. +Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 +permits, because every opaque token would then be rejected. And with +`exchange-token-mode-before-introspection`, the first usable entry of a list is also what the +token exchange requests from the IdP, so list the resource ownCloud should be given first. ++ +Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - +is discarded. If nothing usable is left, every access token is rejected and a warning naming +the offending value is written to the log. Requires the app release carrying the fix for +https://github.com/owncloud/openidconnect/issues/373[openidconnect issue 373]. + auth-params:: Additional parameters which are sent to the IdP during the auth requests diff --git a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index 56eefb2..458779f 100644 --- a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -395,6 +395,36 @@ allowed-user-backends:: Limit the users which are allowed to log in to a specific user backend - e.g. LDAP (`'allowed-user-backends' ⇒ ['LDAP']`) +audience:: +The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access +token is accepted only if this names the resource ownCloud is being addressed as. Takes a single +non-empty string or a list of them, and *replaces* `client-id` as the expected value rather +than adding to it. The default is `client-id`, which is what most IdPs send, so set this +only if your IdP names the resource server instead. Microsoft ADFS does: it prefixes the +identifier of the application the token was issued for with `microsoft:identityserver:`, +unless that identifier is already a URL, in which case it is sent verbatim. Read the +identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group +registration, or with `Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML +relying party trust. It is often configured to the same GUID as the `client-id`, which is +why the prefixed value frequently repeats it - but it is a *resource* identifier, not the +client, and the two are free to differ. Whatever your IdP sends must be reproduced here +exactly, including case: the comparison is strict, so a difference in casing, or a numeric +`aud` against a string here, will not match. ++ +Setting this key makes `aud` authoritative, which has three consequences worth planning for. +A token issued to a *different* client of the same IdP is accepted whenever its `aud` +matches, because the `client_id` of RFC 7662 is no longer consulted - so choose a value that +only ownCloud can be issued for, and do not reuse a tenant-wide resource identifier here. +Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 +permits, because every opaque token would then be rejected. And with +`exchange-token-mode-before-introspection`, the first usable entry of a list is also what the +token exchange requests from the IdP, so list the resource ownCloud should be given first. ++ +Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - +is discarded. If nothing usable is left, every access token is rejected and a warning naming +the offending value is written to the log. Requires the app release carrying the fix for +https://github.com/owncloud/openidconnect/issues/373[openidconnect issue 373]. + auth-params:: Additional parameters, which are sent to the IdP during the auth requests From 63f2bdff021e30be44d21b3229f6698b18b95a23 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <323649642+oc-tmueller@users.noreply.github.com> Date: Mon, 21 Sep 2026 13:49:52 +0200 Subject: [PATCH 2/3] docs: document the access token audience per identity provider MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The audience parameter alone does not tell an admin whether they need it, and for one supported provider it cannot help at all: Keycloak sends no aud claim unless an audience mapper is configured, so no configured value can ever match. openidconnect 2.4.2 and 2.3.5 therefore also accept the claim naming the client a token was issued to - azp, appid or client_id - and that is what these pages now describe. - oidc.adoc gains an "Access Token Audience" section. Because the behaviour differs per app version and both fixing releases are still unreleased, the applicability is structural rather than a footnote: the table has one column per version - "On 2.4.1" and "On 2.4.2" for ownCloud 11, "On 2.3.4 and earlier" and "On 2.3.5" for ownCloud 10 - so a reader of any single row sees what applies to the version they actually run. On 2.4.1 that means Keycloak needs the audience mapper, Azure needs requestedAccessTokenVersion 2, and ADFS and OneLogin have no remedy short of the upgrade. The section also covers both log lines an admin will meet, the refusal of tokens marked as refresh tokens, the deliberate exception for ID tokens, and the Keycloak audience-mapper recipe with its console path and kcadm.sh command. - config_apps_sample_php_parameters.adoc: the audience entry described the default as "the client-id", which is only half of it, and ended in a placeholder instead of a version. Both fixed, with the versions marked as in preparation since neither 2.4.2 nor 2.3.5 is tagged. - ms-azure-setup.adoc: requestedAccessTokenVersion decides whether aud holds the Application ID URI (v1.0 tokens, the default) or the client id (v2.0). On openidconnect 2.4.1 that decides whether the documented setup works at all, which this walkthrough never said. Each table row records whether it was observed or taken from vendor documentation; PingFederate and cidaas are marked undetermined rather than guessed, and the ownCloud 10 page says so where it would otherwise promise that no upgrade can lock a provider out. Two review rounds folded in. The claims that a token issued to a different client is "rejected either way" and that "an attacker's own client cannot get a token through either check" were both wrong and both contradicted elsewhere in the same section: the audience comparison cannot tell which client asked for a token that names ownCloud. The recommendation to set audience *because* another client might target ownCloud was inverted for the same reason - in that scenario the key changes nothing and removes the client-claim check; it binds the resource, not the client, and the pages now say which of the two they mean. Also: the Keycloak workaround was addressed to ownCloud 10 admins who cannot run 2.4.1 at all; the Azure snippet used the client-id placeholder where the App ID URI belongs, which would have rejected every token once audience was set; and the Azure manifest path read Manifest > Manage instead of Manage > Manifest.A third round corrected the ID-token statement, which claimed to hold "independently of the audience". It does not: with audience set to anything other than the client-id - what ADFS, Azure v1.0 and OneLogin need - an ID token's aud no longer matches and the client-claim fallback is suppressed, so ID tokens are rejected. That is a real benefit of the key and the pages now list it. Also in that round: the ownCloud 11 entry's lead described 2.4.2 behaviour in the present tense while 2.4.1 is what ships; the Keycloak 2.4.2 cell said audience "cannot be used" without allowing for an install that followed the 2.4.1 instruction and added the mapper; OneLogin was missing from the admonition that lists who is affected; the Azure note called the suppressed claim "the appid fallback" where v2.0 tokens use azp; and neither page said that on 2.4.1 the check reaches JWT access tokens only, so an install using opaque tokens with introspection is not affected at all. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com> --- .../config_apps_sample_php_parameters.adoc | 55 +++++- .../user/oidc/ms-azure-setup.adoc | 35 ++++ .../pages/configuration/user/oidc/oidc.adoc | 164 +++++++++++++++++ .../config_apps_sample_php_parameters.adoc | 56 +++++- .../user/oidc/ms-azure-setup.adoc | 41 +++++ .../pages/configuration/user/oidc/oidc.adoc | 170 ++++++++++++++++++ 6 files changed, 505 insertions(+), 16 deletions(-) diff --git a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index 8fb8cf9..892378d 100644 --- a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -402,10 +402,20 @@ Limit the users which are allowed to login to a specific user backend - e.g. LDA audience:: The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access -token is accepted only if this names the resource ownCloud is being addressed as. Takes a single -non-empty string or a list of them, and *replaces* `client-id` as the expected value rather -than adding to it. The default is `client-id`, which is what most IdPs send, so set this -only if your IdP names the resource server instead. Microsoft ADFS does: it prefixes the +token is accepted only if this names the resource ownCloud is being addressed as, and no other +claim is used to identify ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` +as the expected value rather than adding to it. ++ +You do not have to set it. Without the key, an access token is accepted when its `aud` carries +the `client-id`, and also when the claim naming the client the token was issued to carries it - +`azp`, `appid` or `client_id`. That second half is what keeps identity providers working that +put the *resource server* in `aud`, which is what RFC 9068 section 3 defines it as; see +xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for what +each supported IdP sends and when setting this key is worthwhile. What neither form of the +check can tell apart is *which client* asked for a token that names ownCloud in `aud` - that is +the resource-server model, and it is why the paragraph below matters. ++ +Microsoft ADFS is one of the providers that names the resource: it prefixes the identifier of the application the token was issued for with `microsoft:identityserver:`, unless that identifier is already a URL, in which case it is sent verbatim. Read the identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group @@ -418,17 +428,46 @@ exactly, including case: the comparison is strict, so a difference in casing, or + Setting this key makes `aud` authoritative, which has three consequences worth planning for. A token issued to a *different* client of the same IdP is accepted whenever its `aud` -matches, because the `client_id` of RFC 7662 is no longer consulted - so choose a value that -only ownCloud can be issued for, and do not reuse a tenant-wide resource identifier here. +matches, because neither the `client_id` of RFC 7662 nor the other client-naming claims are +consulted any more - so choose a value that only ownCloud can be issued for, and do not reuse +a tenant-wide resource identifier here. Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 permits, because every opaque token would then be rejected. And with `exchange-token-mode-before-introspection`, the first usable entry of a list is also what the token exchange requests from the IdP, so list the resource ownCloud should be given first. + +What it buys you is a binding to the *resource*: without it, a token that ownCloud's own client +obtained for some other resource of the same IdP - an RFC 8707 `resource` parameter, or RFC 8693 +token exchange - is accepted here. Set it if your IdP issues tokens to ownCloud's client for more +than one resource. ++ +It also keeps ID tokens out, as long as the value differs from the `client-id`: an ID token's +`aud` is the `client-id`, so it stops being an accepted audience. ++ +What it cannot do is keep another *client's* tokens out. Any token whose `aud` names ownCloud is +accepted whichever client requested it, with or without this key, and a client of the same IdP can +often be granted exactly that - an audience mapper of its own, or a `resource` parameter naming +ownCloud. Which clients may be issued tokens for ownCloud is a decision to make in the IdP. ++ +Independently of the audience, a token that declares itself *not* to be an access token is +refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), +`token_use` of `refresh` (AWS Cognito). ID tokens carry no such marker, and an ID token's `aud` +*is* the `client-id` by definition, so one presented as a bearer token is accepted while the +expected audience is the `client-id`. Setting this key to anything else - which is what the +providers that name a resource require - rejects ID tokens as a side effect, because then the +`client-id` is no longer an accepted audience. ++ Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - is discarded. If nothing usable is left, every access token is rejected and a warning naming -the offending value is written to the log. Requires the app release carrying the fix for -https://github.com/owncloud/openidconnect/issues/373[openidconnect issue 373]. +the offending value is written to the log. ++ +Available starting app version 2.3.5, which is in preparation and is also the release that +introduces the audience check on this server line - every released 2.3.x version, up to and +including 2.3.4, does not check the audience at all. Both halves therefore arrive together, so +upgrading to 2.3.5 cannot lock an IdP out and needs no configuration change for the providers +listed under xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience]. +On ownCloud 11 the check shipped one release before the override: openidconnect 2.4.1 checks the +audience, 2.4.2 adds the override. auth-params:: Additional parameters which are sent to the IdP during the auth requests diff --git a/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc b/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc index 58aaf3a..1e2c247 100644 --- a/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc +++ b/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc @@ -131,6 +131,41 @@ Use these links to see the corresponding configuration section for: ], ---- +[NOTE] +==== +Which token version your app registration issues decides what ends up in the access token's +`aud` (audience) claim. It is not part of the screens above, but the `requestedAccessTokenVersion` +property in menu:Manage[Manifest]: + +* `null` or `1` - the default - issues v1.0 tokens, whose `aud` is the *Application ID URI* you + set under menu:Expose an API[], `api://YOUR-APPLICATION-ID-SCOPE-URI` below. +* `2` issues v2.0 tokens, whose `aud` is the application's *client id*. + +Both work without further configuration on this server line: openidconnect 2.3.4 and earlier do +not check the audience at all, and 2.3.5 - in preparation - accepts the claim naming the client as +well, `appid` for v1.0 tokens and `azp` for v2.0. To have the audience enforced instead, add the +matching value: + +[source,php] +---- +'openid-connect' => [ + // ... + // v1.0 tokens (the default): the Application ID URI exactly as set under + // "Expose an API" - note this is the App ID URI itself, without the /owncloud scope + 'audience' => 'api://YOUR-APPLICATION-ID-SCOPE-URI', + // v2.0 tokens: the client id + // 'audience' => 'YOUR-CLIENT-ID', +], +---- + +Take the value from menu:Expose an API[Application ID URI] verbatim: setting `audience` also +switches off the claim that names the client - `appid` in v1.0 tokens, `azp` in v2.0 - so a value +that does not match exactly rejects every access token. Decode one access token and read its `ver` claim to see which version your tenant issues. +See +xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for the +details and for the openidconnect versions this applies to. +==== + If you want to let ownCloud create users which are not present during a OIDC authentication, replace + `'auto-provision' => ['enabled' => false],` with: diff --git a/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc b/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc index d00365e..3155718 100644 --- a/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc +++ b/content/server/10.16/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc @@ -131,6 +131,170 @@ IMPORTANT: If you use the `.htaccess` file in the ownCloud web root, you have to . Once service discovery is available as described above, the ownCloud clients will attempt to connect via OpenID Connect. +[#access-token-audience] +== Access Token Audience + +An access token is accepted only if it names ownCloud. On this server line that check arrives with +openidconnect **2.3.5**, which is in preparation: + +* every released version up to and including **2.3.4** does not check the audience at all; +* **2.3.5** accepts a token whose `aud` (audience) claim carries the configured `client-id`, or + whose `azp`, `appid` or `client_id` claim does, and adds the `audience` parameter for declaring + what your provider really puts in `aud`. It checks both JWT access tokens and opaque ones + verified through a token introspection endpoint, where the response's `client_id` counts the + same way. + +Both halves therefore arrive together here, which is why the upgrade needs no configuration change +for any provider whose shape is known - see the table. An access token's `aud` identifies the +*resource server* the token was minted for (RFC 9068 section 3), not the client that asked for it, +so many providers never put the ownCloud `client-id` there; accepting the client-naming claim is +what keeps those working. + +[NOTE] +==== +The ownCloud 11 line got the two halves in separate releases: openidconnect 2.4.1 checks the +audience and has no way to override it, so a provider that does not put the `client-id` into `aud` +cannot authenticate at all there, and 2.4.2 is the fix. If you also run an ownCloud 11 instance on +2.4.1, that is where "login works, the first click logs me out" comes from. +==== + +[width="100%",cols="17%,30%,27%,26%",options="header"] +|=== +| Identity provider +| What lands in the access token's `aud` +| On openidconnect 2.3.4 and earlier +| On openidconnect 2.3.5 + +| Keycloak ^1^ +| Nothing - there is no `aud` claim unless an audience mapper is configured for the client. `azp` +carries the client id. +| Works as it is - the audience is not checked. +| Works as it is. `audience` cannot be used at all here - with no `aud` claim, no value can match. + +| Microsoft Azure AD / Entra ID, v1.0 tokens ^2^ +| The Application ID URI, for example `api://`. There is no `azp`; the client is named +by `appid`. This is the default, see the +xref:configuration/user/oidc/ms-azure-setup.adoc[Azure setup page]. +| Works as it is - the audience is not checked. +| Works as it is. Set `audience` to the Application ID URI to bind tokens to ownCloud's API. + +| Microsoft Azure AD / Entra ID, v2.0 tokens ^2^ +| The resource application's client id, which is ownCloud's own `client-id` in the setup described +here. `azp` carries it as well. +| Works as it is - the audience is not checked. +| Works as it is. `audience` may be set to the `client-id`. + +| Microsoft ADFS ^2^ +| The relying party identifier, rendered as `microsoft:identityserver:` unless the +identifier is already a URL. `appid` carries the client. +| Works as it is - the audience is not checked. +| Works as it is. Set `audience` to that exact value, including case, to bind tokens to ownCloud. + +| Kopano Konnect ^3^ +| The client id. +| Works as it is - the audience is not checked. +| Works as it is. + +| OneLogin, with API authorization ^2^ +| The configured API audience URI or URIs, never the client id. `azp` carries the application id. +| Works as it is - the audience is not checked. +| Works as it is. Set `audience` to the API audience URI to bind tokens to that API. + +| PingIdentity PingFederate, cidaas +| Depends on how the access token is configured in the product - not verified by ownCloud. +| Works as it is - the audience is not checked. +| Decode one access token: it works as it is if `aud` or a client claim names ownCloud, otherwise +set `audience` to what `aud` holds. This is the one row where the upgrade can need a change. +|=== + +(1) Verified against Keycloak 26.0 with a confidential client and no audience mapper. + +(2) From the provider's own documentation and from tokens reported by administrators. + +(3) From the Konnect sources, which pass the client id as the access token audience. + +=== What the Log Says + +A rejected audience is logged with both values: + +---- +Token audience does not match the expected audience: token "aud" is "...", expected one of ["..."] +---- + +The value reported as `token "aud"` is what your provider sends, and therefore exactly what +belongs in `audience` if you decide to set it. + +From 2.3.5, a token accepted on its client-naming claim while carrying an audience you *could* +have configured - Azure AD v1.0 tokens, ADFS, OneLogin - is recorded once per verified token with +the same advice: + +---- +Access token "aud" does not name this relying party, accepted because "appid" matches the +configured client-id. To have the audience enforced, set the openid-connect "audience" config +key to ... +---- + +That warning is expected for those providers and does not indicate a broken setup; setting +`audience` is what ends it. It is not written when the token carries no audience at all, as with +Keycloak, because then there is nothing to put in the key - such a token is still accepted on its +`azp` claim, unless `audience` is configured, in which case it is rejected and logged as a +mismatch like any other. + +Independently of the audience, and from 2.3.5, a token that declares itself *not* to be an access +token is refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), +`token_use` of `refresh` (AWS Cognito). + +ID tokens carry no such marker, and an ID token's `aud` *is* the `client-id` by definition, so one +presented as a bearer token is accepted for as long as the `client-id` is an accepted audience. +Setting `audience` to anything else - which is exactly what ADFS, Azure AD v1.0 tokens and +OneLogin need - rejects ID tokens as a side effect. Where that is not an option, treat ID tokens +as credentials. + +=== When to Set the audience Parameter + +None of the providers above needs it on 2.3.5, with the one exception noted in the table. What it adds is a binding to the +*resource*: with it set, a token that ownCloud's own client obtained for some *other* resource of +the same provider - through an RFC 8707 `resource` parameter, or RFC 8693 token exchange - no +longer authenticates here. Set it if your provider issues tokens to ownCloud's client for more +than one resource. + +What it cannot do is keep *another* client's tokens out. Any token whose `aud` names ownCloud is +accepted, whichever client requested it, and a client of the same provider can often be granted +exactly that - an audience mapper of its own, or a `resource` parameter naming ownCloud. That is +the resource-server model, it is the same before and after setting `audience`, and the only place +to control it is the provider: decide there which clients may be issued tokens for ownCloud. + +See +xref:configuration/server/config_apps_sample_php_parameters.adoc#app-openid-connect-oidc[the parameter reference] +for the exact semantics of the key. + +[#making-keycloak-send-an-audience] +=== Making Keycloak Send an Audience + +Keycloak leaves `aud` out of the access token unless the client has an audience mapper. Adding one +makes Keycloak send the client id. Nothing on this server line requires it: no released version +checks the audience, and 2.3.5 accepts Keycloak's `azp` without a mapper. Add it only if you want +to set `audience` afterwards and have the audience enforced. + +In the Keycloak admin console, go to menu:Clients[your ownCloud client > Client scopes] and open +the dedicated scope named `-dedicated`, then +menu:Add mapper[By configuration > Audience] and set *Included Client Audience* to your ownCloud +client, leaving *Add to access token* on. + +The same through `kcadm.sh`, with `CLIENT_UUID` the internal id from +`kcadm.sh get clients -r -q clientId= --fields id`: + +[source,bash] +---- +kcadm.sh create clients/CLIENT_UUID/protocol-mappers/models -r \ + -s name=oc-audience \ + -s protocol=openid-connect \ + -s protocolMapper=oidc-audience-mapper \ + -s 'config."included.client.audience"=' \ + -s 'config."access.token.claim"=true' +---- + +Afterwards the access token carries `"aud": ""`, and `audience` may be set to the same +value to have it enforced. + == General Example Setup All IdPs have their own setup, but often share common ways of configuring things. Although not identical, the xref:example-setup-using-kopano-konnect[Kopano Konnect] example may be a good starting point for the specific configuration of your setup. As Microsoft with Azure AD is different, it has its own example section. diff --git a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index 458779f..84fdf11 100644 --- a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -397,10 +397,21 @@ Limit the users which are allowed to log in to a specific user backend - e.g. LD audience:: The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access -token is accepted only if this names the resource ownCloud is being addressed as. Takes a single -non-empty string or a list of them, and *replaces* `client-id` as the expected value rather -than adding to it. The default is `client-id`, which is what most IdPs send, so set this -only if your IdP names the resource server instead. Microsoft ADFS does: it prefixes the +token is accepted only if this names the resource ownCloud is being addressed as, and no other +claim is used to identify ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` +as the expected value rather than adding to it. ++ +You do not have to set it - from app version 2.4.2 on. There, without the key, an access token is +accepted when its `aud` carries the `client-id`, and also when the claim naming the client the +token was issued to carries it - `azp`, `appid` or `client_id`. On 2.4.1, the current release for +ownCloud 11, only `aud` counts and this key does not exist yet. That second half is what keeps identity providers working that +put the *resource server* in `aud`, which is what RFC 9068 section 3 defines it as; see +xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for what +each supported IdP sends and when setting this key is worthwhile. What neither form of the +check can tell apart is *which client* asked for a token that names ownCloud in `aud` - that is +the resource-server model, and it is why the paragraph below matters. ++ +Microsoft ADFS is one of the providers that names the resource: it prefixes the identifier of the application the token was issued for with `microsoft:identityserver:`, unless that identifier is already a URL, in which case it is sent verbatim. Read the identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group @@ -413,17 +424,46 @@ exactly, including case: the comparison is strict, so a difference in casing, or + Setting this key makes `aud` authoritative, which has three consequences worth planning for. A token issued to a *different* client of the same IdP is accepted whenever its `aud` -matches, because the `client_id` of RFC 7662 is no longer consulted - so choose a value that -only ownCloud can be issued for, and do not reuse a tenant-wide resource identifier here. +matches, because neither the `client_id` of RFC 7662 nor the other client-naming claims are +consulted any more - so choose a value that only ownCloud can be issued for, and do not reuse +a tenant-wide resource identifier here. Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 permits, because every opaque token would then be rejected. And with `exchange-token-mode-before-introspection`, the first usable entry of a list is also what the token exchange requests from the IdP, so list the resource ownCloud should be given first. + +What it buys you is a binding to the *resource*: without it, a token that ownCloud's own client +obtained for some other resource of the same IdP - an RFC 8707 `resource` parameter, or RFC 8693 +token exchange - is accepted here. Set it if your IdP issues tokens to ownCloud's client for more +than one resource. ++ +It also keeps ID tokens out, as long as the value differs from the `client-id`: an ID token's +`aud` is the `client-id`, so it stops being an accepted audience. ++ +What it cannot do is keep another *client's* tokens out. Any token whose `aud` names ownCloud is +accepted whichever client requested it, with or without this key, and a client of the same IdP can +often be granted exactly that - an audience mapper of its own, or a `resource` parameter naming +ownCloud. Which clients may be issued tokens for ownCloud is a decision to make in the IdP. ++ +Independently of the audience, a token that declares itself *not* to be an access token is +refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), +`token_use` of `refresh` (AWS Cognito). ID tokens carry no such marker, and an ID token's `aud` +*is* the `client-id` by definition, so one presented as a bearer token is accepted while the +expected audience is the `client-id`. Setting this key to anything else - which is what the +providers that name a resource require - rejects ID tokens as a side effect, because then the +`client-id` is no longer an accepted audience. ++ Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - is discarded. If nothing usable is left, every access token is rejected and a warning naming -the offending value is written to the log. Requires the app release carrying the fix for -https://github.com/owncloud/openidconnect/issues/373[openidconnect issue 373]. +the offending value is written to the log. ++ +Available starting app version 2.4.2, which is in preparation - 2.4.1 is the current release on +this server line and has neither this key nor the client-naming acceptance. The audience check +itself arrived in 2.4.1, where the `client-id` had to appear in `aud` with no way to override it, +so an IdP that does not put it there could not authenticate at all. Once 2.4.2 is available, +upgrading needs no configuration change for the providers listed under +xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience]; until then, +see that section for what can be done on the IdP side. auth-params:: Additional parameters, which are sent to the IdP during the auth requests diff --git a/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc b/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc index ff811d3..6eaa0a1 100644 --- a/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc +++ b/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/ms-azure-setup.adoc @@ -139,6 +139,47 @@ OWNCLOUD_OPENID_CONNECT={"provider-url":"https://idp.example.net","client-id":". ], ---- +[NOTE] +==== +Which token version your app registration issues decides what ends up in the access token's +`aud` (audience) claim. It is not part of the screens above, but the `requestedAccessTokenVersion` +property in menu:Manage[Manifest]: + +* `null` or `1` - the default - issues v1.0 tokens, whose `aud` is the *Application ID URI* you + set under menu:Expose an API[], `api://YOUR-APPLICATION-ID-SCOPE-URI` below. +* `2` issues v2.0 tokens, whose `aud` is the application's *client id*. + +Which of the two works depends on the openidconnect version, and for v1.0 tokens that difference +decides whether the setup functions at all: + +* On openidconnect *2.4.1*, the current release for ownCloud 11, only `aud` identifies ownCloud, so + a registration issuing *v1.0 tokens cannot authenticate*: the login succeeds and the next request + logs the user out again. Set `requestedAccessTokenVersion` to `2`, or wait for 2.4.2. v2.0 tokens + work as they are. +* On openidconnect *2.4.2*, in preparation, both work without further configuration, because the + claim naming the client is accepted too - `appid` for v1.0 tokens, `azp` for v2.0. To have the + audience enforced instead, add the matching value: + +[source,php] +---- +'openid-connect' => [ + // ... + // v1.0 tokens (the default): the Application ID URI exactly as set under + // "Expose an API" - note this is the App ID URI itself, without the /owncloud scope + 'audience' => 'api://YOUR-APPLICATION-ID-SCOPE-URI', + // v2.0 tokens: the client id + // 'audience' => 'YOUR-CLIENT-ID', +], +---- + +Take the value from menu:Expose an API[Application ID URI] verbatim: setting `audience` also +switches off the claim that names the client - `appid` in v1.0 tokens, `azp` in v2.0 - so a value +that does not match exactly rejects every access token. Decode one access token and read its `ver` claim to see which version your tenant issues. +See +xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for the +details and for the openidconnect versions this applies to. +==== + If you want to let ownCloud create users which are not present during a OIDC authentication, replace + `'auto-provision' => ['enabled' => false],` with: diff --git a/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc b/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc index 9c55cb6..a2a7690 100644 --- a/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc +++ b/content/server/11.0/modules/admin_manual/pages/configuration/user/oidc/oidc.adoc @@ -104,6 +104,176 @@ This variable sets the following rule in the embedded `.htaccess` file on contai . Once service discovery is available as described above, the ownCloud clients will attempt to connect via OpenID Connect. +[#access-token-audience] +== Access Token Audience + +An access token is accepted only if it names ownCloud, and which claims count for that depends on +the app version: + +* openidconnect **2.4.1**, the current release on this server line, requires the configured + `client-id` to appear in the token's `aud` (audience) claim. Nothing else identifies ownCloud. +* openidconnect **2.4.2**, in preparation, additionally accepts a token whose `azp`, `appid` or + `client_id` claim carries the `client-id`, and adds the `audience` parameter for declaring what + your provider really puts in `aud`. + +On 2.4.1 the check reaches only access tokens that are JWTs. If your provider issues opaque access +tokens that ownCloud verifies through a token introspection endpoint, the audience is not checked +at all there - 2.4.2 is the release that starts checking it on that path too, and it accepts the +introspection response's `client_id` in the same way. + +That difference matters because an access token's `aud` identifies the *resource server* the token +was minted for (RFC 9068 section 3), not the client that asked for it. Plenty of providers +therefore never put the ownCloud `client-id` there, and on 2.4.1 those cannot authenticate at all: +the login itself succeeds, and the next request logs the user out again. + +[IMPORTANT] +==== +If you are seeing exactly that - login works, the first click logs you out - with Keycloak, ADFS, +Azure AD issuing v1.0 tokens, or OneLogin with API authorization, this is why. The `On 2.4.1` column below gives the options that +exist today; openidconnect 2.4.2 removes the need for every one of them. +==== + +[width="100%",cols="17%,30%,27%,26%",options="header"] +|=== +| Identity provider +| What lands in the access token's `aud` +| On openidconnect 2.4.1 +| On openidconnect 2.4.2 + +| Keycloak ^1^ +| Nothing - there is no `aud` claim unless an audience mapper is configured for the client. `azp` +carries the client id. +| **Add the audience mapper** described below. Without it no access token is accepted. +| Works as it is. Without a mapper `audience` cannot be used - with no `aud` claim, no value can +match; with the mapper added it can be set to the client id. + +| Microsoft Azure AD / Entra ID, v1.0 tokens ^2^ +| The Application ID URI, for example `api://`. There is no `azp`; the client is named +by `appid`. This is the default, see the +xref:configuration/user/oidc/ms-azure-setup.adoc[Azure setup page]. +| Switch the app registration to v2.0 tokens (`requestedAccessTokenVersion: 2` in the manifest), +which puts the client id into `aud`. +| Works as it is. Set `audience` to the Application ID URI to bind tokens to ownCloud's API. + +| Microsoft Azure AD / Entra ID, v2.0 tokens ^2^ +| The resource application's client id, which is ownCloud's own `client-id` in the setup described +here. `azp` carries it as well. +| Works as it is. +| Works as it is. `audience` may be set to the `client-id`. + +| Microsoft ADFS ^2^ +| The relying party identifier, rendered as `microsoft:identityserver:` unless the +identifier is already a URL. `appid` carries the client. +| No remedy - ADFS cannot be made to put the client id into `aud`. 2.4.2 is the fix. +| Works as it is. Set `audience` to that exact value, including case, to bind tokens to ownCloud. + +| Kopano Konnect ^3^ +| The client id. +| Works as it is. +| Works as it is. + +| OneLogin, with API authorization ^2^ +| The configured API audience URI or URIs, never the client id. `azp` carries the application id. +| No remedy through ownCloud; either stop using API authorization for this app or wait for 2.4.2. +| Works as it is. Set `audience` to the API audience URI to bind tokens to that API. + +| PingIdentity PingFederate, cidaas +| Depends on how the access token is configured in the product - not verified by ownCloud. +| If the access tokens are JWTs, decode one: it works when `aud` carries the `client-id`, and +cannot when it does not. Opaque tokens verified through introspection are not checked on 2.4.1 and +work either way. +| Works as it is if a claim names ownCloud as the client; otherwise set `audience` to what `aud` +holds. +|=== + +(1) Verified against Keycloak 26.0 with a confidential client and no audience mapper. + +(2) From the provider's own documentation and from tokens reported by administrators. + +(3) From the Konnect sources, which pass the client id as the access token audience. + +=== What the Log Says + +A rejected audience is logged with both values: + +---- +Token audience does not match the expected audience: token "aud" is "...", expected one of ["..."] +---- + +The value reported as `token "aud"` is what your provider sends, and therefore exactly what +belongs in `audience` if you decide to set it. + +From 2.4.2, a token accepted on its client-naming claim while carrying an audience you *could* +have configured - Azure AD v1.0 tokens, ADFS, OneLogin - is recorded once per verified token with +the same advice: + +---- +Access token "aud" does not name this relying party, accepted because "appid" matches the +configured client-id. To have the audience enforced, set the openid-connect "audience" config +key to ... +---- + +That warning is expected for those providers and does not indicate a broken setup; setting +`audience` is what ends it. It is not written when the token carries no audience at all, as with +Keycloak, because then there is nothing to put in the key - such a token is still accepted on its +`azp` claim, unless `audience` is configured, in which case it is rejected and logged as a +mismatch like any other. + +Independently of the audience, and from 2.4.2, a token that declares itself *not* to be an access +token is refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), +`token_use` of `refresh` (AWS Cognito). + +ID tokens carry no such marker, and an ID token's `aud` *is* the `client-id` by definition, so one +presented as a bearer token is accepted for as long as the `client-id` is an accepted audience. +Setting `audience` to anything else - which is exactly what ADFS, Azure AD v1.0 tokens and +OneLogin need - rejects ID tokens as a side effect. Where that is not an option, treat ID tokens +as credentials. + +=== When to Set the audience Parameter + +None of the providers above needs it once you run 2.4.2, with the exception noted in the last +table row. What it adds is a binding to the +*resource*: with it set, a token that ownCloud's own client obtained for some *other* resource of +the same provider - through an RFC 8707 `resource` parameter, or RFC 8693 token exchange - no +longer authenticates here. Set it if your provider issues tokens to ownCloud's client for more +than one resource. + +What it cannot do is keep *another* client's tokens out. Any token whose `aud` names ownCloud is +accepted, whichever client requested it, and a client of the same provider can often be granted +exactly that - an audience mapper of its own, or a `resource` parameter naming ownCloud. That is +the resource-server model, it is the same before and after setting `audience`, and the only place +to control it is the provider: decide there which clients may be issued tokens for ownCloud. + +See +xref:configuration/server/config_apps_sample_php_parameters.adoc#app-openid-connect-oidc[the parameter reference] +for the exact semantics of the key. + +[#making-keycloak-send-an-audience] +=== Making Keycloak Send an Audience + +Keycloak leaves `aud` out of the access token unless the client has an audience mapper. Adding one +makes Keycloak send the client id, which satisfies 2.4.1 - on that version this is not optional, +it is what makes a Keycloak setup work at all. + +In the Keycloak admin console, go to menu:Clients[your ownCloud client > Client scopes] and open +the dedicated scope named `-dedicated`, then +menu:Add mapper[By configuration > Audience] and set *Included Client Audience* to your ownCloud +client, leaving *Add to access token* on. + +The same through `kcadm.sh`, with `CLIENT_UUID` the internal id from +`kcadm.sh get clients -r -q clientId= --fields id`: + +[source,bash] +---- +kcadm.sh create clients/CLIENT_UUID/protocol-mappers/models -r \ + -s name=oc-audience \ + -s protocol=openid-connect \ + -s protocolMapper=oidc-audience-mapper \ + -s 'config."included.client.audience"=' \ + -s 'config."access.token.claim"=true' +---- + +Afterwards the access token carries `"aud": ""`, and `audience` may be set to the same +value to have it enforced. + == General Example Setup All IdPs have their own setup, but often share common ways of configuring things. Although not identical, the xref:example-setup-using-kopano-konnect[Kopano Konnect] example may be a good starting point for the specific configuration of your setup. As Microsoft with Azure AD is different, it has its own example section. From fd7fc379e888f80a07c23c52137734e58c505a3a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Thomas=20M=C3=BCller?= <323649642+oc-tmueller@users.noreply.github.com> Date: Mon, 21 Sep 2026 16:28:51 +0200 Subject: [PATCH 3/3] docs: regenerate the app parameter pages from core instead of hand-editing them MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit config_apps_sample_php_parameters.adoc is generated from core's config/config.apps.sample.php by owncloud/config-to-docs, which the audience entry on this branch did not account for: it existed only here, so the next regeneration would have deleted it. The text now lives in core - one commit per server line - and these two pages are that generator's output. Confirmed before regenerating that both pages were byte-identical to what the generator produces from their respective core branches, so this diff is exactly the new entries and nothing else, and running the generator again is a no-op. Five keys arrive with it that the app has always read and neither page documented: exchange-token-mode-before-introspection, use-access-token-introspection-for-user-info, and the three ocis-routing-policy-* keys. The audience entry is shortened in the move, from ten paragraphs to five. A config sample entry is read in the PHP file as well as in the manual, and the long-form discussion belongs in the hand-written Access Token Audience section, which this entry now links to per server version. Two things it had picked up are gone from here: the refresh-token rule, which is not a property of this key, and the ID-token consequence stated twice. Corrections from review, all verified against the app code: the 11.0 text says the 2.4.1 lockout applies to JWT access tokens (2.4.1 does not check the audience on the introspection path at all), the 10.16 text no longer claims the upgrade cannot lock anyone out (an introspection response with neither "aud" nor "client_id" is rejected), and the client-claim acceptance is version-qualified inline rather than only in the closing paragraph. oidc.adoc and ms-azure-setup.adoc are hand-authored pages and are untouched here. Co-Authored-By: Claude Opus 5 (1M context) Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com> --- .../config_apps_sample_php_parameters.adoc | 136 +++++++++-------- .../config_apps_sample_php_parameters.adoc | 137 ++++++++++-------- 2 files changed, 146 insertions(+), 127 deletions(-) diff --git a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index 892378d..f6515a9 100644 --- a/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/10.16/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -401,73 +401,45 @@ Limit the users which are allowed to login to a specific user backend - e.g. LDA (`'allowed-user-backends' ⇒ ['LDAP']`) audience:: -The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access -token is accepted only if this names the resource ownCloud is being addressed as, and no other -claim is used to identify ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` -as the expected value rather than adding to it. +The value your identity provider puts into the access token's `aud` (audience) claim. Once set, +an access token is accepted only if `aud` names it, and no other claim is used to identify +ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` as the +expected value rather than adding to it. `null` counts as not set; any other unusable value - a +number, a boolean, an empty string, an empty list - is dropped with a warning naming it, and if +nothing usable is left every access token is rejected, so a typo here fails closed. + -You do not have to set it. Without the key, an access token is accepted when its `aud` carries -the `client-id`, and also when the claim naming the client the token was issued to carries it - -`azp`, `appid` or `client_id`. That second half is what keeps identity providers working that -put the *resource server* in `aud`, which is what RFC 9068 section 3 defines it as; see -xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for what -each supported IdP sends and when setting this key is worthwhile. What neither form of the -check can tell apart is *which client* asked for a token that names ownCloud in `aud` - that is -the resource-server model, and it is why the paragraph below matters. +Available starting app version 2.3.5, which is also the release that introduces the audience +check on this server line - every released version up to and including 2.3.4 does not check the +audience at all. Without the key, 2.3.5 accepts an access token whose `aud` carries the +`client-id`, and also one whose `azp`, `appid` or `client_id` claim does - which is what keeps +providers working that put the *resource server* into `aud`, as RFC 9068 section 3 defines it. +The one shape the upgrade can lock out is an introspection response carrying neither `aud` nor +`client_id`, which RFC 7662 permits and which no `audience` value can rescue. Which provider +sends what is documented at +https://doc.owncloud.com/server/10.16/admin_manual/configuration/user/oidc/oidc.html#access-token-audience + -Microsoft ADFS is one of the providers that names the resource: it prefixes the -identifier of the application the token was issued for with `microsoft:identityserver:`, -unless that identifier is already a URL, in which case it is sent verbatim. Read the -identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group -registration, or with `Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML -relying party trust. It is often configured to the same GUID as the `client-id`, which is -why the prefixed value frequently repeats it - but it is a *resource* identifier, not the -client, and the two are free to differ. Whatever your IdP sends must be reproduced here -exactly, including case: the comparison is strict, so a difference in casing, or a numeric -`aud` against a string here, will not match. +Microsoft ADFS is one of the providers that names the resource: it prefixes the identifier of +the application the token was issued for with `microsoft:identityserver:`, unless that +identifier is already a URL, in which case it is sent verbatim. Read the identifier with +`Get-AdfsWebApiApplication` for an OpenID Connect application group registration, or with +`Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML relying party trust. It is often +configured to the same GUID as the `client-id`, which is why the prefixed value frequently +repeats it - but it is a *resource* identifier, not the client, and the two are free to differ. +Whatever your provider sends has to be reproduced here exactly, including case: the comparison is +strict, so a difference in casing, or a numeric `aud` against a string here, will not match. ++ +Setting it binds tokens to the *resource*: one that ownCloud's own client obtained for a +different resource of the same provider - through an RFC 8707 `resource` parameter or an RFC 8693 +token exchange - stops being accepted. So do ID tokens, but only where the configured value +differs from the `client-id`, since an ID token's `aud` is the `client-id` by definition. What it +does not bind is the *client*: any token whose `aud` names ownCloud is accepted whichever client +requested it. So choose a value only ownCloud can be issued a token for, do not reuse a +tenant-wide resource identifier here, and control in the provider which clients may ask for it. + -Setting this key makes `aud` authoritative, which has three consequences worth planning for. -A token issued to a *different* client of the same IdP is accepted whenever its `aud` -matches, because neither the `client_id` of RFC 7662 nor the other client-naming claims are -consulted any more - so choose a value that only ownCloud can be issued for, and do not reuse -a tenant-wide resource identifier here. Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 permits, because every opaque token would then be rejected. And with -`exchange-token-mode-before-introspection`, the first usable entry of a list is also what the -token exchange requests from the IdP, so list the resource ownCloud should be given first. -+ -What it buys you is a binding to the *resource*: without it, a token that ownCloud's own client -obtained for some other resource of the same IdP - an RFC 8707 `resource` parameter, or RFC 8693 -token exchange - is accepted here. Set it if your IdP issues tokens to ownCloud's client for more -than one resource. -+ -It also keeps ID tokens out, as long as the value differs from the `client-id`: an ID token's -`aud` is the `client-id`, so it stops being an accepted audience. -+ -What it cannot do is keep another *client's* tokens out. Any token whose `aud` names ownCloud is -accepted whichever client requested it, with or without this key, and a client of the same IdP can -often be granted exactly that - an audience mapper of its own, or a `resource` parameter naming -ownCloud. Which clients may be issued tokens for ownCloud is a decision to make in the IdP. -+ -Independently of the audience, a token that declares itself *not* to be an access token is -refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), -`token_use` of `refresh` (AWS Cognito). ID tokens carry no such marker, and an ID token's `aud` -*is* the `client-id` by definition, so one presented as a bearer token is accepted while the -expected audience is the `client-id`. Setting this key to anything else - which is what the -providers that name a resource require - rejects ID tokens as a side effect, because then the -`client-id` is no longer an accepted audience. -+ -Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - -is discarded. If nothing usable is left, every access token is rejected and a warning naming -the offending value is written to the log. -+ -Available starting app version 2.3.5, which is in preparation and is also the release that -introduces the audience check on this server line - every released 2.3.x version, up to and -including 2.3.4, does not check the audience at all. Both halves therefore arrive together, so -upgrading to 2.3.5 cannot lock an IdP out and needs no configuration change for the providers -listed under xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience]. -On ownCloud 11 the check shipped one release before the override: openidconnect 2.4.1 checks the -audience, 2.4.2 adds the override. +`exchange-token-mode-before-introspection`, the first usable entry is also the audience the +token exchange requests, so list the resource ownCloud should be given first. auth-params:: Additional parameters which are sent to the IdP during the auth requests @@ -490,6 +462,20 @@ auto-update:: When using the provisioning mode `auto-update`, user account info will update with the current information provided by the OpenID Connect provider upon each user log in. +exchange-token-mode-before-introspection:: +If set, an RFC 8693 token exchange is performed before a token is introspected, and the +exchanged token is what gets verified. The value selects the subject token type: `refresh-token` +uses `urn:ietf:params:oauth:token-type:refresh_token`, and `access-token` - which is also what +any other value falls back to - uses `urn:ietf:params:oauth:token-type:access_token`. The +subject is taken from the OpenID Connect login session while a session is being verified, so it +requires such a session: on a request authenticated by a bearer token alone there is none, and +the exchange is attempted with an empty subject token and fails. When user information is read +the subject is the client's current refresh or access token instead, and only if +`use-access-token-introspection-for-user-info` is set as well. The audience requested for the +exchanged token is the first usable entry of `audience`, the `client-id` when `audience` is not +set, and none at all when `audience` is set but holds nothing usable. Use one of the two values +or leave the key out: an empty or otherwise falsy value is not treated alike on both paths. + insecure:: Boolean value (`true`/`false`), no SSL verification will take place when talking to the IdP - **DO NOT use in production!** @@ -509,6 +495,20 @@ mode:: This is the attribute in the owncloud accounts table to search for users. The default value is `email`. The alternative value is: `userid`. +ocis-routing-policy-claim:: +For setups that run ownCloud Classic and ownCloud Infinite Scale side by side behind a proxy: +the user information claim that holds the routing policy. After a successful login, when the +claim is present, its value is written to the cookie named by `ocis-routing-policy-cookie` so +that the proxy can route the user to the right backend. The default is `ocis.routing.policy`. + +ocis-routing-policy-cookie:: +Name of the cookie the value of `ocis-routing-policy-claim` is written to. The default is +`owncloud-selector`. + +ocis-routing-policy-cookie-directives:: +Directives appended to the routing policy cookie, for example to scope or to secure it. The +default is `path=/;`. + post_logout_redirect_uri:: A given URL where the IdP should redirect to after logout. @@ -541,9 +541,19 @@ Client ID to be used with the token introspection endpoint. token-introspection-endpoint-client-secret:: Client secret to be used with the token introspection endpoint. +use-access-token-introspection-for-user-info:: +If set to `true`, user information is read from the token introspection response instead of from +the userinfo endpoint, using `token-introspection-endpoint-client-id` and +`token-introspection-endpoint-client-secret` and honouring +`exchange-token-mode-before-introspection`. `use-access-token-payload-for-user-info` is +evaluated first and wins for a JWT access token; an opaque token has no payload to read, so the +introspection response is used even when both keys are set. + use-access-token-payload-for-user-info:: If set to `true` any user information will be read from the access token. -If set to `false` the userinfo endpoint is used (starting app version 1.1.0). +If set to `false` the userinfo endpoint is used (starting app version 1.1.0) - unless +`use-access-token-introspection-for-user-info` is set, which takes the introspection response +instead. === Easy setup diff --git a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc index 84fdf11..f7d8643 100644 --- a/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc +++ b/content/server/11.0/modules/admin_manual/pages/configuration/server/config_apps_sample_php_parameters.adoc @@ -396,74 +396,45 @@ Limit the users which are allowed to log in to a specific user backend - e.g. LD (`'allowed-user-backends' ⇒ ['LDAP']`) audience:: -The value your IdP puts into the access token's `aud` (audience) claim. Once set, an access -token is accepted only if this names the resource ownCloud is being addressed as, and no other -claim is used to identify ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` -as the expected value rather than adding to it. +The value your identity provider puts into the access token's `aud` (audience) claim. Once set, +an access token is accepted only if `aud` names it, and no other claim is used to identify +ownCloud. Takes a single non-empty string or a list of them, and *replaces* `client-id` as the +expected value rather than adding to it. `null` counts as not set; any other unusable value - a +number, a boolean, an empty string, an empty list - is dropped with a warning naming it, and if +nothing usable is left every access token is rejected, so a typo here fails closed. + -You do not have to set it - from app version 2.4.2 on. There, without the key, an access token is -accepted when its `aud` carries the `client-id`, and also when the claim naming the client the -token was issued to carries it - `azp`, `appid` or `client_id`. On 2.4.1, the current release for -ownCloud 11, only `aud` counts and this key does not exist yet. That second half is what keeps identity providers working that -put the *resource server* in `aud`, which is what RFC 9068 section 3 defines it as; see -xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience] for what -each supported IdP sends and when setting this key is worthwhile. What neither form of the -check can tell apart is *which client* asked for a token that names ownCloud in `aud` - that is -the resource-server model, and it is why the paragraph below matters. +Available starting app version 2.4.2. Without the key, 2.4.2 accepts an access token whose `aud` +carries the `client-id`, and also one whose `azp`, `appid` or `client_id` claim does - which is +what keeps providers working that put the *resource server* into `aud`, as RFC 9068 section 3 +defines it. App version 2.4.1 has neither this key nor that fallback: there the `client-id` had +to appear in `aud`, so a provider naming the resource instead could not authenticate at all with +JWT access tokens. 2.4.1 does not check the audience of an opaque token verified through an +introspection endpoint; 2.4.2 checks both. Which provider sends what, and what can be done on +2.4.1, is documented at +https://doc.owncloud.com/server/11.0/admin_manual/configuration/user/oidc/oidc.html#access-token-audience + -Microsoft ADFS is one of the providers that names the resource: it prefixes the -identifier of the application the token was issued for with `microsoft:identityserver:`, -unless that identifier is already a URL, in which case it is sent verbatim. Read the -identifier with `Get-AdfsWebApiApplication` for an OpenID Connect application group -registration, or with `Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML -relying party trust. It is often configured to the same GUID as the `client-id`, which is -why the prefixed value frequently repeats it - but it is a *resource* identifier, not the -client, and the two are free to differ. Whatever your IdP sends must be reproduced here -exactly, including case: the comparison is strict, so a difference in casing, or a numeric -`aud` against a string here, will not match. +Microsoft ADFS is one of the providers that names the resource: it prefixes the identifier of +the application the token was issued for with `microsoft:identityserver:`, unless that +identifier is already a URL, in which case it is sent verbatim. Read the identifier with +`Get-AdfsWebApiApplication` for an OpenID Connect application group registration, or with +`Get-AdfsRelyingPartyTrust` for a legacy WS-Federation or SAML relying party trust. It is often +configured to the same GUID as the `client-id`, which is why the prefixed value frequently +repeats it - but it is a *resource* identifier, not the client, and the two are free to differ. +Whatever your provider sends has to be reproduced here exactly, including case: the comparison is +strict, so a difference in casing, or a numeric `aud` against a string here, will not match. ++ +Setting it binds tokens to the *resource*: one that ownCloud's own client obtained for a +different resource of the same provider - through an RFC 8707 `resource` parameter or an RFC 8693 +token exchange - stops being accepted. So do ID tokens, but only where the configured value +differs from the `client-id`, since an ID token's `aud` is the `client-id` by definition. What it +does not bind is the *client*: any token whose `aud` names ownCloud is accepted whichever client +requested it. So choose a value only ownCloud can be issued a token for, do not reuse a +tenant-wide resource identifier here, and control in the provider which clients may ask for it. + -Setting this key makes `aud` authoritative, which has three consequences worth planning for. -A token issued to a *different* client of the same IdP is accepted whenever its `aud` -matches, because neither the `client_id` of RFC 7662 nor the other client-naming claims are -consulted any more - so choose a value that only ownCloud can be issued for, and do not reuse -a tenant-wide resource identifier here. Do not set the key at all if your token introspection response omits `aud`, which RFC 7662 permits, because every opaque token would then be rejected. And with -`exchange-token-mode-before-introspection`, the first usable entry of a list is also what the -token exchange requests from the IdP, so list the resource ownCloud should be given first. -+ -What it buys you is a binding to the *resource*: without it, a token that ownCloud's own client -obtained for some other resource of the same IdP - an RFC 8707 `resource` parameter, or RFC 8693 -token exchange - is accepted here. Set it if your IdP issues tokens to ownCloud's client for more -than one resource. -+ -It also keeps ID tokens out, as long as the value differs from the `client-id`: an ID token's -`aud` is the `client-id`, so it stops being an accepted audience. -+ -What it cannot do is keep another *client's* tokens out. Any token whose `aud` names ownCloud is -accepted whichever client requested it, with or without this key, and a client of the same IdP can -often be granted exactly that - an audience mapper of its own, or a `resource` parameter naming -ownCloud. Which clients may be issued tokens for ownCloud is a decision to make in the IdP. -+ -Independently of the audience, a token that declares itself *not* to be an access token is -refused with `Token is not an access token`: `typ` of `Refresh` or `Offline` (Keycloak), -`token_use` of `refresh` (AWS Cognito). ID tokens carry no such marker, and an ID token's `aud` -*is* the `client-id` by definition, so one presented as a bearer token is accepted while the -expected audience is the `client-id`. Setting this key to anything else - which is what the -providers that name a resource require - rejects ID tokens as a side effect, because then the -`client-id` is no longer an accepted audience. -+ -Anything that cannot be an audience - a number, a boolean, an empty string, an empty list - -is discarded. If nothing usable is left, every access token is rejected and a warning naming -the offending value is written to the log. -+ -Available starting app version 2.4.2, which is in preparation - 2.4.1 is the current release on -this server line and has neither this key nor the client-naming acceptance. The audience check -itself arrived in 2.4.1, where the `client-id` had to appear in `aud` with no way to override it, -so an IdP that does not put it there could not authenticate at all. Once 2.4.2 is available, -upgrading needs no configuration change for the providers listed under -xref:configuration/user/oidc/oidc.adoc#access-token-audience[Access Token Audience]; until then, -see that section for what can be done on the IdP side. +`exchange-token-mode-before-introspection`, the first usable entry is also the audience the +token exchange requests, so list the resource ownCloud should be given first. auth-params:: Additional parameters, which are sent to the IdP during the auth requests @@ -486,6 +457,20 @@ auto-update:: When using the provisioning mode `auto-update`, user account info will update with the current information provided by the OpenID Connect provider upon each user login. +exchange-token-mode-before-introspection:: +If set, an RFC 8693 token exchange is performed before a token is introspected, and the +exchanged token is what gets verified. The value selects the subject token type: `refresh-token` +uses `urn:ietf:params:oauth:token-type:refresh_token`, and `access-token` - which is also what +any other value falls back to - uses `urn:ietf:params:oauth:token-type:access_token`. The +subject is taken from the OpenID Connect login session while a session is being verified, so it +requires such a session: on a request authenticated by a bearer token alone there is none, and +the exchange is attempted with an empty subject token and fails. When user information is read +the subject is the client's current refresh or access token instead, and only if +`use-access-token-introspection-for-user-info` is set as well. The audience requested for the +exchanged token is the first usable entry of `audience`, the `client-id` when `audience` is not +set, and none at all when `audience` is set but holds nothing usable. Use one of the two values +or leave the key out: an empty or otherwise falsy value is not treated alike on both paths. + insecure:: Boolean value (`true`/`false`), no SSL verification will take place when talking to the IdP - **DO NOT use in production!** @@ -505,6 +490,20 @@ mode:: This is the attribute in the owncloud accounts table to search for users. The default value is `email`. The alternative value is: `userid`. +ocis-routing-policy-claim:: +For setups that run ownCloud Classic and ownCloud Infinite Scale side by side behind a proxy: +the user information claim that holds the routing policy. After a successful login, when the +claim is present, its value is written to the cookie named by `ocis-routing-policy-cookie` so +that the proxy can route the user to the right backend. The default is `ocis.routing.policy`. + +ocis-routing-policy-cookie:: +Name of the cookie the value of `ocis-routing-policy-claim` is written to. The default is +`owncloud-selector`. + +ocis-routing-policy-cookie-directives:: +Directives appended to the routing policy cookie, for example to scope or to secure it. The +default is `path=/;`. + post_logout_redirect_uri:: A given URL where the IdP should redirect to after logout. @@ -537,9 +536,19 @@ Client ID to be used with the token introspection endpoint. token-introspection-endpoint-client-secret:: Client secret to be used with the token introspection endpoint. +use-access-token-introspection-for-user-info:: +If set to `true`, user information is read from the token introspection response instead of from +the userinfo endpoint, using `token-introspection-endpoint-client-id` and +`token-introspection-endpoint-client-secret` and honouring +`exchange-token-mode-before-introspection`. `use-access-token-payload-for-user-info` is +evaluated first and wins for a JWT access token; an opaque token has no payload to read, so the +introspection response is used even when both keys are set. + use-access-token-payload-for-user-info:: If set to `true`, any user information will be read from the access token. -If set to `false`, the userinfo endpoint is used (starting app version 1.1.0). +If set to `false`, the userinfo endpoint is used (starting app version 1.1.0) - unless +`use-access-token-introspection-for-user-info` is set, which takes the introspection response +instead. === Easy setup