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.