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..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 @@ -400,6 +400,47 @@ 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 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. ++ +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 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. ++ +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 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 @@ -421,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!** @@ -440,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. @@ -472,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/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 56eefb2..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 @@ -395,6 +395,47 @@ 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 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. ++ +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 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. ++ +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 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 @@ -416,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!** @@ -435,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. @@ -467,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 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.