@carisls/sso-standard
v1.3.1
Published
A middleware implementing standard flow SSO
Readme
@carisls/sso-standard
A middleware implementing standard OIDC/OAuth2 authorization code flow SSO for Express.js applications. This package provides a complete, ready-to-use authentication solution with automatic token management, session handling, and user mapping.
Features
- 🔐 Complete OIDC/OAuth2 authorization code flow implementation
- 🍪 Automatic cookie-based session management with encryption
- 🔄 Automatic token refresh handling
- 👤 User token mapping and request injection
- 🛡️ Built-in authorization middleware
- 🌐 Multi-provider support (Keycloak, Okta, and other OIDC providers)
- 🔑 JWKS-based public key validation with caching
- 📦 Supports both ESM and CommonJS
- 🎯 Customizable endpoints and paths
- 🔒 Secure cookie handling with encryption
Installation
npm install @carisls/sso-standardQuick Start
ESM (ECMAScript Modules)
import express from 'express';
import { router } from '@carisls/sso-standard';
const app = express();
app.use(router({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
encPassword: 'your-encryption-password'
}));
app.listen(3000);CommonJS
const express = require('express');
const { router } = require('@carisls/sso-standard');
const app = express();
app.use(router({
clientId: 'your-client-id',
clientSecret: 'your-client-secret',
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
encPassword: 'your-encryption-password'
}));
app.listen(3000);API Reference
router(options)
Creates and configures an Express router with complete SSO authentication flow.
Parameters:
options(Object): Configuration object with the following properties:clientId(string, required): SSO Client ID registered with your identity providerclientSecret(string, required): SSO Client Secretproviders(string | Array, required): Provider configuration(s). Can be:- A comma-separated string of provider URLs (e.g.,
'https://auth1.example.com,https://auth2.example.com') - An array of provider configuration objects:
ssoUrl(string): Base URL for the provider (usually equal toiss). Used to discover OpenID Connect configurationiss(string, optional): Issuer identifier. If not provided, defaults tossoUrlpublicKey(string, optional): Static public key in PEM format (if not using JWKS)
- A comma-separated string of provider URLs (e.g.,
encPassword(string, required): Password used for encrypting cookiesencPasswordSalt(string, optional): Salt for password derivation (default:'C5mp4Hl$X9wby#s5')encIterationCount(number, optional): Iteration count for key derivation (default:123123)publicKeyCache(number, optional): Public key caching expiration in seconds (default:300)expOffset(number, optional): Force renewal of tokens earlier (in seconds) to handle clock skew issues (default:0)groups(boolean, optional): Request groups scope from the provider (default:true)userMapper(function, optional): Custom function to map tokens to user object. Signature:(token, idToken) => userObjectpaths(Object, optional): Customize default endpoint pathslogin(string, default:'/login'): Login initiation endpointsso(string, default:'/sso'): SSO callback endpointafterLogin(string, default:'/'): Redirect after successful loginlogout(string, default:'/logout'): Logout initiation endpointafterLogout(string, default:'/'): Redirect after successful logout
Returns: Express Router instance with the following endpoints configured:
GET /login(or custom path): Initiates SSO login flowGET /sso(or custom path): Handles SSO callback and token exchangeGET /logout(or custom path): Initiates logout flow- Automatic token refresh middleware
- User injection middleware (adds
req.userto authenticated requests)
Example:
import express from 'express';
import { router } from '@carisls/sso-standard';
const app = express();
app.use(router({
clientId: 'my-app-client',
clientSecret: 'my-secret-key',
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or detailed format: array of objects
// providers: [
// {
// ssoUrl: 'https://auth.example.com',
// // iss defaults to ssoUrl if not provided
// }
// ],
encPassword: 'my-encryption-password-123',
publicKeyCache: 600,
expOffset: 60,
groups: true,
paths: {
login: '/auth/login',
sso: '/auth/callback',
afterLogin: '/dashboard',
logout: '/auth/logout',
afterLogout: '/'
},
userMapper: (token, idToken) => {
return {
id: token.sub,
email: token.email,
name: token.name,
roles: token.roles || []
};
}
}));
// Protected route - user is automatically available via req.user
app.get('/dashboard', (req, res) => {
res.json({
message: 'Welcome!',
user: req.user
});
});
app.listen(3000);authorize(role, exceptions, redirectToLogin)
Express.js middleware for authorization filtering based on user roles. This middleware works in conjunction with the router to provide role-based access control.
Parameters:
role(string | string[] | undefined, optional): Role(s) to filter by. Ifundefined, only checks for authenticated user.exceptions(string[] | undefined, optional): Array of URL paths to skip authorization checks.redirectToLogin(boolean, default:false): Iftrue, redirects to login page instead of returning 401.
Returns: Express middleware function (req, res, next) => void
Example:
import express from 'express';
import { router, authorize } from '@carisls/sso-standard';
const app = express();
// Setup SSO router
app.use(router({
clientId: 'my-client-id',
clientSecret: 'my-secret',
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
encPassword: 'encryption-password'
}));
// Require any authenticated user
app.use(authorize());
// Require specific role
app.get('/admin', authorize('admin'), (req, res) => {
res.json({ message: 'Admin access granted', user: req.user });
});
// Require one of multiple roles
app.get('/dashboard', authorize(['admin', 'user']), (req, res) => {
res.json({ message: 'Dashboard access', user: req.user });
});
// With exceptions and redirect
app.use(authorize('user', ['/public', '/health'], true));
app.listen(3000);Complete Example
import express from 'express';
import { router, authorize } from '@carisls/sso-standard';
const app = express();
// Configure SSO middleware
app.use(router({
clientId: process.env.SSO_CLIENT_ID,
clientSecret: process.env.SSO_CLIENT_SECRET,
// Simple format: comma-separated string (iss defaults to ssoUrl)
providers: process.env.SSO_URL,
// Or array format with custom iss:
// providers: [
// {
// ssoUrl: process.env.SSO_URL,
// iss: process.env.SSO_ISSUER // optional, defaults to ssoUrl
// }
// ],
encPassword: process.env.ENCRYPTION_PASSWORD,
publicKeyCache: 600,
paths: {
login: '/login',
sso: '/sso',
afterLogin: '/dashboard',
logout: '/logout',
afterLogout: '/'
}
}));
// Public routes
app.get('/', (req, res) => {
res.send('Welcome! <a href="/login">Login</a>');
});
// Protected routes - require authentication
app.get('/dashboard', authorize(), (req, res) => {
res.json({
message: 'Dashboard',
user: req.user
});
});
// Admin routes - require admin role
app.get('/admin', authorize('admin'), (req, res) => {
res.json({
message: 'Admin panel',
user: req.user
});
});
// API routes with role-based access
app.get('/api/users', authorize(['admin', 'user-manager']), (req, res) => {
res.json({ users: [] });
});
app.listen(3000, () => {
console.log('Server running on http://localhost:3000');
});How It Works
Login Flow: When a user visits
/login, they are redirected to the identity provider's authorization endpoint with the appropriate OAuth2 parameters.Callback Handling: After authentication, the provider redirects back to
/ssowith an authorization code. The middleware exchanges this code for access, refresh, and ID tokens.Token Storage: Tokens are encrypted and stored in HTTP-only cookies:
x-session: Encrypted access tokenx-session-sso: Encrypted refresh tokenx-session-id: Encrypted ID token (used for logout)
User Injection: On each request, the middleware decrypts tokens, validates them, and injects a
req.userobject containing user information.Token Refresh: The middleware automatically refreshes access tokens when they expire using the stored refresh token.
Logout: Visiting
/logoutclears all cookies and redirects to the identity provider's logout endpoint.
Supported Providers
This package works with any OIDC-compliant identity provider, including:
- Keycloak
- Okta
- Auth0
- Azure AD
- Google Identity Platform
- Any other OIDC/OAuth2 provider
User Object Structure
By default, the req.user object follows this structure (can be customized with userMapper):
{
iss: string; // Issuer
id: string; // User ID (from 'sub')
sid?: string; // Session ID
sa: boolean; // Service account flag
azp?: string; // Authorized party
name?: { // Name object (if available)
full: string;
familyName: string;
givenName: string;
};
email?: string; // Email (if available)
roles: string[]; // User roles
}Environment Variables
For production use, store sensitive values in environment variables:
SSO_CLIENT_ID=your-client-id
SSO_CLIENT_SECRET=your-client-secret
SSO_URL=https://auth.example.com
SSO_ISSUER=https://auth.example.com
ENCRYPTION_PASSWORD=your-strong-encryption-passwordSecurity Considerations
- Cookie Encryption: All tokens are encrypted before being stored in cookies
- HTTP-Only Cookies: Prevents XSS attacks by making cookies inaccessible to JavaScript
- Secure Cookies: Automatically enabled when using HTTPS
- Token Validation: All tokens are validated using JWKS before use
- Automatic Refresh: Tokens are refreshed before expiration to prevent service interruption
Dependencies
@carisls/sso-core: Core SSO utilities (token validation, encryption, etc.)express: Web frameworkcookie-parser: Cookie parsing middlewaredebug: Debug logging utility
License
MIT
Author
Mihovil Strujic [email protected]
