Skip to content

docs: document the OIDC audience parameter - #137

Draft
oc-tmueller wants to merge 3 commits into
mainfrom
docs/oidc-audience-parameter
Draft

oc-tmueller wants to merge 3 commits into
mainfrom
docs/oidc-audience-parameter

Conversation

@oc-tmueller

Copy link
Copy Markdown
Contributor

Documents the audience key the openidconnect app gains in owncloud/openidconnect#374 (fixes owncloud/openidconnect#373).

Why

An access token is accepted only if it names the ownCloud relying party. Until now the expected value was always the configured client-id — which is the ID token rule (OpenID Connect Core 1.0 §2), not the access-token one. RFC 9068 §3 defines an access token's aud as the resource server, so providers that address the resource send something else entirely and could not authenticate.

Microsoft ADFS is the case that made this necessary. It takes the identifier of the relying party trust and prefixes it with microsoft:identityserver: unless that identifier is already a URL — so an admin whose relying party identifier is their client-id GUID sees aud: "microsoft:identityserver:<guid>" and has nothing to configure. ADFS is already listed as a supported IdP in configuration/user/oidc/oidc.adoc, so this gap was reachable from a documented setup.

What

Adds an audience:: entry to the OIDC parameter reference in config_apps_sample_php_parameters.adoc (the page both oidc.adoc and kopano-setup.adoc xref for the full key list), for the two versions the app change ships on:

  • content/server/11.0/... — app 2.4.2
  • content/server/10.16/... — app 2.3.5

The entry covers the default (client-id), the accepted shapes, that it replaces rather than extends the client-id, and how to find the ADFS value.

It deliberately documents the operational consequences rather than just the syntax, because each one is a way to take an instance offline:

  • Once set, the audience becomes authoritative, so a token issued to a different client of the same IdP is accepted whenever its aud matches — the RFC 7662 client_id check no longer applies. The entry says to pick a value only ownCloud can be issued for, and not to reuse a tenant-wide resource identifier.
  • It must not be set at all if the token introspection response omits aud (which RFC 7662 permits), because every opaque token would then be rejected.
  • With exchange-token-mode-before-introspection, the first usable list entry is also what the token exchange requests from the IdP.
  • Values that cannot be an audience (number, boolean, empty string, empty list) are discarded, and if none is left every access token is rejected.

Two deliberate choices:

Both ADFS 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 — as the first draft did — would strand admins whose OIDC registration is an application group, which is the modern default on ADFS 2016+.

No app version is claimed. Neither release carrying the fix is tagged yet (tags stop at v2.4.1; the oC11 change sits under ## [Unreleased]), so the entry points at the issue instead of asserting a number that could be off by a release.

Placed alphabetically between allowed-user-backends and auth-params. The two files' entries are byte-identical.

Checks

Full local CI equivalent — npm ci, npm run antora, npm test — all pass, and the built pages carry the entry in both versions.

Rendered output confirms the entry is a proper definition term in the right position:

<dt class="hdlist1">allowed-user-backends</dt>
<dt class="hdlist1">audience</dt>            <- new
<dt class="hdlist1">auth-params</dt>

Note this is the file's first use of + list continuation (the entry needs three paragraphs). Verified in the rendered HTML that all three attach to audience and that every following term still parses as its own <dt>.

Every factual claim was checked line-by-line against the implementation in owncloud/openidconnect#374 rather than against its README alone.

Docs-only; no nav or xref changes.

🤖 Generated with Claude Code

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) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
LukasHirt
LukasHirt previously approved these changes Sep 18, 2026
@oc-tmueller
oc-tmueller marked this pull request as draft September 18, 2026 11:07
@oc-tmueller

Copy link
Copy Markdown
Contributor Author

Converted to draft — do not merge as-is. I authored this by hand, and this page is generated.

config_apps_sample_php_parameters.adoc is produced by config-to-docs from config/config.apps.sample.php in owncloud/core, which says so itself:

This file is also used to generate the configuration documentation using config-to-docs.

// header end do not delete or edit this line on line 25 is the generator's sentinel: everything above it is carried over from the existing file, everything below is rewritten wholesale. My audience:: entry is at line 398, so the next ctd.sh run would silently delete it. That this rendered correctly and passed CI is exactly the problem.

