Use case — Plain OAuth 2.0
OIDC Core 1.0 §3.1.2.1 requires openid in an OIDC authorization request; without it, the request is not an OpenID request and OIDC Core does not define how the OP handles it. This library rejects such requests by default, but one option lets the same OP also serve OAuth-only clients that need an access token without an ID Token.
Specs referenced on this page
- RFC 6749 — OAuth 2.0 Authorization Framework
- RFC 6750 — OAuth 2.0 Bearer Token Usage
- OpenID Connect Core 1.0 — §3.1.2.1 (the
openidscope is the OIDC switch) - OpenID Connect Discovery 1.0 —
scopes_supported
Vocabulary refresher
- OAuth 2.0 vs OIDC — OAuth 2.0 issues
access_tokens for delegated API access — the token says "this client may call this API on this user's behalf". OIDC layers identity on top: when the request includes theopenidscope, the OP additionally returns anid_tokenthat asserts who the user is. Withoutopenid, the OP behaves as a plain OAuth 2.0 server. - Bearer token — An access token whose presentation alone proves authorisation (RFC 6750). The resource server validates it (by introspection or by JWT signature) and then enforces the granted scopes.
/userinfo— Defined by OIDC for access tokens obtained through OIDC authentication. This library also accepts a subject-bearing OAuth-only authorization-code token when its scopes allow user claims. That behavior is a library extension, not an OAuth 2.0 UserInfo standard; grantless service tokens are rejected.
Source:
examples/04-oauth2-only
The seam
op.New(
/* required options */
op.WithOpenIDScopeOptional(),
)After this option:
| Request | Behaviour |
|---|---|
scope=openid profile email | Full OIDC: id_token + userinfo + (when configured) refresh_token |
scope=api:read | OAuth 2.0: access_token, and a refresh_token when the provider and client grant gates pass; no id_token |
scope=(empty) | Rejected — at least one scope must be requested |
The choice is per request, not a global toggle. A client whose Scopes list includes openid can still request it; a client whose Scopes list omits openid stays in OAuth-only mode permanently.
Side effects
| Field | OIDC mode | OAuth-only mode |
|---|---|---|
id_token in /token response | ✅ | ❌ |
/userinfo callable with the access token | ✅ | Library extension for subject-bearing authorization-code tokens; claims still depend on granted scopes |
Discovery claims_supported | populated | unaffected |
nonce validation | enforced when present | bypassed |
auth_time claim | included when prompted | n/a |
Mutual exclusion
op.WithOpenIDScopeOptional is incompatible with this library's FAPI 2.0 profile settings: the constructor rejects the combination at build time. This is a library policy. The FAPI 2.0 Security Profile itself can be used for OAuth 2.0 requests without OIDC; OIDC applies when the client needs an authenticated user's identifier.
op.New(
op.WithProfile(profile.FAPI2Baseline),
op.WithOpenIDScopeOptional(), // ← op.New returns an error
)op.WithStrictOfflineAccess() is also incompatible (strict mode has no meaning when openid itself is optional).
When to use it
| Scenario | Pick |
|---|---|
| OAuth 2.0 only, no users (machine-to-machine) | client_credentials — WithOpenIDScopeOptional not needed |
| OAuth 2.0 + OIDC on the same OP for different RPs | This option |
| FAPI 2.0 deployment | Don't use this option |
Read next
- Tokens primer — what's in an access token vs an ID Token.
- Client Credentials — the other "no end-user" path.