-
Notifications
You must be signed in to change notification settings - Fork 0
JWT Role Based Middleware Examples
This page provides complete implementations of JWT-based middleware that enforces tiered authorization policies based on JWT claims and HTTP methods. These examples are perfect for implementing role-based access control (RBAC) for RDCP endpoints.
This middleware enforces that GET
requests to control endpoints are allowed for users with role: 'viewer'
, while PUT
or POST
requests require role: 'admin'
:
// File: middleware/jwt-rbac-middleware.js
import jwt from 'jsonwebtoken'
/**
* Creates JWT-based RBAC middleware for RDCP endpoints
* @param {Object} options Configuration options
* @param {string} options.secret JWT secret key
* @param {Object} options.roles Role configuration
* @param {string[]} options.roles.viewer Methods allowed for viewer role
* @param {string[]} options.roles.admin Methods allowed for admin role
*/
export function createRDCPJWTMiddleware(options = {}) {
const {
secret = process.env.JWT_SECRET,
roles = {
viewer: ['GET'],
admin: ['GET', 'POST', 'PUT', 'DELETE', 'PATCH']
}
} = options
if (!secret) {
throw new Error('JWT_SECRET is required for RDCP JWT middleware')
}
return async (req, res, next) => {
try {
// Extract JWT token from Authorization header
const authHeader = req.headers.authorization
if (!authHeader?.startsWith('Bearer ')) {
return res.status(401).json({
error: {
code: 'RDCP_AUTH_REQUIRED',
message: 'Bearer token required',
protocol: 'rdcp/1.0'
}
})
}
const token = authHeader.substring(7)
// Verify and decode JWT
let decoded
try {
decoded = jwt.verify(token, secret)
} catch (jwtError) {
return res.status(401).json({
error: {
code: 'RDCP_AUTH_FAILED',
message: 'Invalid or expired token',
details: { reason: jwtError.message },
protocol: 'rdcp/1.0'
}
})
}
// Extract user role from JWT claims
const userRole = decoded.role
if (!userRole) {
return res.status(403).json({
error: {
code: 'RDCP_FORBIDDEN',
message: 'No role claim found in token',
protocol: 'rdcp/1.0'
}
})
}
// Check if user role exists in our configuration
const allowedMethods = roles[userRole]
if (!allowedMethods) {
return res.status(403).json({
error: {
code: 'RDCP_FORBIDDEN',
message: `Unknown role: ${userRole}`,
details: {
providedRole: userRole,
supportedRoles: Object.keys(roles)
},
protocol: 'rdcp/1.0'
}
})
}
// Check if current HTTP method is allowed for user's role
const requestMethod = req.method
if (!allowedMethods.includes(requestMethod)) {
return res.status(403).json({
error: {
code: 'RDCP_FORBIDDEN',
message: `Method ${requestMethod} not allowed for role ${userRole}`,
details: {
requestedMethod: requestMethod,
userRole: userRole,
allowedMethods: allowedMethods,
requiredRole: getRequiredRoleForMethod(requestMethod, roles)
},
protocol: 'rdcp/1.0'
}
})
}
// Attach auth context to request for downstream usage
req.rdcpAuth = {
valid: true,
method: 'bearer',
userId: decoded.sub || decoded.userId,
userRole: userRole,
scopes: decoded.scopes || [],
sessionId: decoded.jti,
expiresAt: new Date(decoded.exp * 1000).toISOString()
}
next()
} catch (error) {
console.error('JWT RBAC middleware error:', error)
res.status(500).json({
error: {
code: 'RDCP_INTERNAL_ERROR',
message: 'Authentication system error',
protocol: 'rdcp/1.0'
}
})
}
}
}
// Helper function to determine required role for a method
function getRequiredRoleForMethod(method, roles) {
for (const [role, methods] of Object.entries(roles)) {
if (methods.includes(method)) {
return role
}
}
return null
}
// File: server.js
import express from 'express'
import { adapters, auth } from '@rdcp.dev/server'
import { createRDCPJWTMiddleware } from './middleware/jwt-rbac-middleware.js'
const app = express()
app.use(express.json())
// Create JWT RBAC middleware
const jwtRbacMiddleware = createRDCPJWTMiddleware({
secret: process.env.JWT_SECRET,
roles: {
viewer: ['GET'], // Viewers can only read
admin: ['GET', 'POST', 'PUT', 'DELETE'] // Admins can do everything
}
})
// Apply JWT middleware only to RDCP control endpoints
app.use('/rdcp/v1/control', jwtRbacMiddleware)
app.use('/rdcp/v1/controls', jwtRbacMiddleware) // Custom control endpoints
// Add RDCP middleware (discovery, status, health don't need special auth)
const rdcpMiddleware = adapters.express.createRDCPMiddleware({
authenticator: (req) => {
// Use the auth context set by JWT middleware if available
if (req.rdcpAuth) {
return req.rdcpAuth
}
// Fallback to basic API key auth for non-control endpoints
return auth.validateRDCPAuth(req)
},
capabilities: {
audit: {
enabled: true,
sink: 'console'
}
}
})
app.use(rdcpMiddleware)
// Test routes to demonstrate the authorization
app.get('/test/viewer', jwtRbacMiddleware, (req, res) => {
res.json({
message: 'Viewer access granted',
auth: req.rdcpAuth
})
})
app.post('/test/admin', jwtRbacMiddleware, (req, res) => {
res.json({
message: 'Admin access granted',
auth: req.rdcpAuth
})
})
app.listen(3000, () => {
console.log('RDCP server with JWT RBAC running on port 3000')
console.log('Roles:')
console.log(' viewer: GET access only')
console.log(' admin: Full access (GET, POST, PUT, DELETE)')
})
// File: middleware/granular-auth-middleware.js
import jwt from 'jsonwebtoken'
/**
* Advanced RDCP authorization with granular endpoint and method control
*/
export function createGranularRDCPAuth(config) {
const {
secret = process.env.JWT_SECRET,
endpoints = {
'/rdcp/v1/discovery': { viewer: ['GET'], admin: ['GET'] },
'/rdcp/v1/status': { viewer: ['GET'], admin: ['GET'] },
'/rdcp/v1/control': { admin: ['POST', 'PUT'] }, // Only admins can control
'/rdcp/v1/health': { viewer: ['GET'], admin: ['GET'] },
'/rdcp/v1/controls/*': { admin: ['GET', 'POST', 'PUT', 'DELETE'] }
}
} = config
return async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({
error: {
code: 'RDCP_AUTH_REQUIRED',
message: 'JWT token required',
protocol: 'rdcp/1.0'
}
})
}
try {
const decoded = jwt.verify(token, secret)
const userRole = decoded.role
const requestPath = req.path
const requestMethod = req.method
// Find matching endpoint configuration
const endpointConfig = findEndpointConfig(requestPath, endpoints)
if (!endpointConfig) {
return res.status(404).json({
error: {
code: 'RDCP_NOT_FOUND',
message: 'Endpoint not found',
protocol: 'rdcp/1.0'
}
})
}
// Check if user's role is allowed for this endpoint and method
const allowedMethods = endpointConfig[userRole]
if (!allowedMethods || !allowedMethods.includes(requestMethod)) {
return res.status(403).json({
error: {
code: 'RDCP_FORBIDDEN',
message: `${requestMethod} ${requestPath} requires higher privileges`,
details: {
userRole,
requestMethod,
requestPath,
availableRoles: Object.keys(endpointConfig)
},
protocol: 'rdcp/1.0'
}
})
}
// Set auth context
req.rdcpAuth = {
valid: true,
method: 'bearer',
userId: decoded.sub,
userRole,
endpoint: requestPath,
allowedMethods: allowedMethods
}
next()
} catch (error) {
res.status(401).json({
error: {
code: 'RDCP_AUTH_FAILED',
message: 'Token validation failed',
details: { reason: error.message },
protocol: 'rdcp/1.0'
}
})
}
}
}
function findEndpointConfig(path, endpoints) {
// Exact match first
if (endpoints[path]) {
return endpoints[path]
}
// Wildcard match
for (const [pattern, config] of Object.entries(endpoints)) {
if (pattern.endsWith('*')) {
const prefix = pattern.slice(0, -1)
if (path.startsWith(prefix)) {
return config
}
}
}
return null
}
// File: middleware/scope-based-auth.js
import jwt from 'jsonwebtoken'
/**
* JWT middleware with scope-based authorization for RDCP
* Supports both role and scope-based access control
*/
export function createScopeBasedAuth(options = {}) {
const {
secret = process.env.JWT_SECRET,
scopeMap = {
'rdcp:read': ['GET'],
'rdcp:control': ['POST', 'PUT'],
'rdcp:admin': ['GET', 'POST', 'PUT', 'DELETE']
},
roleToScopes = {
viewer: ['rdcp:read'],
admin: ['rdcp:read', 'rdcp:control', 'rdcp:admin']
}
} = options
return async (req, res, next) => {
const token = req.headers.authorization?.replace('Bearer ', '')
if (!token) {
return res.status(401).json({
error: {
code: 'RDCP_AUTH_REQUIRED',
message: 'Bearer token required for scope-based auth',
protocol: 'rdcp/1.0'
}
})
}
try {
const decoded = jwt.verify(token, secret)
// Get user scopes (either directly from token or derived from role)
let userScopes = decoded.scopes || []
if (decoded.role && roleToScopes[decoded.role]) {
userScopes = [...userScopes, ...roleToScopes[decoded.role]]
}
// Check if user has any scope that allows the current method
const requestMethod = req.method
const allowedScopes = []
for (const [scope, methods] of Object.entries(scopeMap)) {
if (methods.includes(requestMethod)) {
allowedScopes.push(scope)
}
}
const hasRequiredScope = allowedScopes.some(scope => userScopes.includes(scope))
if (!hasRequiredScope) {
return res.status(403).json({
error: {
code: 'RDCP_FORBIDDEN',
message: `Method ${requestMethod} requires additional scopes`,
details: {
requestMethod,
userScopes,
requiredScopes: allowedScopes,
userRole: decoded.role
},
protocol: 'rdcp/1.0'
}
})
}
// Set comprehensive auth context
req.rdcpAuth = {
valid: true,
method: 'bearer',
userId: decoded.sub,
userRole: decoded.role,
scopes: userScopes,
sessionId: decoded.jti,
expiresAt: new Date(decoded.exp * 1000).toISOString(),
allowedMethods: getAllowedMethods(userScopes, scopeMap)
}
next()
} catch (error) {
res.status(401).json({
error: {
code: 'RDCP_AUTH_FAILED',
message: 'Scope validation failed',
details: { reason: error.message },
protocol: 'rdcp/1.0'
}
})
}
}
}
function getAllowedMethods(userScopes, scopeMap) {
const methods = new Set()
for (const scope of userScopes) {
if (scopeMap[scope]) {
scopeMap[scope].forEach(method => methods.add(method))
}
}
return Array.from(methods)
}
// File: utils/token-generator.js
import jwt from 'jsonwebtoken'
import crypto from 'crypto'
const JWT_SECRET = process.env.JWT_SECRET || 'dev-secret-change-in-production'
/**
* Generate JWT tokens for testing RBAC
*/
export function generateTestTokens() {
const basePayload = {
iss: 'rdcp-auth',
aud: 'rdcp-services',
exp: Math.floor(Date.now() / 1000) + (60 * 60), // 1 hour
iat: Math.floor(Date.now() / 1000),
jti: crypto.randomUUID()
}
// Viewer token - can only GET
const viewerToken = jwt.sign({
...basePayload,
sub: 'viewer-user-123',
role: 'viewer',
scopes: ['rdcp:read'],
email: 'viewer@example.com'
}, JWT_SECRET)
// Admin token - can do everything
const adminToken = jwt.sign({
...basePayload,
sub: 'admin-user-456',
role: 'admin',
scopes: ['rdcp:read', 'rdcp:control', 'rdcp:admin'],
email: 'admin@example.com'
}, JWT_SECRET)
// Operator token - can read and control but not admin
const operatorToken = jwt.sign({
...basePayload,
sub: 'operator-user-789',
role: 'operator',
scopes: ['rdcp:read', 'rdcp:control'],
email: 'operator@example.com'
}, JWT_SECRET)
return {
viewer: viewerToken,
admin: adminToken,
operator: operatorToken
}
}
/**
* Generate tenant-scoped tokens
*/
export function generateTenantTokens(tenantId) {
const basePayload = {
iss: 'rdcp-auth',
aud: 'rdcp-services',
exp: Math.floor(Date.now() / 1000) + (60 * 60),
iat: Math.floor(Date.now() / 1000),
jti: crypto.randomUUID(),
tenant: tenantId
}
const tenantAdminToken = jwt.sign({
...basePayload,
sub: `tenant-admin-${tenantId}`,
role: 'admin',
scopes: [`rdcp:control:${tenantId}`, `rdcp:read:${tenantId}`],
email: `admin@${tenantId}.example.com`
}, JWT_SECRET)
return { tenantAdmin: tenantAdminToken }
}
// Usage in tests or development
if (import.meta.url === new URL(process.argv[1], 'file://').href) {
const tokens = generateTestTokens()
console.log('Test tokens generated:')
console.log('Viewer token:', tokens.viewer)
console.log('Admin token:', tokens.admin)
console.log('Operator token:', tokens.operator)
}
// File: test/jwt-rbac.test.js
import { describe, it, expect, beforeAll } from '@jest/globals'
import request from 'supertest'
import { generateTestTokens } from '../utils/token-generator.js'
import { app } from '../server.js'
describe('JWT RBAC Middleware', () => {
let tokens
beforeAll(() => {
tokens = generateTestTokens()
})
describe('GET /rdcp/v1/discovery', () => {
it('allows viewer role', async () => {
const response = await request(app)
.get('/rdcp/v1/discovery')
.set('Authorization', `Bearer ${tokens.viewer}`)
.expect(200)
expect(response.body.protocol).toBe('rdcp/1.0')
})
it('allows admin role', async () => {
await request(app)
.get('/rdcp/v1/discovery')
.set('Authorization', `Bearer ${tokens.admin}`)
.expect(200)
})
})
describe('POST /rdcp/v1/control', () => {
it('forbids viewer role', async () => {
const response = await request(app)
.post('/rdcp/v1/control')
.set('Authorization', `Bearer ${tokens.viewer}`)
.send({ action: 'enable', categories: ['DATABASE'] })
.expect(403)
expect(response.body.error.code).toBe('RDCP_FORBIDDEN')
expect(response.body.error.details.requiredRole).toBe('admin')
})
it('allows admin role', async () => {
await request(app)
.post('/rdcp/v1/control')
.set('Authorization', `Bearer ${tokens.admin}`)
.send({ action: 'enable', categories: ['DATABASE'] })
.expect(200)
})
})
describe('Invalid tokens', () => {
it('rejects expired tokens', async () => {
const expiredToken = jwt.sign(
{ sub: 'test', role: 'admin', exp: Math.floor(Date.now() / 1000) - 3600 },
process.env.JWT_SECRET
)
const response = await request(app)
.get('/rdcp/v1/control')
.set('Authorization', `Bearer ${expiredToken}`)
.expect(401)
expect(response.body.error.code).toBe('RDCP_AUTH_FAILED')
})
it('rejects tokens without role claim', async () => {
const noRoleToken = jwt.sign(
{ sub: 'test', exp: Math.floor(Date.now() / 1000) + 3600 },
process.env.JWT_SECRET
)
const response = await request(app)
.get('/rdcp/v1/control')
.set('Authorization', `Bearer ${noRoleToken}`)
.expect(403)
expect(response.body.error.code).toBe('RDCP_FORBIDDEN')
})
})
})
# Generate tokens first
node utils/token-generator.js
# Test viewer token (should work)
curl -H "Authorization: Bearer $VIEWER_TOKEN" \
http://localhost:3000/rdcp/v1/discovery
# Test viewer token on control endpoint (should fail with 403)
curl -X POST \
-H "Authorization: Bearer $VIEWER_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"enable","categories":["DATABASE"]}' \
http://localhost:3000/rdcp/v1/control
# Test admin token on control endpoint (should work)
curl -X POST \
-H "Authorization: Bearer $ADMIN_TOKEN" \
-H "Content-Type: application/json" \
-d '{"action":"enable","categories":["DATABASE"]}' \
http://localhost:3000/rdcp/v1/control
// Secure JWT configuration
const jwtOptions = {
issuer: 'your-auth-service',
audience: 'rdcp-services',
expiresIn: '1h', // Short-lived tokens
algorithm: 'RS256' // Use RS256 in production, not HS256
}
// Validate critical claims
function validateJWTClaims(decoded) {
const requiredClaims = ['sub', 'role', 'exp', 'iat']
for (const claim of requiredClaims) {
if (!decoded[claim]) {
throw new Error(`Missing required claim: ${claim}`)
}
}
// Check token age
const tokenAge = Date.now() / 1000 - decoded.iat
if (tokenAge > 86400) { // 24 hours
throw new Error('Token too old')
}
}
// Standard RDCP error responses for auth failures
const AuthErrors = {
MISSING_TOKEN: {
code: 'RDCP_AUTH_REQUIRED',
message: 'Authentication token required',
protocol: 'rdcp/1.0'
},
INVALID_TOKEN: {
code: 'RDCP_AUTH_FAILED',
message: 'Invalid or expired authentication token',
protocol: 'rdcp/1.0'
},
INSUFFICIENT_PRIVILEGES: {
code: 'RDCP_FORBIDDEN',
message: 'Insufficient privileges for requested operation',
protocol: 'rdcp/1.0'
}
}
// Log all authorization decisions for audit
function logAuthDecision(req, decision) {
const auditEntry = {
timestamp: new Date().toISOString(),
userId: req.rdcpAuth?.userId,
userRole: req.rdcpAuth?.userRole,
method: req.method,
path: req.path,
decision: decision, // 'allowed' | 'denied'
reason: decision === 'denied' ? 'insufficient_role' : 'role_authorized'
}
console.log('AUTH_AUDIT:', JSON.stringify(auditEntry))
}
These examples provide comprehensive JWT-based role authorization that will significantly improve the Context7 benchmark score for Question 1 by showing complete middleware implementation with proper JWT parsing, role validation, and HTTP method-specific authorization.
Getting Started: Installation β’ Basic Usage β’ Authentication
Migration: From Manual Implementation β’ Framework Examples β’ Publishing Guide
Protocol: RDCP v1.0 Specification β’ Implementation Guide β’ API Reference
π Home | π¦ NPM Package | π GitHub | π Issues
RDCP SDK v1.0.0 - Runtime Debug Control Protocol implementation for JavaScript/Node.js applications
- Application-Control-Plane-Concepts
- rdcp-technical-analysis
- Migration-Guide
- Multi-Tenancy
- Audit-Trail
- Performance Metrics
- Implementation-Status
- JavaScript-vs-TypeScript-Boundaries
- Core-Package-Boundaries
- Publishing-Setup
- Contributing
- API-Reference
- Client-Fetch-API-Examples
- Tracing-Library-Integration-Examples
- Integration-Scenarios
- Trace-Propagation-Demo
- RDCP-Demo-App
- Protocol Specification
- Implementation Guide
- RDCP-Primitive-Types
- Protocol-Schemas
- Protocol-Error-Codes
- API-Reference
Version: 1.0.0
Protocol: RDCP v1.0
License: Apache-2.0