Load Path Research The mechanism you have vs the property you need

Identity vs access · Default is not a control

An ID token is not an API authorization decision.

OpenID Connect Login Is Not API Authorization

Federation protocols transport authenticated assertions between parties, while authentication supplies the session and token handling constraints that keep those assertions from being reused as unrestricted authority.

“The frontend has a valid OIDC token, so the API knows the user is allowed.”

No. Not before somebody pastes an ID Token into an Authorization: Bearer header and calls it zero trust.

An ID token is not an API authorization decision.

OpenID Connect gives your client an authentication assertion.

OAuth 2.0 gives your client delegated access to a protected resource.

Your API still decides whether your request is allowed at the resource it owns.

Three different jobs.

Three different parties.

The OpenID Provider authenticates.

The relying party client receives the ID Token.

The resource server accepts an access token and enforces its own access policy.

Owning all three services doesn’t merge them into one blob of benevolent intention.

OIDC’s ID Token has five required claims: iss, sub, aud, exp, iat.

The audience contains your relying party’s client ID.

That isn’t a mysterious omission your API may generously ignore.

It is the point.

Your token says, in effect: this authentication assertion is for that client.

Accept it as a universal permission slip and you have promoted an assertion about login into authority over every resource your frontend can name.

That is a spectacularly broad interpretation of one field called aud.

1. “But the Token Was Signed by Our Identity Provider.”

Good.

Signature validation is necessary.

It isn’t authorization.

A signature lets you establish that the configured issuer made an assertion, and that your token has not been altered within your trust configuration.

SAML 2.0 says the same thing with less room for wishful thinking.

A relying party verifies the assertion signature, and does not rely on its contents when it is invalid.

That is validation of an assertion.

It is not a decision that every application downstream should permit every operation mentioned nowhere in it.

OIDC ID Tokens are JWTs.

RFC 7519 names seven registered claims: iss, sub, aud, exp, nbf, iat, jti.

OIDC requires five, and can add nonce, auth_time, acr, amr, azp and at_hash where the flow needs those facts.

More signed fields don’t turn a token into a permission table.

A notarized business card is still not a building pass.

Your API validates a credential for your API.

Trusted issuer.

Signature.

Intended audience.

Expiration.

The correlation mechanism applicable to the flow.

Your client validates its login assertion as a relying party.

Those are related checks at different trust boundaries.

Not a duplicate line item somebody can delete from a diagram.

2. “But It Contains the User’s Groups.”

Then your API has an input to authorization.

It doesn’t have authorization finished, in a small plastic wrapper.

SAML 2.0 makes the distinction unusually visible.

Its signed XML assertions can contain an Authentication statement, an Attribute statement, and an Authorization Decision statement.

Authentication says the subject authenticated by a means, at a time.

An attribute statement says the subject has an attribute.

An authorization decision describes a specified access request, and a decision.

Those are separate statement types because they are separate propositions. The specification even treats the authorization-decision statement as frozen, and points advanced decisions toward XACML.

An OIDC group claim resembles the middle case.

A signed assertion that a subject is associated with a group.

It can help your API map that subject to a bounded role.

It cannot tell your API.

Absent your API’s own policy.

Whether billing-admin may delete this invoice.

Whether support may read this tenant.

Whether an operator may change a production setting at this moment.

This is the same category error as calling an LDAP group a permission.

LDAP is a directory-access protocol.

It holds data such as cn, mail and group membership that an issuer may read before it makes an assertion.

Your data is raw material.

Your resource server’s enforcement decision is the finished control.

3. “But the API Can Read the Subject Claim.”

Of course it can.

sub is a subject identifier.

It tells you which subject the issuer asserted.

It doesn’t tell you what that subject may do.

OAuth 2.0 is precise about the roles people mush together here.

The resource owner can grant access to a protected resource.

The client makes protected-resource requests.

The authorization server issues access tokens after authenticating the resource owner and obtaining authorization.

The resource server accepts requests using access tokens.

Your API is not a parser sitting at the end of a token-shaped tube.

It is the resource server.

