Session-Based Authentication — Complete Implementation Guide
In this tutorial, you will learn about Session. We cover key concepts, practical examples, and best practices to help you master this topic.
Session-based authentication stores user session data on the server and uses a cookie with a session ID to identify the client on subsequent requests, keeping session state server-side for easy management and revocation.
What You'll Learn
By the end of this lesson, you will implement session-based authentication in Node.js and Python, configure secure cookies, choose a session store, prevent session fixation, and handle session expiration and revocation.
Why It Matters
Session auth is the most widely used pattern for traditional web applications. It provides easy server-side session management, immediate revocation on logout, and works without JavaScript. DodaZIP's web interface uses session auth for user dashboards with Redis-backed session storage.
Real-World Use
A user logs into their email provider. The server creates a session, stores it in Redis, and sets an HTTP-only cookie. Every subsequent request includes the cookie automatically. When the user logs out, the server deletes the session, and the cookie becomes invalid.
Session Auth Flow
sequenceDiagram
participant Client
participant Server
participant SessionStore
Client->>Server: POST /login with credentials
Server->>SessionStore: Create session (userId, role, expiry)
SessionStore-->>Server: session_id
Server-->>Client: Set-Cookie: sid=session_id (HttpOnly, Secure)
Client->>Server: GET /profile (Cookie: sid=session_id)
Server->>SessionStore: Lookup session
SessionStore-->>Server: userId=1, role="user"
Server-->>Client: Profile data
Session Auth with Express
const express = require("express");
const session = require("express-session");
const RedisStore = require("connect-redis").default;
const redis = require("redis");
const app = express();
app.use(express.json());
const redisClient = redis.createClient({ url: "redis://localhost:6379" });
redisClient.connect().catch(console.error);
app.use(session({
store: new RedisStore({ client: redisClient, prefix: "session:" }),
secret: "change-this-to-a-random-secret",
resave: false,
saveUninitialized: false,
name: "app.sid",
cookie: {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "strict",
maxAge: 24 * 60 * 60 * 1000,
},
}));
app.post("/api/login", (req, res) => {
const { email, password } = req.body;
if (email === "alice@example.com" && password === "correct-password") {
req.session.userId = 1;
req.session.email = email;
req.session.role = "user";
return res.json({ message: "Logged in" });
}
res.status(401).json({ error: "Invalid credentials" });
});
function requireAuth(req, res, next) {
if (!req.session.userId) {
return res.status(401).json({ error: "Authentication required" });
}
next();
}
app.get("/api/profile", requireAuth, (req, res) => {
res.json({ userId: req.session.userId, email: req.session.email });
});
app.post("/api/logout", (req, res) => {
req.session.destroy(() => {
res.clearCookie("app.sid");
res.json({ message: "Logged out" });
});
});
app.listen(3000);
Expected output: Login sets secure cookie, subsequent requests authenticate via session lookup, logout destroys session and clears cookie.
Session Auth with Django
# settings.py
SESSION_ENGINE = "django.contrib.sessions.backends.cache"
SESSION_CACHE_ALIAS = "session"
SESSION_COOKIE_HTTPONLY = True
SESSION_COOKIE_SECURE = True # HTTPS only
SESSION_COOKIE_SAMESITE = "Strict"
SESSION_EXPIRE_AT_BROWSER_CLOSE = False
SESSION_COOKIE_AGE = 86400 # 24 hours
# views.py
from django.contrib.auth import login, logout, authenticate
from django.http import JsonResponse
def login_view(request):
user = authenticate(
username=request.POST["email"],
password=request.POST["password"]
)
if user:
login(request, user)
request.session["ip_address"] = request.META.get("REMOTE_ADDR")
return JsonResponse({"message": "Logged in"})
return JsonResponse({"error": "Invalid credentials"}, status=401)
def logout_view(request):
logout(request)
return JsonResponse({"message": "Logged out"})
Session Security
// Secure session configuration
app.use(session({
secret: crypto.randomBytes(64).toString("hex"),
name: "__Host-app.sid", // Prefix restricts cookie to the issuing domain
cookie: {
httpOnly: true, // Inaccessible to JavaScript
secure: true, // HTTPS only
sameSite: "lax", // CSRF protection
domain: "example.com", // Specific domain, never wildcard
path: "/", // Limit scope
maxAge: 24 * 60 * 60 * 1000,
},
rolling: true, // Reset maxAge on each request
}));
Common Mistakes
- Not setting HttpOnly on session cookies allows XSS to steal the session ID.
- Using default session cookie name "connect.sid" makes session identification obvious.
- Storing sessions in local memory instead of shared store causes logout on server restart.
- Not rotating session IDs after login allows session fixation attacks.
- Setting excessively long session expiration without activity timeout.
- Allowing concurrent unlimited sessions per user without limits.
Practice Questions
- Why should session IDs be rotated after login?
Session fixation attacks plant a session ID before login. If the ID is not rotated after authentication, the attacker can use the same ID to access the authenticated session.
- What is the difference between resave and saveUninitialized in Express session?
resave forces saving the session even if unchanged. saveUninitialized prevents saving empty sessions for unauthenticated visitors, reducing storage.
- How does SameSite cookie attribute protect against CSRF?
SameSite=Strict prevents the browser from sending the cookie with cross-origin requests. SameSite=Lax allows top-level navigation but blocks POST forms from other sites.
- Challenge: Build a session-based auth system with Redis store, IP address binding, device fingerprinting, concurrent session limits (max 5 per user), and forced logout on password change.
FAQ
Mini Project: Session Manager CLI
Build a CLI tool that connects to Redis and lists active sessions, allows force-killing sessions by user ID, and shows session statistics.
import redis
class SessionManager:
def __init__(self, redis_url="redis://localhost:6379"):
self.redis = redis.from_url(redis_url)
def list_sessions(self):
cursor = 0
sessions = []
while True:
cursor, keys = self.redis.scan(cursor, match="session:*", count=100)
sessions.extend(keys)
if cursor == 0:
break
print(f"Active sessions: {len(sessions)}")
for key in sessions[:10]:
ttl = self.redis.ttl(key)
print(f" {key}: TTL {ttl}s")
def kill_user_sessions(self, user_id):
pattern = f"session:*"
cursor = 0
deleted = 0
while True:
cursor, keys = self.redis.scan(cursor, match=pattern)
for key in keys:
data = self.redis.get(key)
if data and f'"userId":{user_id}' in data:
self.redis.delete(key)
deleted += 1
if cursor == 0:
break
print(f"Deleted {deleted} sessions for user {user_id}")
What's Next
Learn about JWT authentication for stateless API security, then explore OAuth 2.0 for delegated authorization.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro