Intermediate
40 mins

Authentication

Learn how to implement secure authentication in your applications. This guide covers user registration, login, multi-factor authentication, and authorization with best practices.

Prerequisites

  • Understanding of web security concepts
  • Familiarity with JWT and session-based auth
  • Basic knowledge of cryptography
  • Experience with Node.js and Express

Authentication Overview

Authentication Workflow

Visual representation of the authentication workflow and components.

1

Authentication Setup

Configure the authentication system:

// Authentication configuration
const authConfig = {
  // JWT configuration
  jwt: {
    secret: process.env.JWT_SECRET,
    expiresIn: '24h',
    algorithm: 'HS256',
    issuer: 'symbiosis.host',
    audience: 'symbiosis.host/api'
  },
  
  // Password configuration
  password: {
    minLength: 12,
    requireUppercase: true,
    requireLowercase: true,
    requireNumbers: true,
    requireSpecial: true,
    saltRounds: 12
  },
  
  // Session configuration
  session: {
    name: 'sid',
    secret: process.env.SESSION_SECRET,
    cookie: {
      maxAge: 86400000, // 24 hours
      httpOnly: true,
      secure: true,
      sameSite: 'strict'
    },
    resave: false,
    saveUninitialized: false
  },
  
  // Rate limiting
  rateLimit: {
    window: 15 * 60 * 1000, // 15 minutes
    max: 100, // 100 requests per window
    message: 'Too many requests, please try again later'
  }
};

// Initialize authentication
const initAuth = (app) => {
  // Set up session middleware
  app.use(session(authConfig.session));
  
  // Set up rate limiting
  app.use('/api/auth', rateLimit(authConfig.rateLimit));
  
  // Initialize passport
  app.use(passport.initialize());
  app.use(passport.session());
  
  // Configure passport strategies
  configurePassport();
  
  return app;
};
2

User Registration

Implement secure user registration:

