Zeros and OnesLLC

title: "Node.js / Express Quickstart" description: "Secure your Node.js API with TitaniumVault OAuth 2.0 token validation in under 10 minutes." order: 2

Node.js / Express Quickstart

This guide will walk you through adding TitaniumVault authentication to your Node.js Express API.

Prerequisites

  • Node.js 18+ installed
  • A TitaniumVault account and API configured
  • An Express.js project

Installation

Install the required packages:

npm install express jsonwebtoken jwks-rsa express-jwt

Configuration

1. Set up environment variables

Create a .env file:

TV_DOMAIN=your-org.titanium-vault.com
TV_AUDIENCE=https://api.example.com
PORT=3001

2. Configure JWT validation middleware

Create middleware/auth.js:

const { expressjwt: jwt } = require('express-jwt');
const jwksRsa = require('jwks-rsa');

const checkJwt = jwt({
  // Dynamically provide a signing key based on the kid in the header
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://${process.env.TV_DOMAIN}/.well-known/jwks.json`,
  }),

  // Validate the audience and issuer
  audience: process.env.TV_AUDIENCE,
  issuer: `https://${process.env.TV_DOMAIN}/`,
  algorithms: ['RS256'],
});

module.exports = { checkJwt };

Basic Express Setup

Create your Express app with authentication:

require('dotenv').config();
const express = require('express');
const cors = require('cors');
const { checkJwt } = require('./middleware/auth');

const app = express();

// Middleware
app.use(cors());
app.use(express.json());

// Public route - no authentication required
app.get('/api/public', (req, res) => {
  res.json({ message: 'This is a public endpoint' });
});

// Protected route - requires valid JWT
app.get('/api/protected', checkJwt, (req, res) => {
  res.json({
    message: 'This is a protected endpoint',
    user: req.auth, // Contains the decoded JWT payload
  });
});

// Error handling for authentication errors
app.use((err, req, res, next) => {
  if (err.name === 'UnauthorizedError') {
    return res.status(401).json({
      error: 'Invalid or missing token',
      details: err.message,
    });
  }
  next(err);
});

const PORT = process.env.PORT || 3001;
app.listen(PORT, () => {
  console.log(`API server running on port ${PORT}`);
});

Scope-Based Authorization

Check for specific scopes/permissions:

// middleware/checkScopes.js
const checkScopes = (requiredScopes) => {
  return (req, res, next) => {
    const tokenScopes = req.auth?.scope?.split(' ') || [];

    const hasRequiredScopes = requiredScopes.every((scope) =>
      tokenScopes.includes(scope)
    );

    if (!hasRequiredScopes) {
      return res.status(403).json({
        error: 'Insufficient permissions',
        required: requiredScopes,
        provided: tokenScopes,
      });
    }

    next();
  };
};

module.exports = { checkScopes };

Use it in your routes:

const { checkScopes } = require('./middleware/checkScopes');

// Requires 'read:users' scope
app.get('/api/users', checkJwt, checkScopes(['read:users']), (req, res) => {
  res.json({ users: [/* ... */] });
});

// Requires 'write:users' scope
app.post('/api/users', checkJwt, checkScopes(['write:users']), (req, res) => {
  // Create user logic
  res.json({ success: true });
});

// Requires multiple scopes
app.delete(
  '/api/users/:id',
  checkJwt,
  checkScopes(['read:users', 'delete:users']),
  (req, res) => {
    // Delete user logic
    res.json({ deleted: true });
  }
);

TypeScript Setup

For TypeScript projects, create middleware/auth.ts:

import { expressjwt, Request as JWTRequest } from 'express-jwt';
import jwksRsa from 'jwks-rsa';
import { Request, Response, NextFunction } from 'express';

export interface AuthRequest extends JWTRequest {
  auth?: {
    sub: string;
    scope?: string;
    [key: string]: unknown;
  };
}

export const checkJwt = expressjwt({
  secret: jwksRsa.expressJwtSecret({
    cache: true,
    rateLimit: true,
    jwksRequestsPerMinute: 5,
    jwksUri: `https://${process.env.TV_DOMAIN}/.well-known/jwks.json`,
  }),
  audience: process.env.TV_AUDIENCE,
  issuer: `https://${process.env.TV_DOMAIN}/`,
  algorithms: ['RS256'],
});

export const checkScopes = (requiredScopes: string[]) => {
  return (req: AuthRequest, res: Response, next: NextFunction) => {
    const tokenScopes = req.auth?.scope?.split(' ') || [];

    const hasRequiredScopes = requiredScopes.every((scope) =>
      tokenScopes.includes(scope)
    );

    if (!hasRequiredScopes) {
      return res.status(403).json({
        error: 'Insufficient permissions',
      });
    }

    next();
  };
};

Machine-to-Machine Authentication

For server-to-server communication, get an access token using client credentials:

const axios = require('axios');

async function getM2MToken() {
  const response = await axios.post(
    `https://${process.env.TV_DOMAIN}/oauth/token`,
    {
      grant_type: 'client_credentials',
      client_id: process.env.TV_CLIENT_ID,
      client_secret: process.env.TV_CLIENT_SECRET,
      audience: process.env.TV_AUDIENCE,
    },
    {
      headers: { 'Content-Type': 'application/json' },
    }
  );

  return response.data.access_token;
}

// Use the token to call another API
async function callExternalApi() {
  const token = await getM2MToken();

  const response = await axios.get('https://other-api.example.com/data', {
    headers: {
      Authorization: `Bearer ${token}`,
    },
  });

  return response.data;
}

Testing with cURL

Test your protected endpoint:

# Get a token from your frontend app or use the M2M flow
TOKEN="your-access-token"

# Call the protected endpoint
curl -H "Authorization: Bearer $TOKEN" http://localhost:3001/api/protected

Next Steps

Common Issues

"jwt expired" error

Access tokens have a limited lifetime. Ensure your frontend is refreshing tokens before they expire.

"jwt audience invalid" error

The audience claim in the token must match TV_AUDIENCE. Check your token configuration in the TitaniumVault dashboard.

JWKS caching issues

If you're seeing intermittent failures, try adjusting the jwksRequestsPerMinute setting or check network connectivity to the JWKS endpoint.