Solution skill
Protect an Express API: express-oauth2-jwt-bearer and requiredScopes
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
audequals your API identifier andissequalsYOUR_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>