Node.js/Express Integration Guide
Complete guide for integrating Node.js applications with CAS SSO
Updated Architecture: Our CAS system now features modular Admin/User/Public separation with enhanced JWT authentication and PostgreSQL multi-schema design.
Setup time: 10 minutes
Difficulty: Medium
Node.js 14+
1. Installation
npm install express cors axios jsonwebtoken crypto
Project Structure
your-node-app/
├── config/
│ └── cas.js # CAS configuration
├── middleware/
│ └── casAuth.js # CAS authentication middleware
├── services/
│ └── casClient.js # CAS client service
├── routes/
│ ├── auth.js # Authentication routes
│ └── protected.js # Protected routes
└── app.js # Main application
2. Configuration
Environment Variables
# CAS Configuration - Enhanced Architecture
CAS_SERVER_URL=https://your-cas-server.com
CAS_CLIENT_ID=your-client-id
CAS_CLIENT_SECRET=your-client-secret
CAS_SIGNATURE_SECRET=your-signature-secret
CAS_CALLBACK_URL=http://your-app.com/auth/cas/callback
# Database Configuration (if needed)
CAS_DB_HOST=127.0.0.1
CAS_DB_PORT=5432
CAS_DB_NAME=cas_system
CAS_DB_USER=cas_user
CAS_DB_PASSWORD=secure_password
# New Route Configuration
CAS_SSO_TOKEN_ENDPOINT=/api/sso/token
CAS_SSO_VALIDATE_ENDPOINT=/api/sso/validate
CAS_USER_DASHBOARD_URL=/user/dashboard
Configuration File
Create config/cas.js:
module.exports = {
// Enhanced CAS Server Configuration
serverUrl: process.env.CAS_SERVER_URL || 'https://your-cas-server.com',
clientId: process.env.CAS_CLIENT_ID,
clientSecret: process.env.CAS_CLIENT_SECRET,
signatureSecret: process.env.CAS_SIGNATURE_SECRET,
callbackUrl: process.env.CAS_CALLBACK_URL,
// New Route Endpoints - Updated Architecture
endpoints: {
ssoToken: '/api/sso/token', // Enhanced client credentials
ssoValidate: '/api/sso/validate', // Token validation
callback: '/auth/sso/callback', // SSO callback
userDashboard: '/user/dashboard', // User dashboard
logout: '/auth/logout' // Logout endpoint
},
// Enhanced Security Settings
security: {
tokenExpiry: 3600, // 1 hour
hmacAlgorithm: 'sha256', // HMAC-SHA256
ipWhitelistEnabled: true, // IP whitelist support
auditLogging: true, // Comprehensive logging
rateLimiting: true // Rate limiting
},
// PostgreSQL Schema Configuration
database: {
schemas: {
admin: 'cas_admin',
user: 'cas_user',
public: 'cas_public',
audit: 'cas_audit'
}
}
};
3. CAS Client Service
Create services/casClient.js:
const axios = require('axios');
const crypto = require('crypto');
const jwt = require('jsonwebtoken');
const config = require('../config/cas');
class CasClient {
constructor() {
this.baseURL = config.serverUrl;
this.clientId = config.clientId;
this.clientSecret = config.clientSecret;
this.signatureSecret = config.signatureSecret;
}
// Enhanced Token Request with HMAC Signature
async requestToken(username) {
const timestamp = Date.now();
const requestData = {
client_id: this.clientId,
client_secret: this.clientSecret,
username: username,
timestamp: timestamp
};
// Generate HMAC-SHA256 signature
const signature = this.generateSignature(requestData);
requestData.signature = signature;
try {
const response = await axios.post(
`${this.baseURL}${config.endpoints.ssoToken}`,
requestData,
{
headers: {
'Content-Type': 'application/json',
'X-CAS-Client-ID': this.clientId
}
}
);
return response.data;
} catch (error) {
console.error('CAS Token Request Error:', error.response?.data || error.message);
throw new Error('Failed to obtain CAS token');
}
}
// Enhanced Token Validation
async validateToken(token) {
const requestData = {
token: token,
timestamp: Date.now()
};
const signature = this.generateSignature(requestData);
requestData.signature = signature;
try {
const response = await axios.post(
`${this.baseURL}${config.endpoints.ssoValidate}`,
requestData,
{
headers: {
'Content-Type': 'application/json',
'X-CAS-Client-ID': this.clientId
}
}
);
return response.data;
} catch (error) {
console.error('CAS Token Validation Error:', error.response?.data || error.message);
return { valid: false, error: 'Token validation failed' };
}
}
// HMAC-SHA256 Signature Generation
generateSignature(data) {
const sortedKeys = Object.keys(data).sort();
const queryString = sortedKeys
.map(key => `${key}=${encodeURIComponent(data[key])}`)
.join('&');
return crypto
.createHmac('sha256', this.signatureSecret)
.update(queryString)
.digest('hex');
}
// Generate SSO Login URL
getLoginUrl(returnUrl) {
const params = new URLSearchParams({
client_id: this.clientId,
redirect_uri: config.callbackUrl,
return_url: returnUrl || '/'
});
return `${this.baseURL}/auth/sso?${params.toString()}`;
}
// Process SSO Callback
async processCallback(callbackData) {
try {
const validationResult = await this.validateToken(callbackData.token);
if (validationResult.valid) {
return {
success: true,
user: validationResult.user,
token: callbackData.token,
returnUrl: callbackData.return_url || '/'
};
} else {
return {
success: false,
error: 'Invalid token'
};
}
} catch (error) {
return {
success: false,
error: error.message
};
}
}
}
module.exports = new CasClient();
4. Authentication Middleware
Create middleware/casAuth.js:
const casClient = require('../services/casClient');
// Enhanced CAS Authentication Middleware
const casAuth = async (req, res, next) => {
try {
// Check for existing session
if (req.session && req.session.cas_user && req.session.cas_token) {
// Validate existing token
const validation = await casClient.validateToken(req.session.cas_token);
if (validation.valid) {
req.user = req.session.cas_user;
req.casToken = req.session.cas_token;
return next();
} else {
// Clear invalid session
req.session.cas_user = null;
req.session.cas_token = null;
}
}
// Redirect to CAS login
const returnUrl = req.originalUrl;
const loginUrl = casClient.getLoginUrl(returnUrl);
// Store return URL in session
req.session.cas_return_url = returnUrl;
return res.redirect(loginUrl);
} catch (error) {
console.error('CAS Auth Middleware Error:', error);
return res.status(500).json({
error: 'Authentication error',
message: 'Please try again later'
});
}
};
// Optional: Role-based access control
const casRole = (requiredRoles = []) => {
return (req, res, next) => {
if (!req.user || !req.user.roles) {
return res.status(403).json({
error: 'Access denied',
message: 'Insufficient permissions'
});
}
const userRoles = Array.isArray(req.user.roles) ? req.user.roles : [req.user.roles];
const hasRole = requiredRoles.some(role => userRoles.includes(role));
if (!hasRole) {
return res.status(403).json({
error: 'Access denied',
message: 'Required role not found'
});
}
next();
};
};
module.exports = { casAuth, casRole };
5. Routes Implementation
Authentication Routes
Create routes/auth.js:
const express = require('express');
const router = express.Router();
const casClient = require('../services/casClient');
// Enhanced SSO Callback Handler
router.get('/cas/callback', async (req, res) => {
try {
const { token, return_url } = req.query;
if (!token) {
return res.status(400).json({
error: 'Missing token parameter'
});
}
// Process callback with enhanced validation
const result = await casClient.processCallback({
token: token,
return_url: return_url
});
if (result.success) {
// Store user session
req.session.cas_user = result.user;
req.session.cas_token = result.token;
// Redirect to return URL or dashboard
const redirectUrl = result.returnUrl || '/user/dashboard';
return res.redirect(redirectUrl);
} else {
return res.status(401).json({
error: 'Authentication failed',
message: result.error
});
}
} catch (error) {
console.error('SSO Callback Error:', error);
return res.status(500).json({
error: 'Callback processing failed',
message: 'Please try again later'
});
}
});
// Enhanced Logout Handler
router.post('/logout', (req, res) => {
// Clear CAS session
req.session.cas_user = null;
req.session.cas_token = null;
// Destroy entire session
req.session.destroy((err) => {
if (err) {
console.error('Session destruction error:', err);
}
res.clearCookie('connect.sid'); // Clear session cookie
return res.json({
success: true,
message: 'Logged out successfully',
redirect: '/auth/login'
});
});
});
// User Info Endpoint
router.get('/user', (req, res) => {
if (req.session && req.session.cas_user) {
return res.json({
success: true,
user: req.session.cas_user,
authenticated: true
});
} else {
return res.status(401).json({
success: false,
authenticated: false,
message: 'Not authenticated'
});
}
});
module.exports = router;
Protected Routes
Create routes/protected.js:
const express = require('express');
const router = express.Router();
const { casAuth, casRole } = require('../middleware/casAuth');
// Apply CAS authentication to all protected routes
router.use(casAuth);
// User Dashboard - Enhanced with new architecture
router.get('/user/dashboard', (req, res) => {
res.json({
success: true,
message: 'Welcome to your dashboard',
user: req.user,
features: [
'Client System Linking',
'One-Click SSO Login',
'Profile Management',
'System Access Control'
],
architecture: {
separation: 'Admin/User/Public',
database: 'PostgreSQL Multi-Schema',
performance: 'Livewire Optimized'
}
});
});
// Admin Routes (require admin role)
router.get('/admin/systems', casRole(['admin']), (req, res) => {
res.json({
success: true,
message: 'Admin client systems management',
user: req.user,
features: [
'Client System Creation',
'IP Whitelist Management',
'Audit Log Viewing',
'Security Configuration'
]
});
});
// Protected API Endpoint
router.get('/api/profile', (req, res) => {
res.json({
success: true,
profile: {
id: req.user.id,
username: req.user.username,
email: req.user.email,
roles: req.user.roles,
last_login: req.user.last_login,
client_systems: req.user.client_systems || []
}
});
});
module.exports = router;
6. Main Application Setup
Update your app.js:
const express = require('express');
const session = require('express-session');
const cors = require('cors');
const config = require('./config/cas');
const app = express();
// Enhanced Middleware Configuration
app.use(cors({
origin: process.env.ALLOWED_ORIGINS?.split(',') || ['http://localhost:3000'],
credentials: true
}));
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
// Enhanced Session Configuration
app.use(session({
secret: process.env.SESSION_SECRET || 'your-session-secret',
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: config.security.tokenExpiry * 1000
}
}));
// Route Registration
const authRoutes = require('./routes/auth');
const protectedRoutes = require('./routes/protected');
app.use('/auth', authRoutes);
app.use('/', protectedRoutes);
// Health Check - Updated Architecture Info
app.get('/health', (req, res) => {
res.json({
status: 'healthy',
timestamp: new Date().toISOString(),
services: {
cas_client: 'connected',
session_store: 'active',
authentication: 'enabled'
},
architecture: {
type: 'CAS Client - Node.js/Express',
version: '2.0',
features: [
'Enhanced JWT Authentication',
'HMAC-SHA256 Signature Validation',
'Admin/User/Public Route Separation',
'PostgreSQL Multi-Schema Support'
]
}
});
});
// Error Handler
app.use((error, req, res, next) => {
console.error('Application Error:', error);
res.status(500).json({
error: 'Internal server error',
message: process.env.NODE_ENV === 'development' ? error.message : 'Something went wrong'
});
});
const PORT = process.env.PORT || 3000;
app.listen(PORT, () => {
console.log(`CAS Client Application running on port ${PORT}`);
console.log(`Architecture: Enhanced Admin/User/Public Separation`);
console.log(`CAS Server: ${config.serverUrl}`);
});
module.exports = app;
7. Advanced Usage
Custom User Dashboard Integration
// Enhanced dashboard with client system linking
router.get('/user/dashboard', casAuth, async (req, res) => {
try {
// Fetch user's linked client systems
const linkedSystems = await getUserLinkedSystems(req.user.id);
res.render('dashboard', {
user: req.user,
linkedSystems: linkedSystems,
features: {
ssoLogin: true,
systemLinking: true,
profileManagement: true
},
architecture: 'Admin/User/Public Separation'
});
} catch (error) {
res.status(500).render('error', { error: error.message });
}
});
Database Integration
// PostgreSQL connection with schema support
const { Pool } = require('pg');
const pool = new Pool({
host: process.env.CAS_DB_HOST,
port: process.env.CAS_DB_PORT,
database: process.env.CAS_DB_NAME,
user: process.env.CAS_DB_USER,
password: process.env.CAS_DB_PASSWORD,
searchPath: ['cas_user', 'cas_public'] // Multi-schema support
});
// Query user data from CAS schemas
async function getUserData(userId) {
const query = `
SELECT u.*, ucl.client_system_id, cs.name as system_name
FROM cas_user.users u
LEFT JOIN cas_user.user_client_links ucl ON u.id = ucl.user_id
LEFT JOIN cas_admin.client_systems cs ON ucl.client_system_id = cs.id
WHERE u.id = $1
`;
const result = await pool.query(query, [userId]);
return result.rows;
}
8. Testing Your Integration
Test Checklist
- Authentication flow works end-to-end
- Token validation and signature verification
- Protected routes require authentication
- Session management works correctly
- Logout functionality clears session
- Enhanced routes (/user/dashboard) accessible