Auth for Node.js

Token verification, Express middleware, and management API client for backend applications.

Installation

npm install @usetransactional/auth-node
pnpm add @usetransactional/auth-node
yarn add @usetransactional/auth-node

Quick Start

import { verifyToken } from "@usetransactional/auth-node";
 
const payload = await verifyToken(token, "auth.yourdomain.com");
console.log("Authenticated user:", payload.sub);

Token Verification

The SDK provides several utilities for working with JWTs issued by Transactional Auth.

verifyToken

Verifies a JWT against your domain's JWKS endpoint and returns the decoded payload. Throws if the token is invalid, expired, or the signature does not match.

import { verifyToken } from "@usetransactional/auth-node";
 
const payload = await verifyToken(token, "auth.yourdomain.com", {
  audience: "https://api.yourdomain.com",
  issuer: "https://auth.yourdomain.com/", // defaults to domain if omitted
});
 
console.log(payload.sub); // user ID
console.log(payload.email); // user email
console.log(payload.permissions); // assigned permissions

Parameters:

  • token — the raw JWT string
  • domain — your Transactional Auth domain (used to fetch JWKS)
  • options (optional):
    • audience — expected audience claim
    • issuer — expected issuer claim (defaults to the domain)

decodeToken

Decodes a JWT without verifying its signature. Use this only for inspection purposes — never trust the output for authorization decisions.

import { decodeToken } from "@usetransactional/auth-node";
 
const decoded = decodeToken(token);
console.log(decoded.header); // { alg: 'RS256', typ: 'JWT', kid: '...' }
console.log(decoded.payload); // { sub: '...', email: '...', ... }

isTokenExpired

Returns true if the token's exp claim is in the past.

import { isTokenExpired } from "@usetransactional/auth-node";
 
if (isTokenExpired(token)) {
  console.log("Token has expired, request a new one");
}

clearJwksCache

The SDK caches JWKS keys in memory to avoid repeated network requests. Call this to force a refresh on the next verification.

import { clearJwksCache } from "@usetransactional/auth-node";
 
clearJwksCache();

Full Example

import {
  verifyToken,
  decodeToken,
  isTokenExpired,
  clearJwksCache,
} from "@usetransactional/auth-node";
 
const domain = "auth.yourdomain.com";
 
// Quick expiry check before making a network call
if (isTokenExpired(token)) {
  throw new Error("Token expired");
}
 
// Inspect claims without verification
const { payload: inspected } = decodeToken(token);
console.log("Token issued at:", new Date(inspected.iat * 1000));
 
// Full cryptographic verification
try {
  const payload = await verifyToken(token, domain, {
    audience: "https://api.yourdomain.com",
  });
  console.log("User ID:", payload.sub);
  console.log("Email:", payload.email);
} catch (err) {
  console.error("Token verification failed:", err.message);
  clearJwksCache(); // clear cache if keys may have rotated
}

Express Middleware

createAuthMiddleware

Creates an Express middleware that verifies the bearer token on every request and attaches the decoded payload to req.auth.

import { createAuthMiddleware } from "@usetransactional/auth-node";
 
const auth = createAuthMiddleware({
  domain: "auth.yourdomain.com", // required
  audience: "https://api.yourdomain.com",
  algorithms: ["RS256"], // default: ['RS256']
  credentialsRequired: true, // default: true — rejects requests without a token
  getToken: (req) => {
    // optional custom token extractor
    return req.headers["x-custom-token"] as string;
  },
});

Options:

  • domain (required) — your Transactional Auth domain
  • audience — expected audience claim
  • algorithms — accepted signing algorithms (default: ['RS256'])
  • credentialsRequired — if true, requests without a token are rejected with 401 (default: true)
  • getToken — custom function to extract the token from the request. By default, the middleware reads the Authorization: Bearer <token> header.

Full Express App Example

import express from "express";
import { createAuthMiddleware } from "@usetransactional/auth-node";
 
const app = express();
app.use(express.json());
 
const auth = createAuthMiddleware({
  domain: "auth.yourdomain.com",
  audience: "https://api.yourdomain.com",
});
 
// Protect all /api routes
app.use("/api", auth);
 
app.get("/api/profile", (req, res) => {
  // req.auth contains the decoded token payload
  res.json({
    userId: req.auth.sub,
    email: req.auth.email,
    permissions: req.auth.permissions,
  });
});
 
app.listen(3000, () => {
  console.log("Server running on port 3000");
});

Permission Guards

Use these middleware functions after createAuthMiddleware to enforce fine-grained access control on individual routes.

requirePermissions

Checks that the authenticated user has all of the specified permissions.

