javascript
53 lines · 9 steps
Remember-me login with signed cookies in Express
An Express router that logs users in and issues a tamper-proof persistent cookie for staying signed in.
Explained by
highlit
1const express = require('express');
2const cookieParser = require('cookie-parser');
3
4const router = express.Router();
5
6router.use(cookieParser(process.env.COOKIE_SECRET));
7
8const REMEMBER_COOKIE = 'remember_me';
9const THIRTY_DAYS = 30 * 24 * 60 * 60 * 1000;
10
11router.post('/login', async (req, res) => {
12 const { email, password, rememberMe } = req.body;
13
14 const user = await User.authenticate(email, password);
15 if (!user) {
16 return res.status(401).json({ error: 'Invalid credentials' });
17 }
18
19 req.session.userId = user.id;
20
21 if (rememberMe) {
22 const token = await user.issueRememberToken();
23 res.cookie(REMEMBER_COOKIE, token, {
24 signed: true,
25 httpOnly: true,
26 secure: process.env.NODE_ENV === 'production',
27 sameSite: 'lax',
28 maxAge: THIRTY_DAYS,
29 });
30 } else {
31 res.clearCookie(REMEMBER_COOKIE);
32 }
33
34 res.json({ id: user.id, email: user.email });
35});
36
37router.get('/session', async (req, res) => {
38 const token = req.signedCookies[REMEMBER_COOKIE];
39 if (!token) {
40 return res.status(204).end();
41 }
42
43 const user = await User.findByRememberToken(token);
44 if (!user) {
45 res.clearCookie(REMEMBER_COOKIE);
46 return res.status(204).end();
47 }
48
49 req.session.userId = user.id;
50 res.json({ id: user.id, email: user.email });
51});
52
53module.exports = router;
01 / 01
STEP 01
‹ swipe to step through ›
Walkthrough
Space play
←→ step
click any line
Three takeaways
- 1Signing cookies with a secret lets the server detect tampering without storing the value server-side.
- 2Flags like httpOnly, secure, and sameSite harden auth cookies against XSS and CSRF vectors.
- 3A remember-me token should be re-validated against the database on every session restore, not trusted blindly.
Related explainers
javascript
import { NextResponse } from 'next/server'; const locales = ['en', 'fr', 'de', 'es']; const defaultLocale = 'en';
Locale routing with Next.js middleware
middleware
i18n
content-negotiation
Intermediate
10 steps
go
package middleware import ( "crypto/sha256"
Deduplicating concurrent requests in Gin
singleflight
request-coalescing
middleware
Advanced
8 steps
javascript
const TOKEN_SPECS = [ ["comment", /^\/\/[^\n]*|^\/\*[\s\S]*?\*\//], ["string", /^"(?:\\.|[^"\\])*"|^'(?:\\.|[^'\\])*'|^`(?:\\.|[^`\\])*`/], ["number", /^0[xX][\da-fA-F]+|^\d+(?:\.\d+)?(?:[eE][+-]?\d+)?/],
Building a syntax highlighter tokenizer
tokenizer
regular-expressions
lexing
Intermediate
8 steps
javascript
export async function compressImage(file, { maxWidth = 1600, maxHeight = 1600, quality = 0.8, mimeType = 'image/jpeg' } = {}) { const bitmap = await createImageBitmap(file); let { width, height } = bitmap;
Compressing images in the browser with canvas
canvas
image-processing
promises
Intermediate
7 steps
rust
use std::time::Duration; use axum::{ http::{header, HeaderValue, Request},
Serving fingerprinted assets in Axum
static-assets
http-caching
middleware
Intermediate
7 steps
javascript
const express = require('express'); const multer = require('multer'); const path = require('path'); const crypto = require('crypto');
Safe image uploads with Multer in Express
file-upload
multer
validation
Intermediate
7 steps
Share this explainer
Here's the card — post it anywhere.
Made with highlit — turn any snippet into a walkthrough like this in about a minute.
Explain your code
Embed this explainer
Drop the interactive walkthrough into a blog or docs. Views never cost a credit.
<iframe src="https://highlit.co/explainers/remember-me-login-with-signed-cookies-in-express-explained-javascript-7e9e/embed?autoplay=1" width="100%" height="520" loading="lazy" style="border:0"></iframe>
Autoplay is on by default — add ?autoplay=0 to start paused.