calm-hub-ui/src/authService.tsx:82 sends the ID token (id_token), not the access token, as the Authorization: Bearer credential for every configured OIDC provider — not just Entra ID. PluginAuthResource's VS Code plugin callback page reuses the same preference (data.id_token||data.access_token).
An ID token is an authentication receipt scoped to the client that requested it, consumed once at login — not an authorization credential for a resource server. Sending it as a bearer token on every API call is the anti-pattern the OAuth 2.0 Security Best Current Practice (RFC 9700) specifically warns against: its aud claim identifies the client, not the API.
This works today only because quarkus.oidc.token.audience/calm.oidc.token.audience in application-oidc.properties are pinned to the OIDC client ID, which happens to match the aud claim Entra ID and most generic OIDC providers put on an ID token. That match is provider behavior, not a spec guarantee — an IdP that scopes ID-token audience differently (or restricts ID-token issuance to endpoints that never see it) breaks this silently, and "does this token work" becomes "did this happen to work for the one IdP we tested."
Flagged in review on #3001 and again on #3065 (comment); scoped out of both slices to avoid redesigning the token flow mid-stack.
Fix
- Switch to
access_token as the bearer credential for calm-hub API calls; keep id_token only for populating the signed-in user's display identity in the UI.
- Confirm every configured/tested IdP (Entra ID, Keycloak, generic OIDC) issues an access token actually scoped to calm-hub's API — some generic-OIDC setups need an explicit
scope/audience configured to get one, rather than an opaque or narrowly-scoped default.
- Apply the same fix to
PluginAuthResource's callback page.
Out of scope
- Redesigning the token-exchange flow itself (PKCE, server-side vs browser-side exchange).
- Per-provider scope/audience configuration UX.
calm-hub-ui/src/authService.tsx:82sends the ID token (id_token), not the access token, as theAuthorization: Bearercredential for every configured OIDC provider — not just Entra ID.PluginAuthResource's VS Code plugin callback page reuses the same preference (data.id_token||data.access_token).An ID token is an authentication receipt scoped to the client that requested it, consumed once at login — not an authorization credential for a resource server. Sending it as a bearer token on every API call is the anti-pattern the OAuth 2.0 Security Best Current Practice (RFC 9700) specifically warns against: its
audclaim identifies the client, not the API.This works today only because
quarkus.oidc.token.audience/calm.oidc.token.audienceinapplication-oidc.propertiesare pinned to the OIDC client ID, which happens to match theaudclaim Entra ID and most generic OIDC providers put on an ID token. That match is provider behavior, not a spec guarantee — an IdP that scopes ID-token audience differently (or restricts ID-token issuance to endpoints that never see it) breaks this silently, and "does this token work" becomes "did this happen to work for the one IdP we tested."Flagged in review on #3001 and again on #3065 (comment); scoped out of both slices to avoid redesigning the token flow mid-stack.
Fix
access_tokenas the bearer credential for calm-hub API calls; keepid_tokenonly for populating the signed-in user's display identity in the UI.scope/audience configured to get one, rather than an opaque or narrowly-scoped default.PluginAuthResource's callback page.Out of scope