Skip to content

feat(auth): accept an identity provider access token on the API for native clients - #1914

Merged
cevheri merged 3 commits into
mainfrom
feat/oidc-bearer-access-token
Oct 22, 2026
Merged

cevheri merged 3 commits into
mainfrom
feat/oidc-bearer-access-token

Conversation

@cevheri

@cevheri cevheri commented Oct 21, 2026 •

Copy link
Copy Markdown
Member

A native client (a mobile app) signs in to the identity provider itself as a public PKCE client and calls Studio's API with Authorization: Bearer <access token>. Until now Studio accepted only the session cookie.

Opt-in. The channel exists only while OIDC_BEARER_AUDIENCE is set under NEXT_PUBLIC_AUTH_PROVIDER=oidc. Unset, the header is ignored outside /api/mcp as before, so a proxy forwarding Basic credentials (CapRover httpAuth) changes nothing.

What it checks. An asymmetric JWT from the issuer's key set, for the audience, unexpired, not an ID token, from OIDC_BEARER_CLIENT_IDS when set, with a subject, a username and a role. A refusal answers 401 with WWW-Authenticate and is audited. A provider outage answers 503. A bearer session writes no cookie and cannot mint an MCP token. Under governance it meets the same sign-in rule as a cookie session.

Username fix, both channels. Without governance the username keys a person's saved work. It was taken from email, else preferred_username, so an unverified email or an email-shaped username chosen at the provider could open someone else's workspace. Now: email unless email_verified: false, else a preferred_username without @. Behaviour change: an account whose email the provider marks unverified is now named by its preferred_username.

Verified. Full suite, 100% line coverage, build, build:lib and attw pass locally. Live against Keycloak 26.8.1, by bearer and by browser sign-in: the mobile token runs a query; Studio's ID token, a token without the audience, one from an unlisted client and an unverified email with an email-shaped username are refused or not used.

Docs: OIDC.md, API_DOCS.md, GOVERNANCE.md, STORAGE.md, SECURITY.md (new control 1.11).

…ative clients

A native client, such as a mobile app, signs in to the identity provider
itself as a public PKCE client and calls Studio's API with
"Authorization: Bearer <access token>". Studio accepted only the session
cookie, so that could not work.

Opt-in through OIDC_BEARER_AUDIENCE under NEXT_PUBLIC_AUTH_PROVIDER=oidc.
Unset, the Authorization header is ignored outside /api/mcp as before, so
a reverse proxy that forwards Basic credentials changes nothing.

The verifier is one leaf module (src/lib/oidc-bearer.ts) that the proxy
and getSession() both call. It accepts an asymmetrically signed JWT from
the issuer's key set, for the audience, unexpired, not an ID token, from
OIDC_BEARER_CLIENT_IDS when set, naming a subject, an email or username
and a role. A refusal is 401 with the bearer challenge and is audited; a
provider that cannot be reached is 503. A bearer session sets and clears
no cookie and cannot mint an MCP token. Under governance the access
token meets the same sign-in rule as a cookie session.
@codecov

codecov Bot commented Oct 21, 2026

Copy link
Copy Markdown

Codecov Report

✅ All modified and coverable lines are covered by tests.

📢 Thoughts on this report? Let us know!

…n copy

Without governance the session's username keys the person's saved work,
rate limit and audit lines. Both the OIDC callback and the bearer channel
took it from email, else preferred_username, so a user who could set an
unverified email or an email-shaped username at the provider could open
another person's workspace.

oidcUsername() (src/lib/oidc-identity.ts) now takes the email unless the
provider marks it unverified (email_verified: false), else a
preferred_username without an "@". The callback still falls back to sub;
the bearer channel refuses the token.

Behaviour change: an account whose email the provider marks unverified is
now named by its preferred_username.
…ified

Some providers send email_verified as a string. Compared only with the
boolean false, "false" counted as verified, so an unverified email could
still become the username. A provider that sends no email_verified, such
as Microsoft Entra ID, is unchanged.
@sonarqubecloud

Copy link
Copy Markdown

@cevheri
cevheri merged commit dbfea26 into main Oct 22, 2026
53 of 54 checks passed
@cevheri
cevheri deleted the feat/oidc-bearer-access-token branch October 30, 2026 19:13
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.

1 participant