import {
  createAuthMiddleware,
  requirePermissions,
} from "@usetransactional/auth-node";
 
const auth = createAuthMiddleware({ domain: "auth.yourdomain.com" });
 
app.delete(
  "/api/users/:id",
  auth,
  requirePermissions("users:delete"),
  (req, res) => {
    res.json({ message: "User deleted" });
  }
);
 
app.post(
  "/api/admin/settings",
  auth,
  requirePermissions("admin:read", "admin:write"),
  (req, res) => {
    res.json({ message: "Settings updated" });
  }
);

requireRoles

Checks that the authenticated user has any of the specified roles.

import {
  createAuthMiddleware,
  requireRoles,
} from "@usetransactional/auth-node";
 
const auth = createAuthMiddleware({ domain: "auth.yourdomain.com" });
 
app.get("/api/admin/dashboard", auth, requireRoles("admin", "owner"), (req, res) => {
  res.json({ message: "Welcome to the admin dashboard" });
});

requireScopes

Checks that the token includes all of the specified OAuth scopes.

import {
  createAuthMiddleware,
  requireScopes,
} from "@usetransactional/auth-node";
 
const auth = createAuthMiddleware({ domain: "auth.yourdomain.com" });
 
app.get(
  "/api/emails",
  auth,
  requireScopes("read:emails"),
  (req, res) => {
    res.json({ emails: [] });
  }
);

optionalAuth

Works like createAuthMiddleware but does not reject requests without a token. If a valid token is present, req.auth is populated; otherwise req.auth is undefined.

import { optionalAuth } from "@usetransactional/auth-node";
 
const maybeAuth = optionalAuth({
  domain: "auth.yourdomain.com",
  audience: "https://api.yourdomain.com",
});
 
app.get("/api/articles", maybeAuth, (req, res) => {
  if (req.auth) {
    // Authenticated — return personalized content
    res.json({ articles: getArticlesForUser(req.auth.sub) });
  } else {
    // Anonymous — return public content
    res.json({ articles: getPublicArticles() });
  }
});

Management API Client

The TransactionalAuthClient provides a typed client for managing users, applications, connections, and roles programmatically.

import { TransactionalAuthClient } from "@usetransactional/auth-node";
 
const client = new TransactionalAuthClient({
  domain: "auth.yourdomain.com",
  clientId: "your-client-id",
  clientSecret: "your-client-secret",
});

Users

// List users with pagination and filtering
const { data: users, total } = await client.getUsers({
  page: 1,
  perPage: 25,
  search: "jane",
});
 
// Get a single user by ID
const user = await client.getUser("user_abc123");
 
// Get a user by email
const user = await client.getUserByEmail("jane@example.com");
 
// Create a new user
const newUser = await client.createUser({
  email: "jane@example.com",
  name: "Jane Doe",
  password: "securepassword123",
  emailVerified: false,
});
 
// Update a user
const updated = await client.updateUser("user_abc123", {
  name: "Jane Smith",
});
 
// Delete a user
await client.deleteUser("user_abc123");
 
// Block and unblock
await client.blockUser("user_abc123");
await client.unblockUser("user_abc123");
 
// Send verification email
await client.sendVerificationEmail("user_abc123");
 
// Change password
await client.changePassword("user_abc123", "newSecurePassword456");

Applications

// List all applications
const apps = await client.getApplications();
 
// Get a single application
const app = await client.getApplication("app_xyz789");

Connections

// List all connections (identity providers)
const connections = await client.getConnections();
 
// Get a single connection
const connection = await client.getConnection("con_abc123");

Roles

// List all roles
const roles = await client.getRoles();
 
// Assign a role to a user
await client.assignRoleToUser("user_abc123", "role_admin");
 
// Remove a role from a user
await client.removeRoleFromUser("user_abc123", "role_admin");
 
// Get all roles for a user
const userRoles = await client.getUserRoles("user_abc123");

Full Management Example

import { TransactionalAuthClient } from "@usetransactional/auth-node";
 
const client = new TransactionalAuthClient({
  domain: "auth.yourdomain.com",
  clientId: process.env.AUTH_CLIENT_ID!,
  clientSecret: process.env.AUTH_CLIENT_SECRET!,
});
 
async function setupNewUser(email: string, name: string) {
  // Create the user
  const user = await client.createUser({
    email,
    name,
    password: crypto.randomUUID(), // temporary password
    emailVerified: false,
  });
 
  // Assign the default role
  const roles = await client.getRoles();
  const memberRole = roles.find((r) => r.name === "member");
 
  if (memberRole) {
    await client.assignRoleToUser(user.id, memberRole.id);
  }
 
  // Send verification email
  await client.sendVerificationEmail(user.id);
 
  console.log(`Created user ${user.id} and sent verification email`);
  return user;
}