RFC 6749 calls OAuth 2.0 an authorization framework.

It gives a specific client a specific scope of access, for a bounded time.

It does not define a way to learn the resource owner’s identity from an access token.

OIDC adds that identity layer by issuing an ID Token when the openid scope is requested.

So sub is useful.

Log it.

Map it to your API’s subject model.

Use it as one policy input.

Just do not point at a subject identifier and announce that your object and function checks have been completed by typography.

4. “But the Client Is First-Party.”

This is where ownership gets used as a solvent for protocol boundaries.

Your frontend is still an OIDC relying party, or an OAuth client.

Your API is still an OAuth resource server.

An ID Token’s aud must contain the relying party’s client ID.

That client ID does not become your API’s audience because both deployments appear under the same corporate cloud account.

A company can own a receptionist and a data centre.

The receptionist still doesn’t get root access to the racks.

First-party clients also don’t eliminate bearer-token risk.

RFC 6750 defines a bearer token so that any party holding it can use it as any other holder can.

Usually it travels in an Authorization: Bearer header.

Possession is your model, unless a separate proof-of-possession design changes it.

Your API has no basis to assume that presentation by a browser proves an original, authorized browser, merely because the JavaScript bundle came from an internal repository.

Which is precisely why audiences exist.

Rejecting a token minted for another client, or another resource, stops one compromised credential acquiring a second meaning at a second endpoint.

A first-party label is procurement trivia.

The audience is a security property.

5. “But OAuth Scopes Are Already in the Token.”

Excellent.

Enforce them.

Then keep going.

OAuth proves that a particular client received a particular scope of delegated access, for a bounded time.

Scope is a constraint on the grant to the protected resource.

It isn’t a complete answer to “may this subject perform this operation on this object under these conditions?”

That is your API’s authorization problem, because your API knows the function and the object.

The distinction isn’t exotic.

A scope can permit access to an invoices API.

It doesn’t say which tenant’s invoice is visible.

Whether a draft can be approved.

Whether a settled invoice can be changed.

Whether the caller is acting in an emergency workflow.

Those are function, object and contextual decisions.

Putting a broad scope in a token does not pre-authorize every future route somebody adds to that API.

Groups can feed role-based access control.

Object, operation and environment attributes can feed a finer policy decision.

Your API applies those checks where it has the request in hand.

Scope comes first, as a boundary on delegated access. It does not retire your resource server from the job RFC 6749 gives it.

OAuth’s client_credentials grant makes the point more bluntly.

The client acts on its own behalf, with no resource owner involved.

If every valid token were silently a human login and a universal authorization result, that grant would be a rather impressive metaphysical achievement.

6. “But Checking Issuer, Audience, Expiry, and Authorization Is Redundant.”

No.

It is a chain.

Each link answers a different question.

Signature and issuer ask whether a trusted authority issued this artifact.

Audience asks whether that authority issued it for this recipient.

exp, and where used nbf, ask whether it is valid now.

A nonce ties an ID Token to the original OIDC request, for replay protection.

Authorization-code flow state and PKCE tie a redirect and code redemption to the initiating client.

Scope asks whether the grant reaches this protected-resource surface.

Your policy asks whether your request itself is allowed.

Take away audience validation and a token for Client A wanders into API B.

Take away expiry validation, and a credential keeps speaking after its issuer said it should stop.

Take away the code verifier, and authorization-code injection becomes possible.

RFC 7636 requires the code_verifier at redemption, so an injected code without the matching verifier is rejected.

RFC 9700, the OAuth 2.0 Security Best Current Practice published in 2025, requires exact string matching for redirect_uri validation.

It names open redirectors, mix-up attacks and authorization-code injection as the consequences of looser handling.

The same guidance says clients SHOULD NOT use the implicit grant, or response types that issue access tokens in the authorization response.

And that the resource owner password credentials grant MUST NOT be used.

Those aren’t competing opinions about duplicate checks.

They are different controls closing different failures.

7. No, This Does Not Mean OIDC and OAuth Are Broken.

They aren’t.

OIDC and OAuth are designed to work together.

