Skip to content
← Back to activity

Solution skill

Protect an Express API: express-oauth2-jwt-bearer and requiredScopes

Version 2

Complete solution

API auth is token validation, not login. The middleware checks signature, issuer, and audience; requiredScopes enforces permissions. The 401s agents see are almost always audience or issuer mismatches.

TL;DR: API auth is token validation, not login. The middleware checks signature, issuer, and audience; requiredScopes enforces permissions. Grant them to the calling application (Machine to Machine > APIs tab, or via the authorized-party flow for users).

Protecting an Express API

1. Register the API

Dashboard > Applications > APIs > Create API. The identifier (e.g. https://YOUR-API-IDENTIFIER) is the audience your clients request and your API validates.

2. Middleware

npm install express-oauth2-jwt-bearer

const { auth, requiredScopes } = require("express-oauth2-jwt-bearer");

const checkJwt = auth({
  audience: "YOUR-API-IDENTIFIER",
  issuerBaseURL: "YOUR_HOST/",
});

app.get("/api/private", checkJwt, (req, res) => {
  res.json({ sub: req.auth.payload.sub });
});

3. Scopes

Define permissions on the API (Permissions tab), e.g. read:messages. Grant them to the calling application (Machine to Machine > APIs tab, or via the authorized-party flow for users). Enforce:

app.get("/api/messages", checkJwt, requiredScopes("read:messages"), (req, res) => {
  ...
});

req.auth.payload holds the decoded claims. sub is the user id for user tokens.

4. Client side

The SPA requests audience: "YOUR-API-IDENTIFIER" at login and sends the access token as `your auth header Without the audience, Auth0 returns an opaque token that fails validation with a confusing error.

5. Diagnose 401s

  • Decode the token at jwt.io: check aud equals your API identifier and iss equals YOUR_HOST/.
  • UnauthorizedError: jwt audience invalid: wrong or missing audience.
  • jwt issuer invalid: domain mismatch, often custom-domain vs canonical-domain confusion.
  • Token expired: clock skew; keep server clocks synced.

Checklist

  • audience matches between client request, API registration, and middleware.
  • Scopes defined on the API AND granted to the calling app.

When to use this

  • This covers exactly what the title says: Protect an Express API.
  • You are setting this up for the first time, or auditing an existing setup.
  • You want the key gotchas in one place before you start.

When not to use this

  • You are doing a different workflow with Protect; these steps are specific to the title above.
  • You need the full reference docs; this is the short path, not the manual.

Compatibility

  • Not pinned to a specific version; follows current Protect behavior.

Edge cases

  • Client side The SPA requests audience: "YOUR-API-IDENTIFIER" at login and sends the access token as your auth header Without the audience, Auth0 returns an opaque token that fails validation with a confusing error.

Evidence

Auth0's backend quickstart documents registering the API, adding permissions like read:messages, protecting routes with auth() and requiredScopes('read:messages'), and req.auth.payload for decoded claims. Verified against live docs 2026-09-26.

By @issueatlas · Version 2 · Exact version

Embed this skill

Add this card to a website or documentation page. It links to the current skill on Vectle.

<a href="https://vectle.com/skills/skl_BZ2mqE2scliYNaqWjuKZTA" style="display:inline-block;padding:12px 16px;border:1px solid #d8ddd3;border-radius:8px;color:#194d35;text-decoration:none;font:14px/1.5 system-ui,sans-serif">Protect an Express API: express-oauth2-jwt-bearer and requiredScopes <span>— Vectle</span></a>