I IntelliAuth Docs

intelliAuth() is an Express middleware that handles every step of inbound token validation: pulls the bearer token off the request, verifies its signature against the tenant's published key set, checks iss and aud, and surfaces the parsed claims as req.user and req.scopes. Routes that need a specific scope use the requireScope helper.

Install

ts
import { intelliAuth } from '@intelliauth/node-sdk/express'

The default export is the middleware factory.

Minimum setup

ts
import express from 'express'
import { intelliAuth } from '@intelliauth/node-sdk/express'

const app = express()

app.use(intelliAuth({
  tenantUrl: process.env.INTELLIAUTH_TENANT_URL!,
  audience: 'https://api.cymmetri.com',
}))

app.get('/me', (req, res) => {
  res.json({ user: req.user, scopes: req.scopes })
})

app.listen(3000)

tenantUrl and audience are required. The middleware fetches the tenant's JWKS document on first use, caches it, and refreshes on kid miss.

Options

Option

Required

Default

Description

tenantUrl

yes

Base URL of the IntelliAuth tenant.

audience

yes

The expected aud claim. Reject tokens whose audience doesn't match.

issuer

no

tenantUrl

Override the expected iss. Defaults to the tenant URL.

tokenLocation

no

'header'

Where to look for the token: 'header' (Authorization), 'cookie' (a named cookie), or a custom extractor function.

cookieName

no

'access_token'

If tokenLocation: 'cookie', which cookie to read.

allowUnauthenticated

no

false

If true, pass through unauthenticated requests instead of returning 401. Useful for routes mounted later in the stack.

clockTolerance

no

30

Seconds of clock skew to allow when validating iat / exp.

algorithms

no

['RS256']

Allowed signing algorithms. Tighten if your tenant only signs with one.

fetch

no

global fetch

Override the fetch used to fetch JWKS.

req.user shape

After successful validation, the middleware sets:

ts
req.user = {
  sub: 'usr_01HZX...',
  email: 'finance.lead@cymmetri.com',
  email_verified: true,
  name: 'Finance Lead',
  // ...whatever claims the token carries
}
req.scopes = ['users:read', 'audit:read']
req.token = { /* full parsed claims */ }

Express types are extended via the package's module augmentation; if you don't see autocomplete, add a /// <reference /> line or check your tsconfig.json includes the package.

requireScope

A per-route guard. Returns a middleware that rejects with 403 if the token doesn't carry the required scope.

ts
import { intelliAuth } from '@intelliauth/node-sdk/express'

app.post('/admin/users',
  intelliAuth.requireScope('users:write'),
  (req, res) => { /* ... */ },
)

For "any of these scopes":

ts
intelliAuth.requireScope(['audit:read', 'audit:export'], { mode: 'any' })

For "all of these":

ts
intelliAuth.requireScope(['users:write', 'groups:write'], { mode: 'all' })

mode: 'any' is the default for arrays.

requireUser

Some routes don't need a specific scope but do need some authenticated user. Use the bare middleware — it already 401s unauthenticated requests when allowUnauthenticated: false (the default). For more nuance:

ts
intelliAuth.requireUser({
  emailVerified: true,    // also reject users whose email isn't verified
  mfaLevel: 'aal2',       // also reject sessions below this AAL
})

Error responses

The middleware produces standardised error shapes:

json
{
  "error": "unauthorized",
  "message": "Bearer token missing",
  "request_id": "req_01HZX..."
}
json
{
  "error": "insufficient_scope",
  "message": "users:write required",
  "required_scope": "users:write",
  "request_id": "req_01HZX..."
}

For step-up scenarios, return your own 401 with error: "step_up_required" and the required AAL — the step-up topic covers the contract the React SDK expects.

Mixed authenticated + public routes

Two patterns:

ts
// Pattern A: gate at router level.
const adminRouter = express.Router()
adminRouter.use(intelliAuth({ tenantUrl, audience }))
adminRouter.use(intelliAuth.requireScope('admin:*'))
app.use('/admin', adminRouter)

// Public routes outside the router stay open.
app.get('/health', (req, res) => res.send('ok'))
ts
// Pattern B: opt-in per route.
const auth = intelliAuth({ tenantUrl, audience, allowUnauthenticated: true })

app.get('/me', auth, intelliAuth.requireUser(), (req, res) => { /* ... */ })
app.get('/health', (req, res) => res.send('ok'))

Pick whichever fits your code's shape. Pattern A is cleaner when most of your surface is authenticated; Pattern B is cleaner when most is public.

Fastify, Koa, NestJS

This package is Express-specific. For other frameworks, use the underlying verifyAccessToken() primitive exported from @intelliauth/node-sdk and wrap it in your framework's middleware idiom:

ts
import { verifyAccessToken } from '@intelliauth/node-sdk'

const result = await verifyAccessToken(authorizationHeader, {
  tenantUrl,
  audience,
})
// result.valid, result.user, result.scopes, result.error

This is the same primitive the Express middleware wraps; you get identical validation logic without the Express integration.

Concurrency

The middleware is safe for high-concurrency use. JWKS caching is in-process; a kid miss triggers a single re-fetch shared across concurrent requests. There are no per-request token requests to the platform — validation is local.

See also