Skip to main content
โšก Calmops

Authentication Flows: JWT, OAuth, Sessions

Authentication Flows: JWT, OAuth, Sessions

Authentication flows are critical for securing applications. This article covers different authentication patterns.

Introduction

Authentication flows provide:

  • User identity verification
  • Secure token management
  • Third-party integration
  • Session management
  • Access control

Understanding authentication flows helps you:

  • Implement secure authentication
  • Choose appropriate patterns
  • Handle token refresh
  • Integrate OAuth providers
  • Manage user sessions

JWT Authentication Flow

JWT Flow Diagram

Client                          Server
  |                               |
  |--- POST /login ------->       |
  |    (email, password)          |
  |                               |
  |<----- JWT Token ------        |
  |                               |
  |--- GET /api/users ----->      |
  |    (Authorization: Bearer JWT)|
  |                               |
  |<----- User Data ------        |

JWT Implementation

// โœ… Good: JWT login flow
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');

app.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    // Find user
    const user = await User.findOne({ email });
    if (!user) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Verify password
    const isPasswordValid = await bcrypt.compare(password, user.password);
    if (!isPasswordValid) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Create tokens
    const accessToken = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    const refreshToken = jwt.sign(
      { id: user._id },
      process.env.REFRESH_TOKEN_SECRET,
      { expiresIn: '7d' }
    );

    // Store refresh token
    user.refreshToken = refreshToken;
    await user.save();

    res.json({
      accessToken,
      refreshToken,
      expiresIn: 900 // 15 minutes
    });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

// โœ… Good: JWT verification middleware
const authenticate = (req, res, next) => {
  const token = req.headers.authorization?.split(' ')[1];

  if (!token) {
    return res.status(401).json({ error: 'No token provided' });
  }

  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    req.user = decoded;
    next();
  } catch (err) {
    if (err.name === 'TokenExpiredError') {
      return res.status(401).json({ error: 'Token expired' });
    }
    res.status(401).json({ error: 'Invalid token' });
  }
};

// โœ… Good: Refresh token endpoint
app.post('/refresh', async (req, res) => {
  try {
    const { refreshToken } = req.body;

    if (!refreshToken) {
      return res.status(401).json({ error: 'No refresh token' });
    }

    // Verify refresh token
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);

    // Find user and verify token
    const user = await User.findById(decoded.id);
    if (!user || user.refreshToken !== refreshToken) {
      return res.status(401).json({ error: 'Invalid refresh token' });
    }

    // Create new access token
    const newAccessToken = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );

    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

// โœ… Good: Logout endpoint
app.post('/logout', authenticate, async (req, res) => {
  try {
    const user = await User.findById(req.user.id);
    user.refreshToken = null;
    await user.save();

    res.json({ message: 'Logged out successfully' });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

OAuth 2.0 Flow

OAuth Flow Diagram

Client App                  Authorization Server
  |                                |
  |--- Redirect to login ----->    |
  |                                |
  |<----- User logs in -----       |
  |                                |
  |<----- Authorization Code --    |
  |                                |
  |--- Exchange code for token --> |
  |                                |
  |<----- Access Token -----       |
  |                                |
  |--- Request user data ----->    |
  |                                |
  |<----- User Data -----          |

OAuth Implementation

// โœ… Good: OAuth with Passport
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.findOne({ googleId: profile.id });

    if (!user) {
      user = new User({
        googleId: profile.id,
        email: profile.emails[0].value,
        name: profile.displayName,
        avatar: profile.photos[0].value
      });
      await user.save();
    }

    return done(null, user);
  } catch (err) {
    return done(err);
  }
}));

passport.serializeUser((user, done) => {
  done(null, user.id);
});

passport.deserializeUser(async (id, done) => {
  try {
    const user = await User.findById(id);
    done(null, user);
  } catch (err) {
    done(err);
  }
});

// โœ… Good: OAuth routes
app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    // Create JWT token
    const token = jwt.sign(
      { id: req.user._id, email: req.user.email },
      process.env.JWT_SECRET,
      { expiresIn: '7d' }
    );

    res.redirect(`/dashboard?token=${token}`);
  }
);

// โœ… Good: OAuth logout
app.post('/logout', (req, res) => {
  req.logout((err) => {
    if (err) {
      return res.status(500).json({ error: 'Logout failed' });
    }
    res.json({ message: 'Logged out successfully' });
  });
});

Session-Based Authentication Flow

Session Flow Diagram