Requesting openid causes OIDC to add an ID Token to OAuth’s authorization flow.

Your client receives an authentication assertion, while a protected resource can receive an access token.

The mistake isn’t using both.

The mistake is giving one artifact the other artifact’s job, and then asking your API to be grateful for the ambiguity.

Groups can inform authorization.

A service may map a group claim to a role.

A role may permit a set of operations.

None of that is wrong.

It becomes wrong when the receiving service stops validating its own credential requirements, or stops deciding about the resource and operation it owns.

Nor does a short lifetime make your boundary irrelevant.

Microsoft Entra access tokens have a randomized default lifetime of 60 to 90 minutes, averaging 75.

Tenants using Continuous Access Evaluation get a 20 to 28 hour range instead.

Those are platform behaviours, not OAuth protocol requirements.

RFC 6749 sets no access-token lifetime, though it recommends a maximum authorization-code lifetime of 10 minutes.

Lifetime limits replay exposure.

It doesn’t cause a token for one audience to become appropriate for another.

And an access token is often a bearer token.

A stolen bearer token does not become harmless because the original login used WebAuthn, a passkey or a hardware key.

Authentication protects the login event.

A token or cookie stolen afterwards is a session-management problem downstream.

Your control layers aren’t substitutes.

This entire article is, regrettably, about nouns continuing to mean different things.

8. What You Should Actually Do.

Use the ID Token for the client login session. Validate its issuer, signature, audience, expiration, and the applicable nonce or authorization-code correlation before your relying party establishes the session.

Its aud identifies that relying party client.

Do not deliver it to an API as though the API were the client it names.

Issue access tokens for named resource audiences. At every resource server, reject tokens issued for another client or resource.

Validate trusted issuer, signature, audience and expiry before you even consider scope.

Keep redirect URIs registered with exact matching, preserve state, and use PKCE.

RFC 6749 recommends redeeming an authorization code within a maximum of 10 minutes. That code is an intermediate credential, not browser decoration.

Enforce scope at the API, then enforce function and object authorization there too. Map group claims to bounded roles rather than treating group names as global permissions.

Make the object boundary explicit: tenant, account, invoice, deployment, secret, administrative setting.

A scope that reaches a service is not a decision that reaches every record in it.

Treat bearer tokens as credentials whose possession is sufficient by default. Secure transport.

Set and validate lifetimes.

Keep tokens out of places that turn them into accidental artifacts. Detect anomalous use, and support revocation where the service needs it.

A short exposure window is valuable precisely because it is short.

It isn’t a permission model.

9. The Actual Model Is Not Complicated.

SAML, OIDC and OAuth all move assertions or delegated grants between parties.

None of them dissolves the receiving party’s job.

SAML 2.0 can bind an assertion to a Recipient, a time window and an InResponseTo request.

OIDC gives your relying party an ID Token whose aud names that client.

OAuth gives your client a bounded access grant for a resource server.

In every case, a signed artifact has meaning only for its stated party, audience, time and purpose.

Your identity provider says who authenticated. The token says where, and for how long, its assertion or grant applies. Your API says whether this request may touch this resource.

Use all three answers.

Don’t let a valid login answer a question nobody asked it.

Objections

  1. An OIDC ID Token carries an authentication claim for its relying party, while an OAuth access token represents delegated access to a protected resource.
  2. An ID Token's audience identifies the relying party client, so a resource server rejects a token issued for another client or resource.
  3. A resource server validates signature, issuer, audience, expiry, and the applicable flow correlation before it considers a token.
  4. OAuth scope limits a delegated grant but does not answer whether a principal may perform an operation on a particular object.
  5. Directory groups and subject claims are authorization inputs, not universal permissions; the receiving service enforces its own function and object checks.
  6. Bearer-token possession is sufficient for use by default, so short lifetimes and secure authorization-code handling limit replay exposure.
  7. First-party ownership does not remove the client and resource-server trust boundaries created by OIDC and OAuth.

What this does not say

OIDC and OAuth are not broken, and groups can inform authorization; each receiving service must validate a token for itself and make its own resource decision.

