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
-
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); -
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 } })); -
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
Related Resources
- JWT Documentation
- OAuth 2.0 Specification
- Passport.js Documentation
- OWASP Authentication
- bcrypt Documentation
Next Steps
- Learn about Error Handling
- Explore Deployment
- Study Full-Stack Development
- Practice authentication
- Build secure applications
Comments