@carisls/sso-resource-api
v1.3.0
Published
A middleware implementing resource api SSO
Readme
@carisls/sso-resource-api
A middleware implementing resource API SSO for Express.js applications. This package validates JWT tokens from Authorization headers and injects user information into requests. Perfect for API servers that receive tokens from authenticated clients.
Features
- 🔐 JWT token validation from Authorization headers
- 🔑 JWKS-based public key validation with caching
- 👤 User token mapping and request injection
- 🛡️ Built-in authorization middleware
- 🌐 Multi-provider support (Keycloak, Okta, and other OIDC providers)
- 📦 Supports both ESM and CommonJS
- ⚡ Lightweight and performant
- 🔒 Automatic token validation on each request
Installation
npm install @carisls/sso-resource-apiQuick Start
ESM (ECMAScript Modules)
import express from 'express';
import { router } from '@carisls/sso-resource-api';
const app = express();
app.use(router({
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
}));
// Protected route - user is automatically available via req.user
app.get('/api/data', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({ message: 'Protected data', user: req.user });
});
app.listen(3000);CommonJS
const express = require('express');
const { router } = require('@carisls/sso-resource-api');
const app = express();
app.use(router({
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
}));
// Protected route - user is automatically available via req.user
app.get('/api/data', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({ message: 'Protected data', user: req.user });
});
app.listen(3000);API Reference
router(options)
Creates Express middleware that validates JWT tokens from Authorization headers and injects user information into requests.
Parameters:
options(Object): Configuration object with the following properties:providers(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.,
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)userMapper(function, optional): Custom function to map tokens to user object. Signature:(token) => userObject
Returns: Express middleware function (req, res, next) => void
The middleware:
- Validates JWT tokens from
Authorization: Bearer <token>headers - On successful validation, sets
req.userwith user information - On failed validation, logs error but continues (does not block request - you can check
req.userin your routes) - Sets
req.tokenwith the validated token
Example:
import express from 'express';
import { router } from '@carisls/sso-resource-api';
const app = express();
app.use(router({
// Simple format: comma-separated string (iss defaults to ssoUrl)
providers: 'https://auth.example.com',
// Or detailed format: array of objects
// providers: [
// {
// ssoUrl: 'https://auth.example.com',
// // iss defaults to ssoUrl if not provided
// }
// ],
publicKeyCache: 600,
userMapper: (token) => {
return {
id: token.sub,
email: token.email,
name: token.name,
roles: token.roles || []
};
}
}));
// Protected route - check req.user to verify authentication
app.get('/api/users', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({ users: [], user: req.user });
});
// Access token in route
app.get('/api/profile', (req, res) => {
if (!req.user) {
return res.status(401).json({ error: 'Unauthorized' });
}
res.json({
user: req.user,
token: req.token // Access the validated token if needed
});
});
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-resource-api';
const app = express();
// Setup SSO router
app.use(router({
// Simple format: comma-separated string
providers: 'https://auth.example.com',
// Or array format: providers: [{ ssoUrl: 'https://auth.example.com' }],
}));
// Require any authenticated user
app.use(authorize());
// Require specific role
app.get('/api/admin', authorize('admin'), (req, res) => {
res.json({ message: 'Admin access granted', user: req.user });
});
// Require one of multiple roles
app.get('/api/dashboard', authorize(['admin', 'user']), (req, res) => {
res.json({ message: 'Dashboard access', user: req.user });
});
// With exceptions and redirect
app.use(authorize('user', ['/api/public', '/api/health'], false));
app.listen(3000);Complete Example
import express from 'express';
import { router, authorize } from '@carisls/sso-resource-api';
const app = express();
// Configure SSO middleware
app.use(router({
// 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
// }
// ],
publicKeyCache: 600
}));
// Public routes (no authentication required)
app.get('/api/health', (req, res) => {
res.json({ status: 'ok' });
});
// Protected routes - require authentication
app.get('/api/dashboard', authorize(), (req, res) => {
res.json({
message: 'Dashboard',
user: req.user
});
});
// Admin routes - require admin role
app.get('/api/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
Token Validation: On each request, the middleware checks for an
Authorization: Bearer <token>header.Token Parsing: If present, the token is extracted from the header.
Token Validation: The token is validated against the configured identity provider(s) using JWKS:
- The issuer is identified from the token
- The appropriate provider's public key is fetched and cached
- Token signature, expiration, and issuer are validated
User Injection: On successful validation:
req.useris set with user information (mapped from the token)req.tokenis set with the validated token
Error Handling: On validation failure, an error is logged but the request continues. Your routes should check
req.userto determine if authentication was successful.
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_URL=https://auth.example.com
SSO_ISSUER=https://auth.example.comSecurity Considerations
- Token Validation: All tokens are validated using JWKS before use
- Public Key Caching: Public keys are cached to reduce network requests and improve performance
- No Token Storage: This package does not store tokens - it only validates them from request headers
- Authorization Header: Tokens must be sent in the standard
Authorization: Bearer <token>format
Differences from @carisls/sso-standard
This package is designed for resource API servers that receive tokens from authenticated clients:
- ✅ Validates tokens from Authorization headers
- ✅ No login/logout flows (handled by client)
- ✅ No cookie management
- ✅ Lightweight and focused on token validation
- ✅ Perfect for microservices and API gateways
Use @carisls/sso-standard if you need:
- Complete OAuth2 authorization code flow
- Cookie-based session management
- Login/logout endpoints
- Automatic token refresh
Dependencies
@carisls/sso-core: Core SSO utilities (token validation, issuer selection, etc.)
License
MIT
Author
Mihovil Strujic [email protected]