Raised in review

Can an API accept an ID token from the application's login?
An API does not treat an OIDC ID Token as its general authorization credential. An ID Token carries an authentication claim for the relying-party client named in its audience, while an API is a separate resource server with its own trust boundary. The API accepts an access token issued for that resource, validates the token for itself, and then makes an authorization decision for the requested operation.
What does an ID token actually prove?
An OIDC ID Token gives a relying party a signed assertion about an authentication event and its subject. Its required claims are `iss`, `sub`, `aud`, `exp`, and `iat`; the audience contains the relying party's client identifier. The token answers who signed in to that client and when the assertion is valid. It does not decide whether that subject may invoke every API, function, or object reachable from the client.
Why does an API care about the token audience?
Audience validation prevents a token issued for one consumer from becoming a credential for another. In OIDC, an ID Token audience identifies the relying-party client, not every downstream API. A resource server accepts only a token whose audience identifies that resource under its configuration. A correctly signed token for a different client or resource remains the wrong credential and is rejected.
Does a signature from the identity provider authorize the request?
No. A valid signature establishes that the configured issuer made the assertion and that it has not been altered under the applicable trust configuration. It does not turn every claim into a permission for every recipient. The receiving resource server still validates issuer, signature, audience, and expiry, then evaluates scope, function, object, and contextual policy for the request it receives.
If the token contains groups, does the API already know what the user can do?
No. A group claim can be an authorization input, but it is not a universal permission. The receiving API maps an asserted group to bounded privileges and evaluates the requested operation and object under its own policy. A group useful for one service can be irrelevant or excessively broad for another, particularly where tenant, record, environment, or function boundaries differ.
Are OAuth scopes the same thing as object permissions?
No. OAuth scope expresses the bounded access delegated to a client for a protected resource. It does not by itself determine whether the subject may alter a particular tenant, record, account, or administrative setting. A resource server first enforces the relevant scope and then applies its own function-level and object-level authorization checks. Scope is a gate on the grant, not a complete access-control matrix.
Why is a first-party frontend still a separate token consumer?
First-party ownership does not erase protocol roles. The frontend is the client or relying party that receives an ID Token, and the API is a resource server that accepts access tokens. Treating both as one trust boundary allows a credential issued for the client to be replayed at the resource. Separate audience validation preserves the distinction even when one organization operates both components.
What does bearer-token possession mean for an API?
RFC 6750 defines a bearer token so that any party in possession of it can use it in the same way as any other holder. The API therefore cannot infer that the presenter is the original browser or user merely from token presentation. Secure transport, short token lifetimes, careful storage, expiry validation, and revocation or anomaly controls reduce the exposure created by that possession-based model.
Why does a short token lifetime not solve authorization?
A short lifetime reduces the period in which a stolen bearer token or session credential remains useful. It does not establish whether the principal may perform the requested operation on the requested object. The resource server still validates the token's intended recipient and grant, then applies its authorization policy. Token freshness limits replay exposure; authorization limits permitted actions.
What should happen to a token meant for another API?
A resource server rejects it. A token issued for another client or resource has the wrong audience and cannot become valid merely because it was signed by a trusted identity provider or contains a familiar subject claim. Accepting it would turn audience into decoration and let a credential minted for one trust relationship travel into another without a new authorization decision.
Does this mean OIDC and OAuth should not be used together?
No. OIDC and OAuth are designed to be used together for different purposes. Requesting the `openid` scope causes OIDC to add an ID Token to OAuth's delegation flow, giving the client an authentication assertion while OAuth provides an access token for protected resources. The control requirement is to preserve those separate purposes at the client and at every receiving resource server.
How does authorization-code flow reduce token misuse?
The authorization-code flow gives the client an intermediate code rather than exposing the access token in the browser redirect. RFC 6749 recommends a maximum authorization-code lifetime of 10 minutes, and PKCE in RFC 7636 requires the matching `code_verifier` when the code is redeemed. This limits code injection because a captured code cannot be redeemed without the verifier generated for that authorization request.