✅ Backend Implementation: - Add Azure AD JWT token validation middleware - Create hybrid authentication system supporting both Azure AD and password auth - Implement auto-provisioning for new Azure AD users - Add admin controls to toggle password authentication - Update all API routes to use hybrid authentication - Add database fields for authentication (password, lastLoginAt) - Create comprehensive auth routes with validation endpoints ✅ Frontend Implementation: - Install and configure Azure MSAL browser library - Create Azure AD authentication service with popup/redirect support - Build hybrid authentication service managing both auth methods - Update Login.vue with modern dual-authentication UI - Implement dynamic password auth toggle based on admin settings - Update App.vue for proper session management and validation - Modify API service to handle both token types ✅ Security Features: - Azure AD tenant validation (Oliver Agency) - Role-based access control with auto-admin assignment - JWT token validation for both auth methods - Automatic user provisioning with proper defaults - Session validation and automatic logout on token expiry ✅ Admin Features: - Toggle password authentication on/off - Manage users from both authentication methods - Full role and agent access control - Azure AD user auto-provisioning as regular users ✅ Configuration: - Azure AD: Tenant e519c2e6-bc6d-4fdf-8d9c-923c2f002385 - Client ID: 9079054c-9620-4757-a256-23413042f1ef - Development redirect URI support - Fallback password authentication for testing 🔧 Technical Stack: - Azure MSAL Browser & Node libraries - JWT token validation and hybrid middleware - Database schema updates with migrations - Vue.js integration with MSAL - Express.js hybrid authentication routes 🚀 Generated with [Claude Code](https://claude.ai/code) Co-Authored-By: Claude <noreply@anthropic.com>
134 lines
No EOL
4.2 KiB
JavaScript
134 lines
No EOL
4.2 KiB
JavaScript
const jwt = require('jsonwebtoken');
|
|
const { jwtDecode } = require('jwt-decode');
|
|
const User = require('../models/User');
|
|
|
|
// Azure AD configuration
|
|
const AZURE_TENANT_ID = 'e519c2e6-bc6d-4fdf-8d9c-923c2f002385';
|
|
const AZURE_CLIENT_ID = '9079054c-9620-4757-a256-23413042f1ef';
|
|
|
|
const validateAzureToken = async (req, res, next) => {
|
|
try {
|
|
const authHeader = req.headers['authorization'];
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
return res.status(401).json({ message: 'Access token required' });
|
|
}
|
|
|
|
// Decode the Azure AD token (without verification for now - in production should verify signature)
|
|
let decoded;
|
|
try {
|
|
decoded = jwtDecode(token);
|
|
} catch (error) {
|
|
return res.status(403).json({ message: 'Invalid token format' });
|
|
}
|
|
|
|
// Validate Azure AD token claims
|
|
if (decoded.aud !== AZURE_CLIENT_ID) {
|
|
return res.status(403).json({ message: 'Invalid token audience' });
|
|
}
|
|
|
|
if (decoded.tid !== AZURE_TENANT_ID) {
|
|
return res.status(403).json({ message: 'Invalid tenant' });
|
|
}
|
|
|
|
if (decoded.exp * 1000 < Date.now()) {
|
|
return res.status(403).json({ message: 'Token expired' });
|
|
}
|
|
|
|
// Check if user exists in database, create if not
|
|
let user = await User.findOne({ where: { email: decoded.email || decoded.upn } });
|
|
|
|
if (!user) {
|
|
// Auto-provision new Azure AD user
|
|
user = await User.create({
|
|
email: decoded.email || decoded.upn,
|
|
name: decoded.name || decoded.given_name + ' ' + decoded.family_name,
|
|
password: 'azure-ad-user', // Placeholder - not used for Azure users
|
|
preferences: {
|
|
theme: 'light',
|
|
notifications: true,
|
|
defaultAssistant: 'creator-bot-push-the-boundaries-of-technology',
|
|
role: getAzureUserRole(decoded.email || decoded.upn),
|
|
allowedAgents: getAzureUserRole(decoded.email || decoded.upn) === 'admin' ? null : [],
|
|
authMethod: 'azure'
|
|
},
|
|
isActive: true
|
|
});
|
|
} else {
|
|
// Update last login for existing user
|
|
await user.update({
|
|
lastLoginAt: new Date(),
|
|
preferences: {
|
|
...user.preferences,
|
|
authMethod: 'azure'
|
|
}
|
|
});
|
|
}
|
|
|
|
if (!user.isActive) {
|
|
return res.status(403).json({ message: 'User account is disabled' });
|
|
}
|
|
|
|
// Attach user info to request
|
|
req.user = {
|
|
id: user.id,
|
|
email: user.email,
|
|
name: user.name,
|
|
role: user.preferences?.role || 'user',
|
|
allowedAgents: user.preferences?.allowedAgents || null,
|
|
authMethod: 'azure',
|
|
azureId: decoded.oid || decoded.sub
|
|
};
|
|
|
|
next();
|
|
} catch (error) {
|
|
console.error('Azure token validation error:', error);
|
|
return res.status(500).json({ message: 'Token validation failed' });
|
|
}
|
|
};
|
|
|
|
// Determine user role based on email or Azure group membership
|
|
const getAzureUserRole = (email) => {
|
|
// Admin users - add more emails as needed
|
|
const adminEmails = [
|
|
'daveporter@oliver.agency',
|
|
// Add other admin emails here
|
|
];
|
|
|
|
return adminEmails.includes(email.toLowerCase()) ? 'admin' : 'user';
|
|
};
|
|
|
|
// Hybrid authentication middleware that handles both Azure AD and JWT tokens
|
|
const hybridAuthenticate = async (req, res, next) => {
|
|
const authHeader = req.headers['authorization'];
|
|
const token = authHeader && authHeader.split(' ')[1];
|
|
|
|
if (!token) {
|
|
return res.status(401).json({ message: 'Access token required' });
|
|
}
|
|
|
|
try {
|
|
// Try to decode as Azure AD token first
|
|
const decoded = jwtDecode(token);
|
|
|
|
// Check if it's an Azure AD token
|
|
if (decoded.aud === AZURE_CLIENT_ID && decoded.tid === AZURE_TENANT_ID) {
|
|
return validateAzureToken(req, res, next);
|
|
} else {
|
|
// Fall back to regular JWT validation
|
|
const { authenticateToken } = require('./auth');
|
|
return authenticateToken(req, res, next);
|
|
}
|
|
} catch (error) {
|
|
// If decode fails, try regular JWT validation
|
|
const { authenticateToken } = require('./auth');
|
|
return authenticateToken(req, res, next);
|
|
}
|
|
};
|
|
|
|
module.exports = {
|
|
validateAzureToken,
|
|
hybridAuthenticate,
|
|
getAzureUserRole
|
|
}; |