// User registration service
const userService = {
  async register(userData) {
    // Validate user data
    const validationResult = this.validateUserData(userData);
    if (!validationResult.valid) {
      throw new Error(`Invalid user data: ${validationResult.errors.join(', ')}`);
    }
    
    // Check if user already exists
    const existingUser = await db.users.findOne({ email: userData.email });
    if (existingUser) {
      throw new Error('User with this email already exists');
    }
    
    // Hash password
    const passwordHash = await bcrypt.hash(
      userData.password,
      authConfig.password.saltRounds
    );
    
    // Create user
    const user = await db.users.create({
      email: userData.email,
      passwordHash,
      firstName: userData.firstName,
      lastName: userData.lastName,
      role: 'user',
      status: 'active',
      createdAt: new Date()
    });
    
    // Create user profile
    await db.profiles.create({
      userId: user.id,
      createdAt: new Date()
    });
    
    // Send welcome email
    await emailService.sendWelcomeEmail(user);
    
    return this.sanitizeUser(user);
  },
  
  validateUserData(data) {
    const errors = [];
    
    // Validate email
    if (!data.email || !this.isValidEmail(data.email)) {
      errors.push('Valid email is required');
    }
    
    // Validate password
    if (!data.password) {
      errors.push('Password is required');
    } else {
      if (data.password.length < authConfig.password.minLength) {
        errors.push(`Password must be at least ${authConfig.password.minLength} characters`);
      }
      
      if (authConfig.password.requireUppercase && !/[A-Z]/.test(data.password)) {
        errors.push('Password must contain at least one uppercase letter');
      }
      
      if (authConfig.password.requireLowercase && !/[a-z]/.test(data.password)) {
        errors.push('Password must contain at least one lowercase letter');
      }
      
      if (authConfig.password.requireNumbers && !/[0-9]/.test(data.password)) {
        errors.push('Password must contain at least one number');
      }
      
      if (authConfig.password.requireSpecial && !/[!@#$%^&*]/.test(data.password)) {
        errors.push('Password must contain at least one special character');
      }
    }
    
    // Validate name
    if (!data.firstName || !data.lastName) {
      errors.push('First and last name are required');
    }
    
    return {
      valid: errors.length === 0,
      errors
    };
  }
};
3

Login and Authentication

Implement secure login and authentication:

// Authentication service
const authService = {
  async login(email, password) {
    // Find user
    const user = await db.users.findOne({ email });
    
    if (!user) {
      throw new Error('Invalid credentials');
    }
    
    // Check if user is active
    if (user.status !== 'active') {
      throw new Error('Account is not active');
    }
    
    // Verify password
    const isValid = await bcrypt.compare(password, user.passwordHash);
    
    if (!isValid) {
      // Log failed attempt
      await this.logAuthAttempt(user.id, 'login', false);
      throw new Error('Invalid credentials');
    }
    
    // Log successful attempt
    await this.logAuthAttempt(user.id, 'login', true);
    
    // Update last login
    await db.users.update(
      { id: user.id },
      { lastLogin: new Date() }
    );
    
    // Generate token
    const token = this.generateToken(user);
    
    return {
      user: userService.sanitizeUser(user),
      token
    };
  },
  
  generateToken(user) {
    const payload = {
      sub: user.id,
      email: user.email,
      role: user.role
    };
    
    return jwt.sign(
      payload,
      authConfig.jwt.secret,
      {
        expiresIn: authConfig.jwt.expiresIn,
        algorithm: authConfig.jwt.algorithm,
        issuer: authConfig.jwt.issuer,
        audience: authConfig.jwt.audience
      }
    );
  },
  
  async verifyToken(token) {
    try {
      const decoded = jwt.verify(
        token,
        authConfig.jwt.secret,
        {
          algorithms: [authConfig.jwt.algorithm],
          issuer: authConfig.jwt.issuer,
          audience: authConfig.jwt.audience
        }
      );
      
      // Get user
      const user = await db.users.findOne({ id: decoded.sub });
      
      if (!user || user.status !== 'active') {
        return null;
      }
      
      return userService.sanitizeUser(user);
    } catch (error) {
      return null;
    }
  }
};
4

Multi-Factor Authentication

Implement multi-factor authentication:

// MFA service
const mfaService = {
  // MFA methods
  methods: {
    totp: {
      name: 'Time-based One-Time Password',
      setup: async (userId) => {
        // Generate secret
        const secret = speakeasy.generateSecret({
          length: 20,
          name: `Symbiosis.host (${userId})`
        });
        
        // Store secret
        await db.mfa.create({
          userId,
          method: 'totp',
          secret: secret.base32,
          active: false,
          createdAt: new Date()
        });
        
        // Generate QR code
        const qrCode = await qrcode.toDataURL(secret.otpauth_url);
        
        return {
          secret: secret.base32,
          qrCode
        };
      },
      
      verify: async (userId, token) => {
        // Get MFA record
        const mfa = await db.mfa.findOne({
          userId,
          method: 'totp'
        });
        
        if (!mfa) {
          return false;
        }
        
        // Verify token
        const verified = speakeasy.totp.verify({
          secret: mfa.secret,
          encoding: 'base32',
          token,
          window: 1 // Allow 1 period before/after
        });
        
        if (verified && !mfa.active) {
          // Activate MFA if this is the first verification
          await db.mfa.update(
            { id: mfa.id },
            { active: true }
          );
        }
        
        return verified;
      }
    },
    
    sms: {
      name: 'SMS Authentication',
      setup: async (userId, phoneNumber) => {
        // Validate phone number
        if (!phoneNumber || !phoneNumber.match(/^\+[1-9]\d{1,14}$/)) {
          throw new Error('Invalid phone number format');
        }
        
        // Store phone number
        await db.mfa.create({
          userId,
          method: 'sms',
          phoneNumber,
          active: false,
          createdAt: new Date()
        });
        
        // Generate verification code
        const code = this.generateCode();
        
        // Store code
        await db.verificationCodes.create({
          userId,
          code,
          type: 'mfa_setup',
          expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes
        });
        
        // Send SMS
        await smsService.send(phoneNumber, `Your verification code is: ${code}`);
        
        return true;
      },
      
      verify: async (userId, code) => {
        // Get verification code
        const verification = await db.verificationCodes.findOne({
          userId,
          code,
          type: 'mfa_setup',
          expiresAt: { $gt: new Date() }
        });
        
        if (!verification) {
          return false;
        }
        
        // Delete verification code
        await db.verificationCodes.delete({ id: verification.id });
        
        // Activate MFA
        await db.mfa.update(
          { userId, method: 'sms' },
          { active: true }
        );
        
        return true;
      },
      
      challenge: async (userId) => {
        // Get MFA record
        const mfa = await db.mfa.findOne({
          userId,
          method: 'sms',
          active: true
        });
        
        if (!mfa) {
          throw new Error('SMS MFA not set up');
        }
        
        // Generate code
        const code = this.generateCode();
        
        // Store code
        await db.verificationCodes.create({
          userId,
          code,
          type: 'mfa_login',
          expiresAt: new Date(Date.now() + 10 * 60 * 1000) // 10 minutes
        });
        
        // Send SMS
        await smsService.send(mfa.phoneNumber, `Your login code is: ${code}`);
        
        return true;
      }
    }
  },
  
  // Generate random code
  generateCode() {
    return Math.floor(100000 + Math.random() * 900000).toString();
  },
  
  // Get user's MFA methods
  async getUserMethods(userId) {
    const methods = await db.mfa.find({ userId });
    
    return methods.map(method => ({
      id: method.id,
      method: method.method,
      name: this.methods[method.method].name,
      active: method.active,
      createdAt: method.createdAt
    }));
  }
};
5

Authorization and Access Control

Implement authorization and access control:

// Authorization service
const authorizationService = {
  // Role definitions
  roles: {
    admin: {
      name: 'Administrator',
      permissions: ['read:*', 'write:*', 'delete:*']
    },
    manager: {
      name: 'Manager',
      permissions: ['read:*', 'write:users', 'write:content']
    },
    user: {
      name: 'User',
      permissions: ['read:content', 'write:own_content']
    },
    guest: {
      name: 'Guest',
      permissions: ['read:public_content']
    }
  },
  
  // Check if user has permission
  hasPermission(user, permission) {
    if (!user) {
      return this.roles.guest.permissions.includes(permission);
    }
    
    const role = this.roles[user.role];
    
    if (!role) {
      return false;
    }
    
    // Check for wildcard permissions
    for (const perm of role.permissions) {
      if (perm === permission) {
        return true;
      }
      
      if (perm.endsWith(':*')) {
        const prefix = perm.slice(0, -2);
        if (permission.startsWith(prefix)) {
          return true;
        }
      }
    }
    
    return false;
  },
  
  // Middleware to check permission
  requirePermission(permission) {
    return (req, res, next) => {
      if (!this.hasPermission(req.user, permission)) {
        return res.status(403).json({
          error: 'Forbidden',
          message: 'You do not have permission to access this resource'
        });
      }
      
      next();
    };
  },
  
  // Assign role to user
  async assignRole(userId, role) {
    if (!this.roles[role]) {
      throw new Error(`Invalid role: ${role}`);
    }
    
    await db.users.update(
      { id: userId },
      { role }
    );
    
    return true;
  }
};

Best Practices

Password Security

Best practices for password management:

  • Use strong hashing algorithms
  • Implement password policies
  • Prevent credential stuffing
  • Secure password reset flows

Token Management

Secure token handling:

  • Short expiration times
  • Secure storage
  • Proper validation
  • Token revocation

Multi-Factor Authentication

Enhance security with MFA:

  • Offer multiple methods
  • Secure recovery options
  • Risk-based challenges
  • Clear user guidance

Authentication Methods Comparison

Method Pros Cons Best For
Session-based
  • Easy to implement
  • Stateful (easy to invalidate)
  • Well-established pattern
  • Requires server storage
  • Session fixation risks
  • Scaling challenges
Traditional web applications, single-server setups
JWT-based
  • Stateless
  • Scales horizontally
  • Cross-domain capable
  • Difficult to invalidate
  • Token size concerns
  • Secret management
Microservices, SPAs, distributed systems
OAuth/OIDC
  • Delegated authentication
  • Industry standard
  • Separation of concerns
  • Complex implementation
  • Dependency on providers
  • Additional network requests
Applications requiring third-party integration, SSO
Passwordless
  • Improved user experience
  • No password storage
  • Resistant to phishing
  • Requires secondary channel
  • Email/SMS delivery issues
  • User adoption challenges
Consumer applications, security-focused services

Security Considerations

OWASP Top 10 Mitigations

  • Broken Authentication: Implement account lockout, MFA, and strong password policies
  • Sensitive Data Exposure: Encrypt sensitive data, use HTTPS, and implement proper access controls
  • Broken Access Control: Enforce proper authorization checks and principle of least privilege
  • Security Misconfiguration: Use secure defaults and regularly audit configurations
  • Cross-Site Scripting: Implement proper output encoding and Content Security Policy

Common Attack Vectors

  • Credential Stuffing: Use rate limiting, CAPTCHA, and monitor for unusual login patterns
  • Brute Force: Implement progressive delays and account lockout policies
  • Session Hijacking: Use secure cookies, HTTPS, and short session timeouts
  • Man-in-the-Middle: Enforce HTTPS with HSTS and certificate pinning
  • Phishing: Implement MFA and user education

Common Issues

Authentication Failures

Common authentication problems:

  • Expired tokens or sessions
  • Incorrect credentials
  • Account lockouts
  • CORS issues with tokens

MFA Challenges

Multi-factor authentication issues:

  • Time synchronization problems
  • Lost access to MFA device
  • SMS delivery failures
  • Recovery code management

Next Steps

Now that you understand authentication, explore these related topics: