Node.js / Express SDK
Complete guide to integrating Signia authentication in Node.js and Express backend applications.
Installation
npm install @getsignia/signia-auth-sdk express express-session
Setup
1. Initialize OIDC Client
Create the OIDC client with your configuration:
import { OIDCClient } from '@getsignia/signia-auth-sdk';
export const oidcClient = new OIDCClient({
clientId: 'my-backend-api-abc123', // Copy from Signia ID Dashboard
clientSecret: 'YOUR_CLIENT_SECRET', // Copy from Signia ID Dashboard (shown once!)
redirectUri: 'http://localhost:3001/auth/callback',
issuer: 'https://YOUR_TENANT.signiaauth.com',
scopes: ['openid', 'profile', 'email']
});
Client Secret
Unlike frontend apps, backend applications use a client secret for secure token exchange. Never expose this secret to clients.
2. Configure Express Session
import express from 'express';
import session from 'express-session';
const app = express();
app.use(session({
secret: 'your-session-secret', // Change in production
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production', // HTTPS only in prod
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}));
Authentication Routes
Login Route
app.get('/login', async (req, res) => {
try {
const authUrl = await oidcClient.getAuthorizationUrl();
res.redirect(authUrl);
} catch (error) {
console.error('Login error:', error);
res.status(500).send('Authentication failed');
}
});
Callback Route
app.get('/auth/callback', async (req, res) => {
const { code, state } = req.query;
if (!code) {
return res.status(400).send('Missing authorization code');
}
try {
// Exchange code for tokens
const tokens = await oidcClient.exchangeCodeForTokens(code as string);
// Verify and decode ID token
const userInfo = await oidcClient.verifyIdToken(tokens.id_token);
// Store user info in session
req.session.user = userInfo;
req.session.tokens = tokens;
res.redirect('/dashboard');
} catch (error) {
console.error('Callback error:', error);
res.status(500).send('Authentication failed');
}
});
Logout Route
app.get('/logout', async (req, res) => {
const idToken = req.session.tokens?.id_token;
// Clear session
req.session.destroy((err) => {
if (err) {
console.error('Session destroy error:', err);
}
});
// Redirect to OIDC logout endpoint
const logoutUrl = await oidcClient.getLogoutUrl(idToken);
res.redirect(logoutUrl);
});
Middleware
Authentication Middleware
Protect routes that require authentication:
function requireAuth(req: express.Request, res: express.Response, next: express.NextFunction) {
if (!req.session.user) {
return res.redirect('/login');
}
next();
}
// Usage
app.get('/dashboard', requireAuth, (req, res) => {
res.render('dashboard', { user: req.session.user });
});
Token Refresh Middleware
Automatically refresh expired tokens:
async function refreshTokenMiddleware(
req: express.Request,
res: express.Response,
next: express.NextFunction
) {
const tokens = req.session.tokens;
if (!tokens?.refresh_token) {
return next();
}
// Check if access token is expired or about to expire
const decoded = oidcClient.decodeToken(tokens.access_token);
const expiresIn = decoded.exp - Math.floor(Date.now() / 1000);
if (expiresIn < 300) { // Refresh if less than 5 minutes remaining
try {
const newTokens = await oidcClient.refreshAccessToken(tokens.refresh_token);
req.session.tokens = newTokens;
} catch (error) {
console.error('Token refresh failed:', error);
return res.redirect('/login');
}
}
next();
}
Making Authenticated API Calls
Using the HTTP Client
app.get('/api/user-data', requireAuth, async (req, res) => {
try {
const httpClient = await oidcClient.getAuthHttpClient(
req.session.tokens.access_token
);
const response = await httpClient.get('https://api.example.com/user');
res.json(response.data);
} catch (error) {
console.error('API call failed:', error);
res.status(500).json({ error: 'Failed to fetch user data' });
}
});
Manual Token Usage
import axios from 'axios';
app.get('/api/protected', requireAuth, async (req, res) => {
const accessToken = req.session.tokens.access_token;
try {
const response = await axios.get('https://api.example.com/data', {
headers: {
'Authorization': `Bearer ${accessToken}`
}
});
res.json(response.data);
} catch (error) {
res.status(500).json({ error: 'API request failed' });
}
});
User Information
Access User Profile
app.get('/api/me', requireAuth, (req, res) => {
res.json({
sub: req.session.user.sub,
email: req.session.user.email,
name: req.session.user.name,
email_verified: req.session.user.email_verified
});
});
Fetch User Info from OIDC Endpoint
app.get('/api/userinfo', requireAuth, async (req, res) => {
try {
const userInfo = await oidcClient.getUserInfo(
req.session.tokens.access_token
);
res.json(userInfo);
} catch (error) {
res.status(500).json({ error: 'Failed to fetch user info' });
}
});
Complete Example
import express from 'express';
import session from 'express-session';
import { OIDCClient } from '@getsignia/signia-auth-sdk';
const app = express();
const port = 3001;
// Initialize OIDC client
const oidcClient = new OIDCClient({
clientId: process.env.OIDC_CLIENT_ID!,
clientSecret: process.env.OIDC_CLIENT_SECRET!,
redirectUri: `http://localhost:${port}/auth/callback`,
issuer: process.env.OIDC_ISSUER!,
scopes: ['openid', 'profile', 'email']
});
// Session configuration
app.use(session({
secret: process.env.SESSION_SECRET || 'dev-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000
}
}));
// Middleware
function requireAuth(req: express.Request, res: express.Response, next: express.NextFunction) {
if (!req.session.user) {
return res.redirect('/login');
}
next();
}
// Routes
app.get('/', (req, res) => {
if (req.session.user) {
res.redirect('/dashboard');
} else {
res.send(`
<h1>Welcome</h1>
<a href="/login">Login with Signia</a>
`);
}
});
app.get('/login', async (req, res) => {
const authUrl = await oidcClient.getAuthorizationUrl();
res.redirect(authUrl);
});
app.get('/auth/callback', async (req, res) => {
const { code } = req.query;
try {
const tokens = await oidcClient.exchangeCodeForTokens(code as string);
const userInfo = await oidcClient.verifyIdToken(tokens.id_token);
req.session.user = userInfo;
req.session.tokens = tokens;
res.redirect('/dashboard');
} catch (error) {
console.error('Authentication error:', error);
res.status(500).send('Authentication failed');
}
});
app.get('/dashboard', requireAuth, (req, res) => {
res.send(`
<h1>Dashboard</h1>
<p>Welcome, ${req.session.user.email}</p>
<p>User ID: ${req.session.user.sub}</p>
<a href="/logout">Logout</a>
`);
});
app.get('/logout', (req, res) => {
req.session.destroy(() => {
res.redirect('/');
});
});
app.listen(port, () => {
console.log(`Server running at http://localhost:${port}`);
});
TypeScript Support
Type Declarations
import { Request } from 'express';
declare module 'express-session' {
interface SessionData {
user: {
sub: string;
email?: string;
name?: string;
email_verified?: boolean;
};
tokens: {
access_token: string;
id_token: string;
refresh_token?: string;
expires_in: number;
};
}
}
Security Best Practices
1. Use Environment Variables
# .env file
OIDC_CLIENT_ID=my-backend-api-abc123 # From Signia ID Dashboard
OIDC_CLIENT_SECRET=your-client-secret # From Signia ID Dashboard (shown once!)
OIDC_ISSUER=https://tenant.signiaauth.com
SESSION_SECRET=your-random-secret
NODE_ENV=production
2. Secure Session Configuration
app.use(session({
secret: process.env.SESSION_SECRET!,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // Prevent XSS
sameSite: 'lax', // CSRF protection
maxAge: 3600000 // 1 hour
}
}));
3. Validate Redirect URIs
const ALLOWED_REDIRECT_URIS = [
'http://localhost:3001/auth/callback',
'https://myapp.com/auth/callback'
];
app.get('/auth/callback', async (req, res) => {
const redirectUri = req.query.redirect_uri as string;
if (!ALLOWED_REDIRECT_URIS.includes(redirectUri)) {
return res.status(400).send('Invalid redirect URI');
}
// ... rest of callback logic
});
4. Use PKCE (Proof Key for Code Exchange)
const oidcClient = new OIDCClient({
clientId: process.env.OIDC_CLIENT_ID!,
clientSecret: process.env.OIDC_CLIENT_SECRET!,
redirectUri: 'http://localhost:3001/auth/callback',
issuer: process.env.OIDC_ISSUER!,
scopes: ['openid', 'profile', 'email'],
usePKCE: true // Enable PKCE
});
Troubleshooting
"Invalid client" error
Make sure your client secret is correct and matches what's configured in Signia dashboard.
Session not persisting
Check that your session secret is set and cookies are being sent:
// Check cookie configuration
cookie: {
secure: process.env.NODE_ENV === 'production', // false for local dev
sameSite: 'lax' // not 'strict' which can break redirects
}
Token expired errors
Implement token refresh middleware to automatically renew tokens before they expire.
Next Steps
- Core Concepts - Understand OIDC fundamentals
- React SDK - Frontend integration