Node OAuth2 JWT Bearer Integration
The express-oauth2-jwt-bearer package provides Express middleware for validating Auth0 JWT Bearer access tokens. It handles token extraction, signature verification, audience and issuer validation, and expiry checks per RFC 6750 — letting you focus on business logic rather than JWT parsing.
Agent instruction: Before providing SDK setup instructions, fetch the latest release version by running:
gh api repos/auth0/node-oauth2-jwt-bearer/releases/latest --jq '.tag_name'
Use the returned version in all package.json dependency lines instead of any hardcoded version below.
Prerequisites
- Node.js 18+ (20+ recommended; required for bootstrap script automation)
- Express 4.x or 5.x
- npm or yarn
- An Auth0 account with a configured API (Resource Server)
- Auth0 CLI (for automatic setup):
npm install -g @auth0/auth0-cli
When NOT to Use
| Use Case |
Recommended Skill |
| Building a server-side web app with login UI (Express sessions) |
auth0-express |
| Building a Next.js app with server-side auth |
auth0-nextjs |
| Building a React/Angular/Vue SPA |
auth0-react, auth0-angular, auth0-vue |
| Building a React Native or mobile app |
auth0-react-native, auth0-android, auth0-swift |
| ASP.NET Core Web API |
auth0-aspnetcore-api |
| Go API with JWT middleware |
go-jwt-middleware |
| Python API (Flask/FastAPI) |
auth0-api-python |
Node.js API using the older express-jwt package |
express-jwt |
Quick Start Workflow
Agent instruction: Follow these steps to integrate express-oauth2-jwt-bearer into the user's Node.js API project.
Fetch latest version (see instruction above).
Install the SDK:
npm install express-oauth2-jwt-bearer
Configure Auth0 — follow references/setup.md. If the user already provided their Auth0 Domain and API Audience in the prompt, use them directly — skip the bootstrap script and do NOT call AskUserQuestion to re-confirm. Otherwise, offer automatic setup via bootstrap script or manual setup.
Set up middleware — add to app.js or server.js:
import { auth } from 'express-oauth2-jwt-bearer';
const checkJwt = auth({
issuerBaseURL: `https://${process.env.AUTH0_DOMAIN}`,
audience: process.env.AUTH0_AUDIENCE,
});
app.use(checkJwt); // apply globally, or per-route
Protect endpoints — apply middleware globally or to specific routes:
// Global protection
app.use(checkJwt);
// Or per-route
app.get('/api/private', checkJwt, (req, res) => {
res.json({ sub: req.auth.payload.sub });
});
Add RBAC (optional) — use requiredScopes() or claimIncludes() for permission-based access:
import { auth, requiredScopes, claimIncludes } from 'express-oauth2-jwt-bearer';
app.get('/api/messages', checkJwt, requiredScopes('read:messages'), (req, res) => {
res.json({ messages: [] });
});
Important: requiredScopes accepts a single argument — a space-separated string or an array. Do NOT pass multiple string arguments: requiredScopes('read:msg', 'write:msg') silently ignores everything after the first. Use requiredScopes('read:msg write:msg') or requiredScopes(['read:msg', 'write:msg']) instead.
Verify the integration — build and test:
node server.js
curl http://localhost:3000/api/private # should return 401
curl -H "Authorization: Bearer <token>" http://localhost:3000/api/private # should return 200
Failcheck: If the server fails to start or tokens are rejected unexpectedly, check references/api.md for common issues. After 5-6 failed iterations, use AskUserQuestion to ask the user for more details about their environment.
Detailed Documentation
- Setup Guide — Auth0 API registration, .env configuration, bootstrap script for automated setup, and secret management
- Integration Patterns — Protected endpoints, RBAC with scopes and claims, DPoP, CORS setup, error handling, and testing with curl
- API Reference & Testing — Full configuration options, claims reference, complete code example, testing checklist, and common issues
Common Mistakes
| Mistake |
Symptom |
Fix |
| Created an Application instead of an API in Auth0 Dashboard |
Token validation fails; wrong audience |
Create a new API (Resource Server) in Auth0 Dashboard → APIs |
| Audience doesn't match API identifier exactly |
401 Unauthorized — "Audience mismatch" |
Copy the exact API Identifier string from Auth0 Dashboard → APIs |
Domain includes https:// prefix |
Error: Invalid URL at startup |
Use hostname only: your-tenant.us.auth0.com, not https://... |
Checking scope claim instead of permissions for RBAC |
403 always returned or permissions ignored |
Use requiredScopes() for scope-based RBAC; use claimIncludes('permissions', 'read:data') for Auth0 RBAC permission claims |
| CORS not configured before auth middleware |
Preflight OPTIONS requests return 401 |
Add cors() middleware before auth() in the middleware chain |
.env file not loaded |
undefined for domain/audience |
Add import 'dotenv/config' at the top of the entry file |
req.auth is undefined |
TypeError: Cannot read properties of undefined |
Verify checkJwt middleware runs before the handler |
Related Skills
Quick Reference
Core Middleware
| Function |
Description |
Returns |
auth(options?) |
JWT Bearer validation middleware |
Handler — 401 if token invalid/missing |
requiredScopes(scopes) |
Validates token has all required scopes |
Handler — 403 if scopes missing |
scopeIncludesAny(scopes) |
Validates token has at least one scope |
Handler — 403 if no match |
claimEquals(claim, value) |
Validates a claim equals a value |
Handler — 401 if mismatch |
claimIncludes(claim, ...values) |
Validates claim includes all values |
Handler — 401 if incomplete |
claimCheck(fn, desc?) |
Custom claim validation function |
Handler — 401 if fn returns false |
Configuration Options
| Option |
Type |
Description |
issuerBaseURL |
string |
Auth0 domain with https:// (required unless using env vars) |
audience |
string |
API Identifier from Auth0 Dashboard (required unless using env vars) |
tokenSigningAlg |
string |
Signing algorithm (default: RS256; use HS256 for symmetric) |
authRequired |
boolean |
Set false to make authentication optional (default: true) |
clockTolerance |
number |
Clock skew tolerance in seconds (no default; undefined unless set) |
dpop |
DPoPOptions |
DPoP configuration (see integration.md) |
Environment Variables
| Variable |
Description |
ISSUER_BASE_URL |
Auth0 domain with https:// (auto-detected by SDK) |
AUDIENCE |
API Identifier (auto-detected by SDK) |
Request Object
After successful validation, req.auth contains:
req.auth.payload // Decoded JWT payload (sub, iss, aud, exp, permissions, etc.)
req.auth.header // JWT header (alg, typ, kid)
req.auth.token // Raw JWT string
SDK Architecture
The node-oauth2-jwt-bearer monorepo contains three packages:
| Package |
Purpose |
express-oauth2-jwt-bearer |
Main package. Express middleware for JWT Bearer validation. Published to npm. |
access-token-jwt |
Low-level JWT verification utilities (used internally). |
oauth2-bearer |
RFC 6750 Bearer token extraction (used internally). |
In practice, you only install and import express-oauth2-jwt-bearer.
Auth Flow Comparison
| Auth Pattern |
SDK |
When to Use |
| JWT Bearer (stateless) |
express-oauth2-jwt-bearer |
APIs called by SPAs, mobile apps, M2M clients |
| Session-based (stateful) |
@auth0/express-openid-connect |
Web apps with login UI and server-side sessions |
Testing Quick Reference
# Get test token from Auth0 Dashboard → APIs → your API → Test tab
# Copy the token, then:
# 1. Verify 401 on protected route (no token)
curl -v http://localhost:3000/api/private
# 2. Verify 200 with valid token
curl -H "Authorization: Bearer <paste-token-here>" http://localhost:3000/api/private
# 3. Verify 403 with valid token but missing scope
curl -H "Authorization: Bearer <paste-token-here>" http://localhost:3000/api/admin
# 4. Verify CORS preflight
curl -v -X OPTIONS http://localhost:3000/api/private \
-H "Origin: http://localhost:5173" \
-H "Access-Control-Request-Method: GET" \
-H "Access-Control-Request-Headers: Authorization"
References
Source: auth0/docs-v2 — distributed by TomeVault.
1---2name: express-oauth2-jwt-bearer3description: Use when adding Auth0 token validation to Express or Node.js APIs - integrates express-oauth2-jwt-bearer SDK to protect Node.js API endpoints with JWT Bearer authentication, scope-based RBAC, claim validation, and optional DPoP support4license: Apache-2.05---67# Node OAuth2 JWT Bearer Integration89The `express-oauth2-jwt-bearer` package provides Express middleware for validating Auth0 JWT Bearer access tokens. It handles token extraction, signature verification, audience and issuer validation, and expiry checks per RFC 6750 — letting you focus on business logic rather than JWT parsing.1011> **Agent instruction:** Before providing SDK setup instructions, fetch the latest release version by running:12> ```bash13> gh api repos/auth0/node-oauth2-jwt-bearer/releases/latest --jq '.tag_name'14> ```15> Use the returned version in all `package.json` dependency lines instead of any hardcoded version below.1617## Prerequisites1819- **Node.js** 18+ (20+ recommended; required for bootstrap script automation)20- **Express** 4.x or 5.x21- **npm** or **yarn**22- An **Auth0 account** with a configured API (Resource Server)23- **Auth0 CLI** (for automatic setup): `npm install -g @auth0/auth0-cli`2425## When NOT to Use2627| Use Case | Recommended Skill |28|----------|------------------|29| Building a server-side web app with login UI (Express sessions) | `auth0-express` |30| Building a Next.js app with server-side auth | `auth0-nextjs` |31| Building a React/Angular/Vue SPA | `auth0-react`, `auth0-angular`, `auth0-vue` |32| Building a React Native or mobile app | `auth0-react-native`, `auth0-android`, `auth0-swift` |33| ASP.NET Core Web API | `auth0-aspnetcore-api` |34| Go API with JWT middleware | `go-jwt-middleware` |35| Python API (Flask/FastAPI) | `auth0-api-python` |36| Node.js API using the older `express-jwt` package | `express-jwt` |3738## Quick Start Workflow3940> **Agent instruction:** Follow these steps to integrate `express-oauth2-jwt-bearer` into the user's Node.js API project.41>42> 1. **Fetch latest version** (see instruction above).43>44> 2. **Install the SDK:**45> ```bash46> npm install express-oauth2-jwt-bearer47> ```48>49> 3. **Configure Auth0** — follow `references/setup.md`. If the user already provided their Auth0 Domain and API Audience in the prompt, use them directly — skip the bootstrap script and do NOT call `AskUserQuestion` to re-confirm. Otherwise, offer automatic setup via bootstrap script or manual setup.50>51> 4. **Set up middleware** — add to `app.js` or `server.js`:52> ```javascript53> import { auth } from 'express-oauth2-jwt-bearer';54>55> const checkJwt = auth({56> issuerBaseURL: `https://${process.env.AUTH0_DOMAIN}`,57> audience: process.env.AUTH0_AUDIENCE,58> });59>60> app.use(checkJwt); // apply globally, or per-route61> ```62>63> 5. **Protect endpoints** — apply middleware globally or to specific routes:64> ```javascript65> // Global protection66> app.use(checkJwt);67>68> // Or per-route69> app.get('/api/private', checkJwt, (req, res) => {70> res.json({ sub: req.auth.payload.sub });71> });72> ```73>74> 6. **Add RBAC** (optional) — use `requiredScopes()` or `claimIncludes()` for permission-based access:75> ```javascript76> import { auth, requiredScopes, claimIncludes } from 'express-oauth2-jwt-bearer';77>78> app.get('/api/messages', checkJwt, requiredScopes('read:messages'), (req, res) => {79> res.json({ messages: [] });80> });81> ```82> > **Important:** `requiredScopes` accepts a single argument — a space-separated string or an array. Do NOT pass multiple string arguments: `requiredScopes('read:msg', 'write:msg')` silently ignores everything after the first. Use `requiredScopes('read:msg write:msg')` or `requiredScopes(['read:msg', 'write:msg'])` instead.83>84> 7. **Verify the integration** — build and test:85> ```bash86> node server.js87> curl http://localhost:3000/api/private # should return 40188> curl -H "Authorization: Bearer <token>" http://localhost:3000/api/private # should return 20089> ```90>91> 8. **Failcheck:** If the server fails to start or tokens are rejected unexpectedly, check `references/api.md` for common issues. After 5-6 failed iterations, use `AskUserQuestion` to ask the user for more details about their environment.9293## Detailed Documentation9495- **[Setup Guide](./references/setup.md)** — Auth0 API registration, .env configuration, bootstrap script for automated setup, and secret management96- **[Integration Patterns](./references/integration.md)** — Protected endpoints, RBAC with scopes and claims, DPoP, CORS setup, error handling, and testing with curl97- **[API Reference & Testing](./references/api.md)** — Full configuration options, claims reference, complete code example, testing checklist, and common issues9899## Common Mistakes100101| Mistake | Symptom | Fix |102|---------|---------|-----|103| Created an **Application** instead of an **API** in Auth0 Dashboard | Token validation fails; wrong audience | Create a new **API** (Resource Server) in Auth0 Dashboard → APIs |104| Audience doesn't match API identifier exactly | `401 Unauthorized` — "Audience mismatch" | Copy the exact API Identifier string from Auth0 Dashboard → APIs |105| Domain includes `https://` prefix | `Error: Invalid URL` at startup | Use hostname only: `your-tenant.us.auth0.com`, not `https://...` |106| Checking `scope` claim instead of `permissions` for RBAC | 403 always returned or permissions ignored | Use `requiredScopes()` for scope-based RBAC; use `claimIncludes('permissions', 'read:data')` for Auth0 RBAC permission claims |107| CORS not configured before auth middleware | Preflight OPTIONS requests return 401 | Add `cors()` middleware before `auth()` in the middleware chain |108| `.env` file not loaded | `undefined` for domain/audience | Add `import 'dotenv/config'` at the top of the entry file |109| `req.auth` is undefined | `TypeError: Cannot read properties of undefined` | Verify `checkJwt` middleware runs before the handler |110111## Related Skills112113- **[auth0-express](../auth0-express)** — For Express web apps with login UI (sessions, cookies)114- **[auth0-nextjs](../auth0-nextjs)** — For Next.js server-side web apps115- **[auth0-aspnetcore-api](../auth0-aspnetcore-api)** — BACKEND_API reference implementation for .NET116- **[go-jwt-middleware](../go-jwt-middleware)** — JWT middleware for Go APIs117- **[auth0-api-python](../auth0-api-python)** — JWT validation for Python APIs (Flask/FastAPI)118119## Quick Reference120121### Core Middleware122123| Function | Description | Returns |124|----------|-------------|---------|125| `auth(options?)` | JWT Bearer validation middleware | `Handler` — 401 if token invalid/missing |126| `requiredScopes(scopes)` | Validates token has all required scopes | `Handler` — 403 if scopes missing |127| `scopeIncludesAny(scopes)` | Validates token has at least one scope | `Handler` — 403 if no match |128| `claimEquals(claim, value)` | Validates a claim equals a value | `Handler` — 401 if mismatch |129| `claimIncludes(claim, ...values)` | Validates claim includes all values | `Handler` — 401 if incomplete |130| `claimCheck(fn, desc?)` | Custom claim validation function | `Handler` — 401 if fn returns false |131132### Configuration Options133134| Option | Type | Description |135|--------|------|-------------|136| `issuerBaseURL` | `string` | Auth0 domain with `https://` (required unless using env vars) |137| `audience` | `string` | API Identifier from Auth0 Dashboard (required unless using env vars) |138| `tokenSigningAlg` | `string` | Signing algorithm (default: `RS256`; use `HS256` for symmetric) |139| `authRequired` | `boolean` | Set `false` to make authentication optional (default: `true`) |140| `clockTolerance` | `number` | Clock skew tolerance in seconds (no default; undefined unless set) |141| `dpop` | `DPoPOptions` | DPoP configuration (see integration.md) |142143### Environment Variables144145| Variable | Description |146|----------|-------------|147| `ISSUER_BASE_URL` | Auth0 domain with `https://` (auto-detected by SDK) |148| `AUDIENCE` | API Identifier (auto-detected by SDK) |149150### Request Object151152After successful validation, `req.auth` contains:153```typescript154req.auth.payload // Decoded JWT payload (sub, iss, aud, exp, permissions, etc.)155req.auth.header // JWT header (alg, typ, kid)156req.auth.token // Raw JWT string157```158159## SDK Architecture160161The `node-oauth2-jwt-bearer` monorepo contains three packages:162163| Package | Purpose |164|---------|---------|165| `express-oauth2-jwt-bearer` | **Main package.** Express middleware for JWT Bearer validation. Published to npm. |166| `access-token-jwt` | Low-level JWT verification utilities (used internally). |167| `oauth2-bearer` | RFC 6750 Bearer token extraction (used internally). |168169In practice, you only install and import `express-oauth2-jwt-bearer`.170171## Auth Flow Comparison172173| Auth Pattern | SDK | When to Use |174|-------------|-----|-------------|175| JWT Bearer (stateless) | `express-oauth2-jwt-bearer` | APIs called by SPAs, mobile apps, M2M clients |176| Session-based (stateful) | `@auth0/express-openid-connect` | Web apps with login UI and server-side sessions |177178## Testing Quick Reference179180```bash181# Get test token from Auth0 Dashboard → APIs → your API → Test tab182# Copy the token, then:183184# 1. Verify 401 on protected route (no token)185curl -v http://localhost:3000/api/private186187# 2. Verify 200 with valid token188curl -H "Authorization: Bearer <paste-token-here>" http://localhost:3000/api/private189190# 3. Verify 403 with valid token but missing scope191curl -H "Authorization: Bearer <paste-token-here>" http://localhost:3000/api/admin192193# 4. Verify CORS preflight194curl -v -X OPTIONS http://localhost:3000/api/private \195 -H "Origin: http://localhost:5173" \196 -H "Access-Control-Request-Method: GET" \197 -H "Access-Control-Request-Headers: Authorization"198```199200## References201202- [express-oauth2-jwt-bearer on npm](https://www.npmjs.com/package/express-oauth2-jwt-bearer)203- [GitHub: auth0/node-oauth2-jwt-bearer](https://github.com/auth0/node-oauth2-jwt-bearer)204- [Auth0 Node.js API Quickstart](https://auth0.com/docs/quickstart/backend/nodejs/interactive)205- [Auth0 APIs Dashboard](https://manage.auth0.com/#/apis)206- [RFC 6750 — Bearer Token Usage](https://datatracker.ietf.org/doc/html/rfc6750)207208---209> Source: [auth0/docs-v2](https://github.com/auth0/docs-v2) — distributed by [TomeVault](https://tomevault.io).210<!-- tomevault:4.0:skill_md:2026-05-22 -->