Support OAuth2-only providers for OpenID login (providers that cannot issue an id_token) #15457
mkroemer
started this conversation in
Feature Requests & Suggestions
Replies: 2 comments
|
Can I open a PR? |
0 replies
|
Opened as #16410, rebased on current Since the proposal, the OAuth2-only path also verifies |
0 replies
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment
Uh oh!
There was an error while loading. Please reload this page.
What feature would you like to see added?
An
OPENID_USE_OAUTH2mode that authenticates against explicitly configured OAuth 2.0 endpointsinstead of OIDC discovery, so providers that expose OAuth 2.0 but will not issue an
id_tokentothird-party clients can be used for login.
The problem
setupOpenId()requires OIDC discovery and a validatedid_token. Some providers cannot supplyone to third-party apps at all, which makes them impossible to use for LibreChat login today —
not misconfigured, just unreachable.
Atlassian is the concrete case, and it fails in a way that is easy to misread as a config error:
permission in the developer console offers exactly
read:meandread:account— no OIDCscopes are selectable, for existing apps or newly created ones.
auth.atlassian.comdoes publish a discovery document advertisingopenid,profileandemailinscopes_supported— but that appears to be the identity platform's stock outputrather than a description of what a 3LO app may request. The document exposes
mfa_challenge_endpoint,device_authorization_endpointand/oidc/register, so theauthorization server itself speaks full OIDC; the 3LO app-registration layer in front of it is
what does not offer those scopes.
documents the token response as
access_token,expires_inandscope— noid_token— anddoes not mention
openidor OpenID Connect anywhere.complete. In my testing, requesting
openid profile emailfrom a 3LO app failed at Atlassian'sconsent screen before the callback was reached; requesting the API scopes it will grant means
no
id_tokencomes back foropenid-clientto validate.I should be candid about the limits of that evidence: I could not find an Atlassian statement
explicitly ruling out the
openidscope for 3LO apps, so the above is convergent evidence(documented token response, developer-console scope list, and an observed consent failure) rather
than a quotable prohibition. Note also that Atlassian's own OpenID Connect documentation covers
Data Center and Access acting as a relying party consuming an external IdP — the opposite
direction from what this needs.
Identity for such providers also lives at a provider-specific endpoint —
https://api.atlassian.com/mefor Atlassian — rather than a standard
userinfo_endpoint.Every documented provider today (Apple, Auth0, Authelia, Authentik, AWS, Azure, Discord, Keycloak)
is a full OIDC provider, so this gap has not come up in the existing guides.
Prior art worth noting:
passport-atlassian-oauth2(2019) solves this outside LibreChat with exactly the same shape —
auth.atlassian.com/authorize,auth.atlassian.com/oauth/token, a profile fetched fromapi.atlassian.com/me, anaudienceparameter, and no
openidscope orid_tokenanywhere in its source. That a dedicated Atlassianstrategy arrived at this independently is part of why I think the generic version belongs upstream
rather than as a per-provider package.
I could not find an existing request for this. The closest prior art I found is
Discussion #8986, which is a different
problem — a refresh response dropping the
id_tokenwhenOPENID_REUSE_TOKENS=true— though itdoes show the OIDC path assuming
id_tokenavailability. This proposal does not fix that, and thetwo are independent.
Proposed implementation
Only the transport differs. The result feeds into the same
processOpenIDAuth, so userprovisioning, role sync, avatars and email-domain restrictions behave identically to the OIDC path.
packages/api/src/oauth/oauth2Login.ts(new, TypeScript)fetchOAuth2UserInfo(url, accessToken)— bearer request to the userinfo endpoint, honouring theexisting OpenID proxy dispatcher
resolveOAuth2Subject(userinfo)—sub→account_id→id, since OAuth2 userinfo endpointsare not bound by the OIDC spec and name this field differently
getMissingOAuth2LoginConfig(env)— reports every missing required variable at oncebuildOAuth2StrategyOptions(env)— maps the environment onto the strategy optionsbuildOAuth2AuthorizationParams(env)— sendsOPENID_AUDIENCEon the authorization request,using the same comma-separated "first non-empty wins" semantics as the OIDC path
api/strategies/openidStrategy.js(+84 / −2)setupOpenId()branches tosetupOpenIdOAuth2()when the mode is enabledpassport-oauth2strategy registers under the existingopenidname, so routes, buttons andcallback URLs are unchanged
processOpenIDAuth()gains an optional pre-fetched-userinfo argument, since there is noConfigurationto hand toclient.fetchUserInfoin this modeapi/package.json— declarespassport-oauth2, already present transitively viapassport-github2/passport-google-oauth20.env.example— documents the four new variablesPer the TypeScript conversion guidance, the logic is TypeScript in
/packages/apialongside theexisting
oauth/module (+123 lines), and/apigets only what has to sit next toprocessOpenIDAuthandpassport.usein the legacy JS server (+84 lines): the strategyregistration and the verify callback.
Configuration
OPENID_USE_OAUTH2=true OPENID_AUTHORIZATION_URL=https://auth.atlassian.com/authorize OPENID_TOKEN_URL=https://auth.atlassian.com/oauth/token OPENID_USERINFO_URL=https://api.atlassian.com/me OPENID_SCOPE="read:me read:account"OPENID_ISSUERis unused in this mode. If any of the three URLs is missing, setup logs which onesand returns
nullwithout registering a strategy.Which components are impacted?
Backend / authentication only. No frontend, schema or API-contract changes — the login button,
routes and callback URL are the existing OpenID ones.
Default behaviour is unchanged:
OPENID_USE_OAUTH2defaults to false and the OIDC path is untouched.Testing
I have a working branch, rebased on
dev:handling, the ignored PKCE flag, the bearer userinfo request, subject resolution and its fallbacks, userinfo
failure, missing identifier, domain rejection)
openidStrategytests pass unchanged;api/strategies/is 308/308 andpackages/api/src/oauth/is 129/129Also verified end-to-end against Atlassian with a real 3LO app: login succeeds, and the identifier
resolved this way matches the account IDs already stored in
openidIdfor users provisionedthrough that provider — so existing installs would not be stranded behind a second identity.
Open questions for maintainers
rather than as an Atlassian special case, but you may prefer it scoped differently.
setupOpenIdAdminis not wired up for this mode — it needs anopenid-clientConfiguration, which does not exist here. Leave it OIDC-only, or should OAuth2 admin login bepart of this?
/oauth/openidpassesstate: randomState()topassport.authenticate, and passport-oauth2 short-circuits on a stringstate: it puts the value on the authorization URL and never calls the state store, so anystore other than the NullStore has nothing to verify against and every callback fails. The
strategy therefore sets
state: false, which means the returned state is not checked (and PKCEis unavailable, since its verifier lives in that same store —
OPENID_USE_PKCElogs a warningand is ignored). Letting the store manage state would fix both, but that means changing the
shared route, which also feeds the OIDC strategy — I did not want to touch that unilaterally.
How would you prefer this handled?
OPENID_REUSE_TOKENSis likewise OIDC-specific and not claimed by thismode. Is that an acceptable limitation to document?
account, which I have deliberately kept out of this proposal since it is orthogonal and I saw
account linking mentioned as already planned. Happy to raise separately.
Happy to open the PR once there is a signal this is wanted, or to adjust the approach first.
All reactions