Complete Express Example

import express from "express";
import {
  createAuthMiddleware,
  optionalAuth,
  requirePermissions,
  requireRoles,
  TransactionalAuthClient,
} from "@usetransactional/auth-node";
 
const app = express();
app.use(express.json());
 
const AUTH_DOMAIN = "auth.yourdomain.com";
const AUDIENCE = "https://api.yourdomain.com";
 
// Auth middleware instances
const auth = createAuthMiddleware({ domain: AUTH_DOMAIN, audience: AUDIENCE });
const maybeAuth = optionalAuth({ domain: AUTH_DOMAIN, audience: AUDIENCE });
 
// Management client
const mgmt = new TransactionalAuthClient({
  domain: AUTH_DOMAIN,
  clientId: process.env.AUTH_CLIENT_ID!,
  clientSecret: process.env.AUTH_CLIENT_SECRET!,
});
 
// --- Public route with optional auth ---
app.get("/api/articles", maybeAuth, (req, res) => {
  if (req.auth) {
    res.json({ message: `Welcome back, ${req.auth.email}` });
  } else {
    res.json({ message: "Welcome, guest" });
  }
});
 
// --- Protected route ---
app.get("/api/profile", auth, (req, res) => {
  res.json({
    userId: req.auth.sub,
    email: req.auth.email,
  });
});
 
// --- Permission-gated route ---
app.delete(
  "/api/users/:id",
  auth,
  requirePermissions("users:delete"),
  async (req, res) => {
    await mgmt.deleteUser(req.params.id);
    res.json({ message: "User deleted" });
  }
);
 
// --- Role-gated route ---
app.get(
  "/api/admin/users",
  auth,
  requireRoles("admin", "owner"),
  async (req, res) => {
    const { data: users } = await mgmt.getUsers({ page: 1, perPage: 50 });
    res.json({ users });
  }
);
 
// --- Error handler ---
app.use((err: any, req: express.Request, res: express.Response, next: express.NextFunction) => {
  if (err.name === "UnauthorizedError") {
    return res.status(401).json({ error: "Invalid or missing token" });
  }
  if (err.name === "ForbiddenError") {
    return res.status(403).json({ error: "Insufficient permissions" });
  }
  res.status(500).json({ error: "Internal server error" });
});
 
app.listen(3000, () => {
  console.log("API server running on port 3000");
});

TypeScript Types

The SDK exports the following types for use in your application:

import type {
  DecodedToken,
  User,
  CreateUserData,
  UpdateUserData,
  ListUsersParams,
  PaginatedResponse,
  Application,
  Connection,
  Role,
} from "@usetransactional/auth-node";

DecodedToken

interface DecodedToken {
  sub: string;           // User ID
  email: string;         // User email
  name?: string;         // Display name
  picture?: string;      // Avatar URL
  email_verified: boolean;
  permissions?: string[];
  roles?: string[];
  scope?: string;
  org_id?: string;       // Organization ID
  org_name?: string;     // Organization name
  org_role?: string;     // Organization role
  iss: string;           // Issuer
  aud: string | string[];// Audience
  iat: number;           // Issued at (unix timestamp)
  exp: number;           // Expiration (unix timestamp)
}

User

interface User {
  id: string;
  email: string;
  name: string | null;
  picture: string | null;
  emailVerified: boolean;
  blocked: boolean;
  createdAt: string;
  updatedAt: string;
  lastLogin: string | null;
}

CreateUserData

interface CreateUserData {
  email: string;
  name?: string;
  password: string;
  emailVerified?: boolean;
  blocked?: boolean;
}

UpdateUserData

interface UpdateUserData {
  email?: string;
  name?: string;
  picture?: string;
  emailVerified?: boolean;
  blocked?: boolean;
}

ListUsersParams

interface ListUsersParams {
  page?: number;
  perPage?: number;
  search?: string;
  sort?: string;
  order?: "asc" | "desc";
}

PaginatedResponse

interface PaginatedResponse<T> {
  data: T[];
  total: number;
  page: number;
  perPage: number;
  totalPages: number;
}

Application

interface Application {
  id: string;
  name: string;
  clientId: string;
  type: "spa" | "regular_web" | "machine_to_machine";
  callbacks: string[];
  allowedOrigins: string[];
  createdAt: string;
}

Connection

interface Connection {
  id: string;
  name: string;
  strategy: string;
  enabled: boolean;
  createdAt: string;
}

Role

interface Role {
  id: string;
  name: string;
  description: string | null;
  permissions: string[];
  createdAt: string;
}