auth0/express-oauth2-jwt-bearer
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 support
npx skills add https://github.com/auth0/docs-v2 --skill express-oauth2-jwt-bearer
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:
> `bash
> 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.
npm install -g @auth0/auth0-cli| 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 |
> Agent instruction: Follow these steps to integrate express-oauth2-jwt-bearer into the user's Node.js API project.
>
> 1. Fetch latest version (see instruction above).
>
> 2. Install the SDK:
> `bash
> npm install express-oauth2-jwt-bearer
> `
>
> 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.
>
> 4. Set up middleware — add to app.js or server.js:
> `javascript
> 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
> `
>
> 5. Protect endpoints — apply middleware globally or to specific routes:
> `javascript
> // Global protection
> app.use(checkJwt);
>
> // Or per-route
> app.get('/api/private', checkJwt, (req, res) => {
> res.json({ sub: req.auth.payload.sub });
> });
> `
>
> 6. Add RBAC (optional) — use requiredScopes() or claimIncludes() for permission-based access:
> `javascript
> 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.
>
> 7. Verify the integration — build and test:
> `bash
> 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
> `
>
> 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.
| 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 |
| 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 |
| 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) |
| Variable | Description |
|----------|-------------|
| ISSUER_BASE_URL | Auth0 domain with https:// (auto-detected by SDK) |
| AUDIENCE | API Identifier (auto-detected by SDK) |
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
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 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 |
# 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"
Take auth0/express-oauth2-jwt-bearer from the repository into ~/.claude/skills for personal
use, or into .claude/skills inside a project.
The agent identifies a skill by the name field in its header. Two skills with the
same name cannot sit side by side — one of them will be ignored.
The instructions reference npm.
Without those the skill loads but fails at the first command.