The pipeline is live, not dead — core changelog 11.0.0_2026-07-30/41668 removed the msteamsbridge sample block "so the config-to-docs sync no longer re-adds the parameters to the admin documentation", and the published pages track their core branch exactly (10.16 still has Microsoft Teams Bridge and the older "sub-keys" spelling; 11.0 has neither).

What this PR becomes

  1. The key gets documented in config/config.apps.sample.php on core master and 10.16 — waiting on fix: let the expected access token audience be configured openidconnect#374 merging and a release being cut, so the entry can name a version in that file's own style rather than linking an issue.
  2. This PR is then regenerated, so its diff is byte-for-byte generator output and the next regeneration is a no-op.
  3. The narrative half — the three ways setting audience can lock an instance out, and the exchange-token-mode-before-introspection interaction — moves to hand-authored configuration/user/oidc/oidc.adoc, which can xref properly. The generated entry keeps the inventory: name, default, accepted shapes, the AD FS derivation rule.

Verified in the meantime: both pages here are currently byte-in-sync with core, so the regeneration will add only the new entry and no unrelated drift.

Two side findings, each getting its own PR: 10.16's sibling page config_sample_php_parameters.adoc is stale by a web-updater.enabled block, and ctd.sh still writes to owncloud/docs-server — archived last month — at the pre-monorepo path.

kw-tmueller pushed a commit to owncloud/openidconnect that referenced this pull request Sep 21, 2026
The README claimed that because another client of the same IdP can often get
ownCloud's identifier into "aud" - its own audience mapper, an RFC 8707 resource
parameter - the "audience" key is worth setting for that reason too. That is
backwards. In exactly that scenario the token's "aud" names ownCloud, so it is
accepted whether or not the key is set; setting it changes nothing there, and
additionally stops the client-naming claims from being consulted.

What the key actually buys is a binding to the resource: a token ownCloud's own
client obtained for some *other* resource stops authenticating here. Which clients
an IdP may issue ownCloud-audience tokens to is a decision in the IdP, and no
ownCloud setting can override it.

The three paragraphs are rewritten as one sequence - what it buys, what it does not,
what holds when it is unset - rather than two passes over the same two facts, and the
Keycloak sentence now sits in the paragraph it belongs to instead of running into the
end of the previous one. Same content on the admin manual side
(owncloud/docs.owncloud.com#137).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
kw-tmueller pushed a commit to owncloud/openidconnect that referenced this pull request Sep 21, 2026
The README claimed that because another client of the same IdP can often get
ownCloud's identifier into "aud" - its own audience mapper, an RFC 8707 resource
parameter - the "audience" key is worth setting for that reason too. That is
backwards. In exactly that scenario the token's "aud" names ownCloud, so it is
accepted whether or not the key is set; setting it changes nothing there, and
additionally stops the client-naming claims from being consulted.

What the key actually buys is a binding to the resource: a token ownCloud's own
client obtained for some *other* resource stops authenticating here. Which clients
an IdP may issue ownCloud-audience tokens to is a decision in the IdP, and no
ownCloud setting can override it. Documented as such now, on the admin manual side
too (owncloud/docs.owncloud.com#137).

Co-Authored-By: Claude Opus 5 (1M context) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
(cherry picked from commit 2928e3c69eeaa613cfa25314efda09add16257ad)
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) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
…iting them

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) <noreply@anthropic.com>
Signed-off-by: Thomas Müller <323649642+oc-tmueller@users.noreply.github.com>
@oc-tmueller

Copy link
Copy Markdown
Contributor Author

The parameter-reference pages in this PR are now generator output, not hand-edited: the audience text lives in core's config/config.apps.sample.php (owncloud/core#41846 for master → the 11.0 tree, owncloud/core#41847 for 10.16), and config-to-docs produced these two pages from those branches.

That matters because config_apps_sample_php_parameters.adoc is generated — the entry as it stood existed only here, so the next regeneration would have deleted it.

Verified before regenerating that both pages were byte-identical to what the generator produces from their respective core branches, so the diff here is exactly the new entries. Five keys arrive with audience 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.

oidc.adoc and ms-azure-setup.adoc are hand-authored and unchanged by that step. This PR should merge after the two core PRs, so the pages and their source land together.

This branch has not been deployed

No deployments
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

AD FS access token audience validation fails with microsoft:identityserver:<client-id>

2 participants