JSPM

  • ESM via JSPM
  • ES Module Entrypoint
  • Export Map
  • Keywords
  • License
  • Repository URL
  • TypeScript Types
  • README
  • Created
  • Published
  • Downloads 96
  • Score
    100M100P100Q76934F
  • License MIT

Backend JWT verification SDK for Azirid Identity. Validate access tokens locally using JWKS — no HTTP calls to the auth server on every request.

Package Exports

  • azirid-node
  • azirid-node/express
  • azirid-node/nestjs

Readme

azirid-node

Backend JWT verification SDK for Azirid Identity.

Validate access tokens locally using JWKS — no HTTP call to the auth server on every request. Includes plug-and-play middleware for Express and NestJS, plus a low-level createVerifier() for any Node.js runtime.

Each Azirid app has its own per-app JWKS endpoint with a unique URL. If the token's signature verifies against your app's JWKS, it's guaranteed to belong to that app — no extra appId check needed.

Installation

npm install azirid-node
# or
pnpm add azirid-node
# or
yarn add azirid-node

Express

Middleware — protect all routes under a prefix

import express from 'express'
import { aziridAuth } from 'azirid-node/express'

const app = express()

app.use(
  '/api',
  aziridAuth({
    jwksUrl: process.env.AZIRID_JWKS_URL!,
  }),
)

app.get('/api/me', (req, res) => {
  // req.user is typed as AziridUser
  res.json(req.user)
})

Protect individual routes

import { aziridAuth } from 'azirid-node/express'

const auth = aziridAuth({
  jwksUrl: process.env.AZIRID_JWKS_URL!,
})

router.get('/profile', auth, (req, res) => {
  res.json({ id: req.user!.sub, email: req.user!.email })
})

TypeScript — augmented req.user

The package automatically augments Express.Request, so req.user is typed as AziridUser with no extra setup.

import type { AziridUser } from 'azirid-node/express'

app.get('/me', auth, (req, res) => {
  const user: AziridUser = req.user!
  res.json({ sub: user.sub, email: user.email })
})

Custom token extractor or error handler

import { aziridAuth } from 'azirid-node/express'

app.use(
  '/api',
  aziridAuth({
    jwksUrl: process.env.AZIRID_JWKS_URL!,

    // Extract token from a cookie instead of the Authorization header
    getToken: (req) => req.cookies?.access_token ?? null,

    // Custom 401 response shape
    onError: (err, _req, res) => {
      res.status(401).json({ code: err.code, detail: err.message })
    },
  }),
)

NestJS

1. Register the module globally

// app.module.ts
import { Module } from '@nestjs/common'
import { AziridAuthModule } from 'azirid-node/nestjs'

@Module({
  imports: [
    AziridAuthModule.forRoot({
      jwksUrl: process.env.AZIRID_JWKS_URL!,
    }),
  ],
})
export class AppModule {}

2. Protect controllers with AziridAuthGuard

// users.controller.ts
import { Controller, Get, UseGuards } from '@nestjs/common'
import { AziridAuthGuard, CurrentUser, AziridUser } from 'azirid-node/nestjs'

@Controller('users')
export class UsersController {
  @UseGuards(AziridAuthGuard)
  @Get('me')
  getMe(@CurrentUser() user: AziridUser) {
    return { id: user.sub, email: user.email }
  }
}

3. Apply the guard globally (optional)

// main.ts
import { NestFactory } from '@nestjs/core'
import { AppModule } from './app.module'
import { AziridAuthGuard } from 'azirid-node/nestjs'

async function bootstrap() {
  const app = await NestFactory.create(AppModule)
  app.useGlobalGuards(app.get(AziridAuthGuard))
  await app.listen(3000)
}
bootstrap()

@CurrentUser() decorator

Injects the verified AziridUser into the handler argument:

@Get("profile")
@UseGuards(AziridAuthGuard)
getProfile(@CurrentUser() user: AziridUser) {
  return {
    id: user.sub,
    email: user.email,
    workspaceId: user.workspaceId,
    tenantId: user.tenantId,
    tenantRole: user.tenantRole,
    custom: user.custom,
  };
}

