Authentication and Authorization in JavaScript
Authentication and authorization are critical for security. This article covers authentication methods, authorization patterns, and secure identity management.
Introduction
Authentication and authorization provide:
- User identity verification
- Access control
- Security
- Compliance
- User management
Understanding these concepts helps you:
- Implement secure authentication
- Manage user access
- Protect resources
- Implement authorization
- Build secure systems
Authentication Basics
Password Hashing
// โ
Good: Secure password hashing
const bcrypt = require('bcrypt');
class PasswordManager {
static async hashPassword(password) {
const saltRounds = 10;
return bcrypt.hash(password, saltRounds);
}
static async verifyPassword(password, hash) {
return bcrypt.compare(password, hash);
}
static validatePasswordStrength(password) {
const requirements = {
minLength: password.length >= 8,
hasUppercase: /[A-Z]/.test(password),
hasLowercase: /[a-z]/.test(password),
hasNumbers: /\d/.test(password),
hasSpecialChars: /[!@#$%^&*]/.test(password)
};
return {
strong: Object.values(requirements).every(v => v),
requirements
};
}
}
// Usage
const hash = await PasswordManager.hashPassword('MyPassword123!');
const isValid = await PasswordManager.verifyPassword('MyPassword123!', hash);
console.log(isValid); // true
const strength = PasswordManager.validatePasswordStrength('weak');
console.log(strength.strong); // false
Session-based Authentication
// โ
Good: Session-based authentication
const express = require('express');
const session = require('express-session');
const app = express();
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: true, // HTTPS only
httpOnly: true, // No JavaScript access
sameSite: 'strict',
maxAge: 3600000 // 1 hour
}
}));
class SessionAuthenticator {
static async login(req, username, password) {
// Verify credentials
const user = await this.verifyCredentials(username, password);
if (!user) {
throw new Error('Invalid credentials');
}
// Create session
req.session.userId = user.id;
req.session.username = user.username;
req.session.role = user.role;
return user;
}
static logout(req) {
req.session.destroy((err) => {
if (err) {
console.error('Error destroying session:', err);
}
});
}
static isAuthenticated(req) {
return req.session && req.session.userId;
}
static async verifyCredentials(username, password) {
// Database lookup
const user = await User.findByUsername(username);
if (!user) {
return null;
}
const isValid = await PasswordManager.verifyPassword(password, user.passwordHash);
return isValid ? user : null;
}
}
// Usage
app.post('/login', async (req, res) => {
try {
const user = await SessionAuthenticator.login(
req,
req.body.username,
req.body.password
);
res.json({ success: true, user });
} catch (error) {
res.status(401).json({ error: error.message });
}
});
JWT Authentication
JWT Implementation
// โ
Good: JWT authentication
const jwt = require('jsonwebtoken');
class JWTAuthenticator {
static generateToken(payload, expiresIn = '1h') {
return jwt.sign(payload, process.env.JWT_SECRET, { expiresIn });
}
static verifyToken(token) {
try {
return jwt.verify(token, process.env.JWT_SECRET);
} catch (error) {
return null;
}
}
static decodeToken(token) {
return jwt.decode(token);
}
static async login(username, password) {
// Verify credentials
const user = await this.verifyCredentials(username, password);
if (!user) {
throw new Error('Invalid credentials');
}
// Generate tokens
const accessToken = this.generateToken(
{ userId: user.id, username: user.username, role: user.role },
'1h'
);
const refreshToken = this.generateToken(
{ userId: user.id },
'7d'
);
return { accessToken, refreshToken, user };
}
static refreshAccessToken(refreshToken) {
const payload = this.verifyToken(refreshToken);
if (!payload) {
throw new Error('Invalid refresh token');
}
return this.generateToken(
{ userId: payload.userId },
'1h'
);
}
static async verifyCredentials(username, password) {
const user = await User.findByUsername(username);
if (!user) {
return null;
}
const isValid = await PasswordManager.verifyPassword(password, user.passwordHash);
return isValid ? user : null;
}
}
// Usage
const { accessToken, refreshToken } = await JWTAuthenticator.login('john', 'password123');
console.log(accessToken); // JWT token
JWT Middleware
// โ
Good: JWT middleware
function authenticateToken(req, res, next) {
const authHeader = req.headers['authorization'];
const token = authHeader && authHeader.split(' ')[1]; // Bearer TOKEN
if (!token) {
return res.status(401).json({ error: 'Access token required' });
}
const payload = JWTAuthenticator.verifyToken(token);
if (!payload) {
return res.status(403).json({ error: 'Invalid or expired token' });
}
req.user = payload;
next();
}
// Usage
app.get('/api/profile', authenticateToken, (req, res) => {
res.json({ user: req.user });
});
OAuth 2.0
OAuth Implementation
// โ
Good: OAuth 2.0 implementation
const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
passport.use(new GoogleStrategy({
clientID: process.env.GOOGLE_CLIENT_ID,
clientSecret: process.env.GOOGLE_CLIENT_SECRET,
callbackURL: '/auth/google/callback'
}, async (accessToken, refreshToken, profile, done) => {
try {
// Find or create user
let user = await User.findByGoogleId(profile.id);
if (!user) {
user = await User.create({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value
});
}
return done(null, user);
} catch (error) {
return done(error);
}
}));
// Routes
app.get('/auth/google',
passport.authenticate('google', { scope: ['profile', 'email'] })
);
app.get('/auth/google/callback',
passport.authenticate('google', { failureRedirect: '/login' }),
(req, res) => {
// Generate JWT token
const token = JWTAuthenticator.generateToken({
userId: req.user.id,
email: req.user.email
});
res.redirect(`/?token=${token}`);
}
);
Authorization
Role-Based Access Control (RBAC)
// โ
Good: Role-based access control
class RoleBasedAccessControl {
static roles = {
admin: ['read', 'write', 'delete', 'manage_users'],
moderator: ['read', 'write', 'delete'],
user: ['read', 'write'],
guest: ['read']
};
static hasPermission(role, permission) {
const permissions = this.roles[role] || [];
return permissions.includes(permission);
}
static hasRole(userRole, requiredRole) {
const roleHierarchy = ['admin', 'moderator', 'user', 'guest'];
const userLevel = roleHierarchy.indexOf(userRole);
const requiredLevel = roleHierarchy.indexOf(requiredRole);
return userLevel <= requiredLevel;
}
static middleware(requiredRole) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!this.hasRole(req.user.role, requiredRole)) {
return res.status(403).json({ error: 'Insufficient permissions' });
}
next();
};
}
static permissionMiddleware(requiredPermission) {
return (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
if (!this.hasPermission(req.user.role, requiredPermission)) {
return res.status(403).json({ error: 'Permission denied' });
}
next();
};
}
}
// Usage
app.delete('/api/users/:id',
authenticateToken,
RoleBasedAccessControl.middleware('admin'),
(req, res) => {
// Delete user
}
);
Attribute-Based Access Control (ABAC)
// โ
Good: Attribute-based access control
class AttributeBasedAccessControl {
static policies = [
{
name: 'owner_can_edit',
condition: (user, resource) => user.id === resource.ownerId
},
{
name: 'admin_can_do_anything',
condition: (user) => user.role === 'admin'
},
{
name: 'published_content_readable',
condition: (user, resource) => resource.published || user.id === resource.ownerId
}
];
static canAccess(user, resource, action) {
for (const policy of this.policies) {
if (policy.condition(user, resource)) {
return true;
}
}
return false;
}
static middleware(action) {
return async (req, res, next) => {
if (!req.user) {
return res.status(401).json({ error: 'Authentication required' });
}
const resource = await this.getResource(req.params.id);
if (!resource) {
return res.status(404).json({ error: 'Resource not found' });
}
if (!this.canAccess(req.user, resource, action)) {
return res.status(403).json({ error: 'Access denied' });
}
req.resource = resource;
next();
};
}
static async getResource(id) {
// Fetch resource from database
return null;
}
}
// Usage
app.put('/api/posts/:id',
authenticateToken,
AttributeBasedAccessControl.middleware('edit'),
(req, res) => {
// Update post
}
);
Multi-Factor Authentication (MFA)
TOTP Implementation
// โ
Good: Time-based One-Time Password (TOTP)
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');
class TOTPManager {
static generateSecret(username) {
return speakeasy.generateSecret({
name: `MyApp (${username})`,
issuer: 'MyApp',
length: 32
});
}
static async generateQRCode(secret) {
return QRCode.toDataURL(secret.otpauth_url);
}
static verifyToken(secret, token) {
return speakeasy.totp.verify({
secret: secret,
encoding: 'base32',
token: token,
window: 2
});
}
static async setupMFA(user) {
const secret = this.generateSecret(user.username);
const qrCode = await this.generateQRCode(secret);
// Store secret temporarily
user.mfaSecret = secret.base32;
user.mfaEnabled = false;
return { qrCode, secret: secret.base32 };
}
static async confirmMFA(user, token) {
const isValid = this.verifyToken(user.mfaSecret, token);
if (isValid) {
user.mfaEnabled = true;
await user.save();
}
return isValid;
}
static async verifyMFAToken(user, token) {
if (!user.mfaEnabled) {
return true;
}
return this.verifyToken(user.mfaSecret, token);
}
}
// Usage
const { qrCode, secret } = await TOTPManager.setupMFA(user);
// Display QR code to user
// User scans with authenticator app
// User provides token to confirm
const confirmed = await TOTPManager.confirmMFA(user, userToken);
Practical Examples
Secure Login Flow
// โ
Good: Secure login flow
app.post('/api/auth/login', async (req, res) => {
try {
const { username, password, mfaToken } = req.body;
// Validate input
if (!username || !password) {
return res.status(400).json({ error: 'Username and password required' });
}
// Find user
const user = await User.findByUsername(username);
if (!user) {
// Don't reveal if user exists
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify password
const isValid = await PasswordManager.verifyPassword(password, user.passwordHash);
if (!isValid) {
return res.status(401).json({ error: 'Invalid credentials' });
}
// Verify MFA if enabled
if (user.mfaEnabled) {
if (!mfaToken) {
return res.status(401).json({ error: 'MFA token required' });
}
const mfaValid = await TOTPManager.verifyMFAToken(user, mfaToken);
if (!mfaValid) {
return res.status(401).json({ error: 'Invalid MFA token' });
}
}
// Generate tokens
const accessToken = JWTAuthenticator.generateToken({
userId: user.id,
username: user.username,
role: user.role
});
const refreshToken = JWTAuthenticator.generateToken({
userId: user.id
}, '7d');
// Set secure cookie
res.cookie('refreshToken', refreshToken, {
httpOnly: true,
secure: true,
sameSite: 'strict',
maxAge: 7 * 24 * 60 * 60 * 1000
});
res.json({
success: true,
accessToken,
user: {
id: user.id,
username: user.username,
email: user.email,
role: user.role
}
});
} catch (error) {
console.error('Login error:', error);
res.status(500).json({ error: 'Internal server error' });
}
});
Best Practices
-
Use HTTPS everywhere:
// โ Good // All authentication over HTTPS // โ Bad // HTTP connections allowed -
Hash passwords with bcrypt:
// โ Good const hash = await bcrypt.hash(password, 10); // โ Bad const hash = sha256(password); -
Use secure token storage:
// โ Good // Store in httpOnly cookie or secure storage // โ Bad // localStorage.setItem('token', token);
Common Mistakes
-
Storing passwords in plain text:
// โ Bad user.password = password; // โ Good user.passwordHash = await bcrypt.hash(password, 10); -
Exposing user existence:
// โ Bad if (!user) return 'User not found'; // โ Good return 'Invalid credentials'; -
Not validating tokens:
// โ Bad const payload = jwt.decode(token); // โ Good const payload = jwt.verify(token, secret);
Summary
Authentication and authorization are critical. Key takeaways:
- Hash passwords securely
- Use JWT or sessions
- Implement RBAC
- Use HTTPS
- Validate tokens
- Implement MFA
- Secure token storage
- Follow best practices
Related Resources
Next Steps
- Learn about Security Testing
- Explore Secure Coding Practices
- Study CSRF Protection
- Practice authentication flows
- Implement authorization
Comments