Skip to main content
โšก Calmops

Authentication and Authorization in JavaScript

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

  1. Use HTTPS everywhere:

    // โœ… Good
    // All authentication over HTTPS
    
    // โŒ Bad
    // HTTP connections allowed
    
  2. Hash passwords with bcrypt:

    // โœ… Good
    const hash = await bcrypt.hash(password, 10);
    
    // โŒ Bad
    const hash = sha256(password);
    
  3. Use secure token storage:

    // โœ… Good
    // Store in httpOnly cookie or secure storage
    
    // โŒ Bad
    // localStorage.setItem('token', token);
    

Common Mistakes

  1. Storing passwords in plain text:

    // โŒ Bad
    user.password = password;
    
    // โœ… Good
    user.passwordHash = await bcrypt.hash(password, 10);
    
  2. Exposing user existence:

    // โŒ Bad
    if (!user) return 'User not found';
    
    // โœ… Good
    return 'Invalid credentials';
    
  3. 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

Next Steps

Comments