Low-level: createVerifier()

Use this when you are not using Express or NestJS — e.g. Fastify, Hono, AWS Lambda, edge functions, etc.

import { createVerifier } from 'azirid-node'

const verify = createVerifier({
  jwksUrl: process.env.AZIRID_JWKS_URL!,
})

// Inside a request handler:
const user = await verify(token) // throws AziridAuthError on failure
console.log(user.sub, user.email)

Hono example

import { Hono } from 'hono'
import { createVerifier, AziridAuthError } from 'azirid-node'

const app = new Hono()
const verify = createVerifier({
  jwksUrl: process.env.AZIRID_JWKS_URL!,
})

app.use('/api/*', async (c, next) => {
  const auth = c.req.header('Authorization')
  if (!auth?.startsWith('Bearer ')) {
    return c.json({ error: 'TOKEN_MISSING' }, 401)
  }
  try {
    const user = await verify(auth.slice(7))
    c.set('user', user)
    await next()
  } catch (err) {
    if (err instanceof AziridAuthError) {
      return c.json({ error: err.code, message: err.message }, 401)
    }
    return c.json({ error: 'INTERNAL_ERROR' }, 500)
  }
})

AWS Lambda (Node.js)

import { createVerifier, AziridAuthError } from 'azirid-node'

const verify = createVerifier({
  jwksUrl: process.env.AZIRID_JWKS_URL!,
})

export const handler = async (event: AWSLambda.APIGatewayProxyEvent) => {
  const token = event.headers.Authorization?.replace('Bearer ', '')
  if (!token)
    return {
      statusCode: 401,
      body: JSON.stringify({ error: 'TOKEN_MISSING' }),
    }

  try {
    const user = await verify(token)
    return { statusCode: 200, body: JSON.stringify({ sub: user.sub }) }
  } catch (err) {
    return { statusCode: 401, body: JSON.stringify({ error: 'Unauthorized' }) }
  }
}

VerifierOptions

Option Type Required Description
jwksUrl string Per-app JWKS URL from the Azirid dashboard. e.g. https://api.azirid.com/v1/jwks/ax7k2m9p4q/.well-known/jwks.json
appId string Optional extra check: reject tokens issued for a different appId. Not needed with per-app JWKS URLs
tenantId string Restrict to a specific tenant
environment "development" | "production" Restrict to a specific environment
audience string Expected aud claim
issuer string Expected iss claim
clockTolerance number Seconds of tolerance for expiration checks. Default: 5

AziridUser — token claims

interface AziridUser {
  sub: string // User ID
  email: string // User email
  workspaceId: string // Workspace that owns the app
  appId: string // App ID the token was issued for
  tenantId: string // Active tenant ID (always present)
  tenantRole: string // Role in the active tenant: "OWNER" | "MEMBER"
  environment?: 'development' | 'production' // Token environment
  sessionId: string // Active session ID
  custom?: Record<string, unknown> // Custom claims set by your app
  iat?: number // Issued at (epoch seconds)
  exp?: number // Expires at (epoch seconds)
}

Error handling — AziridAuthError

All verification failures throw an AziridAuthError with a machine-readable code:

Code Meaning
TOKEN_MISSING No token provided
TOKEN_INVALID Signature invalid or malformed JWT
TOKEN_EXPIRED Token has expired
APP_MISMATCH Token was issued for a different appId
TENANT_MISMATCH Token tenant doesn't match expected tenant
ENVIRONMENT_MISMATCH Token environment doesn't match expected environment
JWKS_ERROR Failed to fetch or parse the JWKS endpoint
import { AziridAuthError } from 'azirid-node'

try {
  const user = await verify(token)
} catch (err) {
  if (err instanceof AziridAuthError) {
    console.error(err.code, err.message)
  }
}

Environment variables

The only required config is the per-app JWKS URL (found in your Azirid dashboard under Integration):

AZIRID_JWKS_URL=https://api.azirid.com/v1/jwks/ax7k2m9p4q/.well-known/jwks.json

Then:

createVerifier({
  jwksUrl: process.env.AZIRID_JWKS_URL!,
})

License

MIT © Azirid