The Difference Between Authentication and Authorization
Authentication is the process of verifying “Who are you?”, while authorization is the process of determining “What are you allowed to do?”. Using a hotel analogy, checking your ID at the front desk is authentication, and the keycard granting access only to specific floors/rooms is authorization.
| Concept | Authentication (AuthN) | Authorization (AuthZ) |
|---|---|---|
| Question | Who are you? | What can you do? |
| Timing | At login | When accessing resources |
| Result | User identification info | Permissions/roles |
| Examples | ID/PW, OAuth | RBAC, ABAC |
JWT (JSON Web Token) Structure
A JWT consists of three parts: Header.Payload.Signature
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9. ← Header (algorithm, type)
eyJzdWIiOiIxMjM0NTY3ODkwIiwibmFtZSI6Ik ← Payload (claims, user info)
SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQ ← Signature (tamper protection)
JWT Creation and Verification
import jwt from "jsonwebtoken";
const JWT_SECRET = process.env.JWT_SECRET; // Load secret from environment variable
// Generate JWT tokens
function generateTokens(user) {
// Access Token — short-lived (15 minutes)
const accessToken = jwt.sign(
{
sub: user.id, // User unique ID
email: user.email,
role: user.role, // Permission info
type: "access"
},
JWT_SECRET,
{ expiresIn: "15m" } // Expires after 15 minutes
);
// Refresh Token — long-lived (7 days)
const refreshToken = jwt.sign(
{
sub: user.id,
type: "refresh"
},
JWT_SECRET,
{ expiresIn: "7d" } // Expires after 7 days
);
return { accessToken, refreshToken };
}
// Verify JWT token
function verifyToken(token) {
try {
const decoded = jwt.verify(token, JWT_SECRET);
return { valid: true, payload: decoded };
} catch (error) {
if (error.name === "TokenExpiredError") {
return { valid: false, error: "TOKEN_EXPIRED" };
}
return { valid: false, error: "INVALID_TOKEN" };
}
}
// Usage example
const user = { id: "user_123", email: "alice@example.com", role: "admin" };
const tokens = generateTokens(user);
console.log("Access:", tokens.accessToken);
// Output: Access: eyJhbGciOiJIUzI1NiIs...
const result = verifyToken(tokens.accessToken);
console.log("Verification:", result);
// Output: Verification: { valid: true, payload: { sub: 'user_123', ... } }
Middleware Implementation
// Express.js authentication middleware
function authMiddleware(req, res, next) {
// Extract token from Authorization header
const authHeader = req.headers.authorization;
if (!authHeader || !authHeader.startsWith("Bearer ")) {
return res.status(401).json({
error: { code: "NO_TOKEN", message: "Authentication token is required." }
});
}
const token = authHeader.split(" ")[1];
const result = verifyToken(token);
if (!result.valid) {
const status = result.error === "TOKEN_EXPIRED" ? 401 : 403;
return res.status(status).json({
error: { code: result.error, message: "Token is invalid." }
});
}
// Add verified user info to request object
req.user = result.payload;
next();
}
// Role-based authorization middleware
function requireRole(...roles) {
return (req, res, next) => {
if (!roles.includes(req.user.role)) {
return res.status(403).json({
error: { code: "FORBIDDEN", message: "Access denied." }
});
}
next();
};
}
// Apply to routes
app.get("/api/users", authMiddleware, (req, res) => {
// Only authenticated users can access
res.json({ users: [...] });
});
app.delete("/api/users/:id",
authMiddleware,
requireRole("admin"), // Only admin role can access
(req, res) => {
// Only admins can delete users
res.json({ message: "Deleted successfully" });
}
);
OAuth 2.0 Flow
OAuth 2.0 is a protocol for delegating authentication to third-party services. Social logins like “Sign in with Google” are the classic example.
Authorization Code Flow (Most Secure, Recommended)
1. User -> Client: Clicks "Sign in with Google"
2. Client -> Google: Redirects to authentication page
3. User -> Google: Logs in + grants consent
4. Google -> Client: Returns Authorization Code (redirect)
5. Client Server -> Google: Requests Token with Code + Client Secret
6. Google -> Client Server: Issues Access Token + Refresh Token
7. Client Server -> Google API: Fetches user info with Access Token
Implementation Example (Express + Passport)
import express from "express";
import passport from "passport";
import { Strategy as GoogleStrategy } from "passport-google-oauth20";
// Configure Google OAuth 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) => {
// Look up or create user from Google profile
let user = await findUserByGoogleId(profile.id);
if (!user) {
user = await createUser({
googleId: profile.id,
email: profile.emails[0].value,
name: profile.displayName,
avatar: profile.photos[0].value
});
}
return done(null, user);
}
));
const app = express();
// Start OAuth login — redirect to Google auth page
app.get("/auth/google",
passport.authenticate("google", {
scope: ["profile", "email"] // Requested permission scope
})
);
// OAuth callback — redirect after Google auth completes
app.get("/auth/google/callback",
passport.authenticate("google", { session: false }),
(req, res) => {
// Issue JWT tokens
const tokens = generateTokens(req.user);
// Set Refresh Token as httpOnly cookie
res.cookie("refresh_token", tokens.refreshToken, {
httpOnly: true, // Not accessible via JavaScript (XSS protection)
secure: true, // Only sent over HTTPS
sameSite: "strict", // CSRF protection
maxAge: 7 * 24 * 60 * 60 * 1000 // 7 days
});
// Access Token in response body
res.json({ accessToken: tokens.accessToken });
}
);
Token Refresh Strategy
// Reissue Access Token using Refresh Token
app.post("/auth/refresh", (req, res) => {
const refreshToken = req.cookies.refresh_token;
if (!refreshToken) {
return res.status(401).json({
error: { code: "NO_REFRESH_TOKEN", message: "Please log in again." }
});
}
const result = verifyToken(refreshToken);
if (!result.valid || result.payload.type !== "refresh") {
// Clear cookie and prompt re-login
res.clearCookie("refresh_token");
return res.status(401).json({
error: { code: "INVALID_REFRESH", message: "Please log in again." }
});
}
// Issue new token pair (Refresh Token Rotation)
const user = { id: result.payload.sub };
const tokens = generateTokens(user);
// Update cookie with new Refresh Token
res.cookie("refresh_token", tokens.refreshToken, {
httpOnly: true,
secure: true,
sameSite: "strict",
maxAge: 7 * 24 * 60 * 60 * 1000
});
res.json({ accessToken: tokens.accessToken });
});
Security Checklist
| Item | Recommendation | Risk |
|---|---|---|
| Access Token storage | Memory (variable) | localStorage is vulnerable to XSS |
| Refresh Token storage | httpOnly cookie | Not accessible via JavaScript |
| Token lifetime | Access: 15min, Refresh: 7 days | Too long increases theft risk |
| Signing algorithm | RS256 (asymmetric) or HS256 | Never allow none algorithm |
| CORS settings | Specify allowed domains | Never use * (wildcard) |
| HTTPS | Required | Tokens can be stolen over HTTP |
Practical Tips
- Keep Access Tokens short, Refresh Tokens long: Even if an Access Token is stolen, it expires in 15 minutes.
- Apply Refresh Token Rotation: Issuing a new pair on each Refresh Token use means a stolen token can only be used once.
- Never put sensitive information in JWTs: The Payload is only Base64-encoded, not encrypted. Do not include passwords or personal IDs.
- Maintain a token blacklist on logout: Since JWTs cannot be invalidated server-side, maintain a blacklist in Redis or similar stores.
- Use PKCE (Proof Key for Code Exchange): Prevents authorization code interception attacks when using OAuth in SPAs or mobile apps.