Client                          Server
  |                               |
  |--- POST /login ------->       |
  |    (email, password)          |
  |                               |
  |<----- Set-Cookie: SID --      |
  |                               |
  |--- GET /api/users ----->      |
  |    (Cookie: SID)              |
  |                               |
  |<----- User Data ------        |

Session Implementation

// โœ… Good: Session configuration
const session = require('express-session');
const MongoStore = require('connect-mongo');

app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: new MongoStore({
    mongoUrl: process.env.MONGODB_URI,
    touchAfter: 24 * 3600 // Lazy session update
  }),
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    sameSite: 'strict',
    maxAge: 1000 * 60 * 60 * 24 // 24 hours
  }
}));

// โœ… Good: Session login
app.post('/login', async (req, res) => {
  try {
    const { email, password } = req.body;

    const user = await User.findOne({ email });
    if (!user || !await bcrypt.compare(password, user.password)) {
      return res.status(401).json({ error: 'Invalid credentials' });
    }

    // Store user in session
    req.session.userId = user._id;
    req.session.user = {
      id: user._id,
      email: user.email,
      role: user.role
    };

    res.json({ message: 'Logged in successfully' });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

// โœ… Good: Session middleware
const requireLogin = (req, res, next) => {
  if (!req.session.userId) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  next();
};

app.get('/api/profile', requireLogin, (req, res) => {
  res.json({ user: req.session.user });
});

// โœ… Good: Session logout
app.post('/logout', (req, res) => {
  req.session.destroy((err) => {
    if (err) {
      return res.status(500).json({ error: 'Logout failed' });
    }
    res.json({ message: 'Logged out successfully' });
  });
});

Multi-Factor Authentication

MFA Implementation

// โœ… Good: TOTP (Time-based One-Time Password)
const speakeasy = require('speakeasy');
const QRCode = require('qrcode');

// Generate MFA secret
app.post('/mfa/setup', authenticate, async (req, res) => {
  const secret = speakeasy.generateSecret({
    name: `MyApp (${req.user.email})`,
    issuer: 'MyApp'
  });

  // Generate QR code
  const qrCode = await QRCode.toDataURL(secret.otpauth_url);

  res.json({
    secret: secret.base32,
    qrCode
  });
});

// Verify MFA token
app.post('/mfa/verify', authenticate, async (req, res) => {
  const { token, secret } = req.body;

  const verified = speakeasy.totp.verify({
    secret,
    encoding: 'base32',
    token,
    window: 2
  });

  if (!verified) {
    return res.status(400).json({ error: 'Invalid token' });
  }

  // Save MFA secret
  const user = await User.findByIdAndUpdate(
    req.user.id,
    { mfaSecret: secret, mfaEnabled: true }
  );

  res.json({ message: 'MFA enabled' });
});

// โœ… Good: Email-based MFA
app.post('/mfa/email', authenticate, async (req, res) => {
  const code = Math.random().toString().slice(2, 8);

  // Send email
  await sendEmail(req.user.email, 'MFA Code', `Your code: ${code}`);

  // Store code temporarily
  await redis.setex(`mfa:${req.user.id}`, 300, code);

  res.json({ message: 'Code sent to email' });
});

// Verify email MFA
app.post('/mfa/email/verify', authenticate, async (req, res) => {
  const { code } = req.body;

  const storedCode = await redis.get(`mfa:${req.user.id}`);
  if (storedCode !== code) {
    return res.status(400).json({ error: 'Invalid code' });
  }

  // Clear code
  await redis.del(`mfa:${req.user.id}`);

  res.json({ message: 'MFA verified' });
});

Best Practices

  1. Use HTTPS:

    // โœ… Good: HTTPS in production
    const https = require('https');
    https.createServer(options, app).listen(443);
    
    // โŒ Bad: HTTP
    app.listen(3000);
    
  2. Secure token storage:

    // โœ… Good: Store in httpOnly cookie
    res.cookie('token', token, {
      httpOnly: true,
      secure: true,
      sameSite: 'strict'
    });
    
    // โŒ Bad: Store in localStorage
    localStorage.setItem('token', token);
    
  3. Implement rate limiting:

    // โœ… Good: Rate limit login
    const loginLimiter = rateLimit({
      windowMs: 15 * 60 * 1000,
      max: 5
    });
    
    app.post('/login', loginLimiter, (req, res) => {
      // Login logic
    });
    

Summary

Authentication flows are essential. Key takeaways:

  • Understand JWT flow
  • Implement OAuth 2.0
  • Use sessions appropriately
  • Implement MFA
  • Use HTTPS
  • Secure token storage
  • Rate limit authentication
  • Handle token refresh

Next Steps

Comments