Skip to content

Add jwt-bearer grant for ID-JAG (Identity Assertion Authorization Grant) support#462

Open
manmohan-shaw-okta wants to merge 1 commit into
node-oauth:masterfrom
manmohan-shaw-okta:feat/id-jag-jwt-bearer-grant
Open

Add jwt-bearer grant for ID-JAG (Identity Assertion Authorization Grant) support#462
manmohan-shaw-okta wants to merge 1 commit into
node-oauth:masterfrom
manmohan-shaw-okta:feat/id-jag-jwt-bearer-grant

Conversation

@manmohan-shaw-okta

Copy link
Copy Markdown

Summary

Adds a built-in jwt-bearer grant (urn:ietf:params:oauth:grant-type:jwt-bearer, RFC 7523) implementing the Identity Assertion Authorization Grant (ID-JAG) draft, so this library can act as the Resource Authorization Server side of a Cross App Access exchange: it verifies an ID-JAG assertion minted by an external Identity Provider and, once verified, issues a locally-scoped access token. Minting the ID-JAG itself (the IdP side, RFC 8693 Token Exchange) is out of scope for this grant.

The library currently has zero JWT/crypto dependencies, and per CONTRIBUTING.md new tight dependencies shouldn't be introduced without discussion, so signature verification (RS256/ES256/PS256) is implemented using only Node's built-in crypto module — no new npm dependency.

Linked issue(s)

This continues the discussion in #411 (ecosystem-wide ID-JAG tracking + the requirements spec for this library specifically). I have not yet opened a formal tracking Issue for this PR — happy to do so if a maintainer confirms the existing Discussion isn't sufficient per the "no PR without an issue" contribution guideline. Opening this as a Draft for that reason.

Involved parts of the project

  • lib/utils/jwt-util.js (new): dependency-free JWS decode/verify (RS256/ES256/PS256) using Node's built-in crypto.
  • lib/grant-types/jwt-bearer-grant-type.js (new): the grant itself.
  • lib/handlers/token-handler.js, lib/server.js: register the grant as built-in (not extendedGrantTypes, since this is a registered IETF grant type) and thread through new options: tokenEndpointUri (required — this AS's own RFC 8414 issuer identifier), idJagClockSkew, jwtBearerAllowedAlgorithms, jwtBearerAllowPublicClients.
  • lib/model.js, index.d.ts: 5 new model hooks — getTrustedIssuer, getRequestingIssuerKey, getUserFromIdJagAssertion, validateIdJagPermission, validateJti (or isJtiUsed+recordJti) — only required if this grant is used, following the same conditionally-required pattern as e.g. getRefreshToken for the refresh_token grant.
  • docs/guide/grant-types.md, docs/guide/model.md, examples/express-id-jag-server.js: usage docs and a runnable example.

OAuth2 workflow involved: token endpoint (grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer) only. No changes to authorize/authenticate.

Added tests?

Yes — test/unit/grant-types/jwt-bearer-grant-type_test.js, test/integration/grant-types/jwt-bearer-grant-type_test.js, test/unit/utils/jwt-util_test.js. The integration suite mints real signed JWTs (RS256/ES256/PS256) and covers the full security matrix: typ type-confusion, alg allow-list violations (none, HS256), audience mismatch, expired/future/clock-skew-boundary timestamps, tampered signatures, untrusted issuer, unresolvable key, client_id claim mismatch, replay (both validateJti() and isJtiUsed()/recordJti() model shapes, plus fail-closed on a throwing replay store), refresh-token suppression, and scope intersection/narrowing. npm run lint and npm test (528 passing) are clean.

OAuth2 standard

  • RFC 7523 — JWT Bearer grant.
  • ID-JAG draft (draft-ietf-oauth-identity-assertion-authz-grant-03) — the profile this grant implements claim-by-claim (Sections 3.1, 4.4.1, 4.4.3, 8.1, 9 referenced directly in code comments/JSDoc).
  • RFC 8414aud is validated against this AS's issuer identifier.
  • RFC 6749 §5.2 — all failures map to existing OAuthError subclasses (InvalidGrantError, InvalidRequestError, InvalidClientError, InvalidScopeError); no new error classes were needed. Per the draft's error-handling guidance, assertion-validation failures all share one non-specific message so a response can't be used as an oracle for which check failed.

Reproduction

const OAuth2Server = require('@node-oauth/oauth2-server');
const oauth = new OAuth2Server({ model, tokenEndpointUri: 'https://rs.example.com' });
// POST /token with grant_type=urn:ietf:params:oauth:grant-type:jwt-bearer&assertion=<ID-JAG JWT>

See examples/express-id-jag-server.js for a full runnable example (mints a self-signed test assertion and exchanges it end-to-end with no external IdP required — npm install express && node examples/express-id-jag-server.js).

Additional note for maintainers

CONTRIBUTING.md says to branch from development, but that branch is stale (last commit Jan 2026, 5.2.2-rc.0) relative to master (actively merged into, most recently Jul 2026). This PR targets master; happy to retarget if that's wrong.

Adds a built-in `jwt-bearer` grant (urn:ietf:params:oauth:grant-type:jwt-bearer,
RFC 7523) implementing the Identity Assertion Authorization Grant (ID-JAG)
draft, letting this library act as the Resource Authorization Server side of
a Cross App Access exchange.

- lib/utils/jwt-util.js: dependency-free JWS decode/verify (RS256/ES256/PS256)
  using only Node's built-in crypto module.
- lib/grant-types/jwt-bearer-grant-type.js: the grant itself - assertion
  parsing, typ/alg checks, issuer trust + key resolution, claim validation,
  replay protection, user resolution, permission hook, scope narrowing, and
  token issuance without ever setting a refresh token.
- lib/handlers/token-handler.js, lib/server.js: wire the grant into the
  built-in grantTypes map and thread through the new tokenEndpointUri /
  idJagClockSkew / jwtBearerAllowedAlgorithms / jwtBearerAllowPublicClients
  options.
- lib/model.js, index.d.ts: new required model hooks (getTrustedIssuer,
  getRequestingIssuerKey, getUserFromIdJagAssertion, validateIdJagPermission,
  validateJti / isJtiUsed+recordJti) and their TypeScript types.
- docs/guide/{grant-types,model}.md, examples/express-id-jag-server.js: usage
  docs and a runnable example.
- test/{unit,integration}/...: unit + integration coverage, including the
  full security matrix (type confusion, algorithm confusion, audience/claim
  validation, clock skew, replay, scope narrowing, refresh-token suppression).
@jankapunkt

Copy link
Copy Markdown
Member

@dhensby how should we proceed with PRs that are geared toward non standard RFCs? The RFC 7523 is a draft and likely become standard but it remains unclear when this will happen.

@jankapunkt

Copy link
Copy Markdown
Member

also, how far is this related to #453 ?

@manmohan-shaw-okta
manmohan-shaw-okta marked this pull request as ready for review July 20, 2026 02:53
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.

2 participants