Skip to main content
โšก Calmops

Authentication: JWT, OAuth, Sessions

Authentication: JWT, OAuth, Sessions

Authentication is critical for securing applications. This article covers JWT, OAuth, and session-based authentication.

Introduction

Authentication provides:

  • User identity verification
  • Secure access control
  • Token management
  • Session handling
  • Third-party integration

Understanding authentication helps you:

  • Secure applications
  • Manage user sessions
  • Implement authorization
  • Integrate with OAuth providers
  • Protect sensitive data

JWT Authentication

JWT Basics

// โœ… Good: Install JWT package
// npm install jsonwebtoken

const jwt = require('jsonwebtoken');

// โœ… Good: Create JWT token
function createToken(user) {
  const payload = {
    id: user.id,
    email: user.email,
    role: user.role
  };

  const token = jwt.sign(payload, process.env.JWT_SECRET, {
    expiresIn: '1h'
  });

  return token;
}

// โœ… Good: Verify JWT token
function verifyToken(token) {
  try {
    const decoded = jwt.verify(token, process.env.JWT_SECRET);
    return decoded;
  } catch (err) {
    return null;
  }
}

// โœ… Good: Refresh token pattern
function createTokenPair(user) {
  const accessToken = jwt.sign(
    { id: user.id, email: user.email },
    process.env.JWT_SECRET,
    { expiresIn: '15m' }
  );

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

  return { accessToken, refreshToken };
}

// โœ… Good: Refresh access token
function refreshAccessToken(refreshToken) {
  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
    const newAccessToken = jwt.sign(
      { id: decoded.id },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
    return newAccessToken;
  } catch (err) {
    return null;
  }
}

JWT Middleware

// middleware/auth.js
const jwt = require('jsonwebtoken');

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) {
    res.status(401).json({ error: 'Invalid token' });
  }
};

const authorize = (roles) => {
  return (req, res, next) => {
    if (!roles.includes(req.user.role)) {
      return res.status(403).json({ error: 'Forbidden' });
    }
    next();
  };
};

module.exports = { authenticate, authorize };

// routes/auth.js
const express = require('express');
const jwt = require('jsonwebtoken');
const bcrypt = require('bcrypt');
const User = require('../models/User');

const router = express.Router();

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

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

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

    const token = jwt.sign(
      { id: user._id, email: user.email, role: user.role },
      process.env.JWT_SECRET,
      { expiresIn: '1h' }
    );

    res.json({ token });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

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

  try {
    const decoded = jwt.verify(refreshToken, process.env.REFRESH_TOKEN_SECRET);
    const newAccessToken = jwt.sign(
      { id: decoded.id },
      process.env.JWT_SECRET,
      { expiresIn: '15m' }
    );
    res.json({ accessToken: newAccessToken });
  } catch (err) {
    res.status(401).json({ error: 'Invalid refresh token' });
  }
});

module.exports = router;

Session-Based Authentication

Session Setup

// โœ… Good: Install session packages
// npm install express-session connect-mongo

const session = require('express-session');
const MongoStore = require('connect-mongo');

const app = express();

// โœ… Good: Configure sessions
app.use(session({
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  store: new MongoStore({
    mongoUrl: process.env.MONGODB_URI
  }),
  cookie: {
    secure: process.env.NODE_ENV === 'production',
    httpOnly: true,
    maxAge: 1000 * 60 * 60 * 24 // 24 hours
  }
}));

// โœ… Good: Login with sessions
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' });
    }

    req.session.userId = user._id;
    req.session.user = user;

    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('/profile', requireLogin, (req, res) => {
  res.json({ user: req.session.user });
});

// โœ… Good: 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' });
  });
});

OAuth 2.0 Authentication

OAuth Setup with Passport

// โœ… Good: Install Passport
// npm install passport passport-google-oauth20

const passport = require('passport');
const GoogleStrategy = require('passport-google-oauth20').Strategy;
const User = require('./models/User');

// โœ… Good: Configure Passport
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 {
    let user = await User.findOne({ googleId: profile.id });

    if (!user) {
      user = new User({
        googleId: profile.id,
        email: profile.emails[0].value,
        name: profile.displayName
      });
      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
const app = express();

app.use(passport.initialize());
app.use(passport.session());

app.get('/auth/google',
  passport.authenticate('google', { scope: ['profile', 'email'] })
);

app.get('/auth/google/callback',
  passport.authenticate('google', { failureRedirect: '/login' }),
  (req, res) => {
    res.redirect('/dashboard');
  }
);

app.get('/profile', (req, res) => {
  if (!req.isAuthenticated()) {
    return res.status(401).json({ error: 'Not authenticated' });
  }
  res.json({ user: req.user });
});

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

Password Security

Password Hashing

// โœ… Good: Install bcrypt
// npm install bcrypt

const bcrypt = require('bcrypt');

// โœ… Good: Hash password
async function hashPassword(password) {
  const salt = await bcrypt.genSalt(10);
  return bcrypt.hash(password, salt);
}

// โœ… Good: Compare password
async function comparePassword(password, hash) {
  return bcrypt.compare(password, hash);
}

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

    // Check if user exists
    const existingUser = await User.findOne({ email });
    if (existingUser) {
      return res.status(400).json({ error: 'User already exists' });
    }

    // Hash password
    const hashedPassword = await hashPassword(password);

    // Create user
    const user = new User({
      email,
      password: hashedPassword,
      name
    });

    await user.save();

    res.status(201).json({ message: 'User created successfully' });
  } catch (err) {
    res.status(500).json({ error: 'Server error' });
  }
});

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

    const user = await User.findOne({ email });
    if (!user) {
      return res.status(404).json({ error: 'User not found' });
    }

    user.password = await hashPassword(newPassword);
    await user.save();

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

Best Practices

  1. Use HTTPS:

    // โœ… Good: HTTPS in production
    const https = require('https');
    const fs = require('fs');
    
    const options = {
      key: fs.readFileSync('key.pem'),
      cert: fs.readFileSync('cert.pem')
    };
    
    https.createServer(options, app).listen(443);
    
    // โŒ Bad: HTTP in production
    app.listen(3000);
    
  2. Secure cookies:

    // โœ… Good: Secure cookie settings
    app.use(session({
      cookie: {
        secure: true,
        httpOnly: true,
        sameSite: 'strict'
      }
    }));
    
    // โŒ Bad: Insecure cookies
    app.use(session({
      cookie: { secure: false }
    }));
    
  3. Validate tokens:

    // โœ… Good: Validate token
    const authenticate = (req, res, next) => {
      const token = req.headers.authorization?.split(' ')[1];
      if (!token) return res.status(401).json({ error: 'No token' });
      try {
        req.user = jwt.verify(token, process.env.JWT_SECRET);
        next();
      } catch (err) {
        res.status(401).json({ error: 'Invalid token' });
      }
    };
    
    // โŒ Bad: No validation
    const authenticate = (req, res, next) => {
      next();
    };
    

Summary

Authentication is essential. Key takeaways:

  • Use JWT for stateless authentication
  • Use sessions for stateful authentication
  • Implement OAuth for third-party login
  • Hash passwords with bcrypt
  • Use HTTPS in production
  • Secure cookies properly
  • Validate tokens
  • Implement refresh tokens

Next Steps

Comments