Docs: document the access-token vs ID-token aud convention #30

Closed
opened 2026-05-25 14:19:15 +00:00 by navicore · 1 comment
Owner

Background

anz uses two different aud conventions across the two JWTs it issues:

  • Access tokenaud = "https://auth.example.com/realms/<realm>" (the realm URL, i.e. the resource-server identifier). This follows RFC 9068 (JWT Profile for OAuth 2.0 Access Tokens).
  • ID tokenaud = "<client_id>" (the relying party). This is the standard OIDC convention.

Both are correct. Both are common. But the difference is a foot-gun for anyone configuring a resource server's audience check, because the natural assumption ("aud is just the client_id") is wrong for access tokens.

Encountered

During the navinote integration I configured the resource server with audiences=["navinote-pwa", "navinote-sync"]. Every authenticated request returned 401. The fix was changing the audience allow-list to the realm URL — the navinote-server is now configured with a single audience: https://auth.navicore.tech/realms/homelab. Cost about 30 minutes of decoding tokens to figure out.

Proposed

A two-line note in docs/ARCHITECTURE.md near the JWT contract section (or in the README under "OIDC Endpoints") clarifying:

Access tokens carry the realm URL as aud (RFC 9068 convention) — the resource server should validate against this, not the client_id. ID tokens use client_id as aud per standard OIDC.

That's enough to save the next integrator.

## Background anz uses two different `aud` conventions across the two JWTs it issues: - **Access token** → `aud = "https://auth.example.com/realms/<realm>"` (the realm URL, i.e. the resource-server identifier). This follows [RFC 9068](https://www.rfc-editor.org/rfc/rfc9068) (JWT Profile for OAuth 2.0 Access Tokens). - **ID token** → `aud = "<client_id>"` (the relying party). This is the standard OIDC convention. Both are correct. Both are common. But the difference is a foot-gun for anyone configuring a resource server's audience check, because the natural assumption (\"aud is just the client_id\") is wrong for access tokens. ## Encountered During the navinote integration I configured the resource server with `audiences=["navinote-pwa", "navinote-sync"]`. Every authenticated request returned 401. The fix was changing the audience allow-list to the realm URL — the navinote-server is now configured with a single audience: `https://auth.navicore.tech/realms/homelab`. Cost about 30 minutes of decoding tokens to figure out. ## Proposed A two-line note in `docs/ARCHITECTURE.md` near the JWT contract section (or in the README under "OIDC Endpoints") clarifying: > Access tokens carry the realm URL as `aud` (RFC 9068 convention) — the resource server should validate against this, not the `client_id`. ID tokens use `client_id` as `aud` per standard OIDC. That's enough to save the next integrator.
Author
Owner

#32

https://git.navicore.tech/navicore/anz/pulls/32
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
navicore/anz#30
No description provided.