Here's a comprehensive list of common authentication methods used in Node.js backends:
- Uses server-side sessions (stored in memory, Redis, or a database).
- Relies on cookies (
express-session
+cookie-parser
). - Libraries:
express-session
βcookie-session
connect-redis
(for Redis-based sessions)
- Stateless authentication using digitally signed tokens.
- Tokens are stored client-side (localStorage, cookies, or mobile storage).
- Libraries:
jsonwebtoken
(JWT)passport-jwt
(for JWT strategy in Passport.js)
- Delegated authentication (e.g., "Login with Google/GitHub/Facebook").
- Uses access tokens & refresh tokens.
- Libraries:
passport-oauth2
openid-client
google-auth-library
(for Google OAuth)
- Simple authentication using a unique API key (often in headers or query params).
- Used for server-to-server communication.
- Libraries: Custom middleware or
express-api-key-auth
.
- Uses
username:password
encoded in Base64 in theAuthorization
header. - Libraries:
express-basic-auth
- Custom middleware with
Buffer.from(req.headers.authorization, 'base64')
- Supports multiple auth methods (Local, JWT, OAuth, etc.).
- Strategies:
passport-local
(username/password)passport-jwt
passport-google-oauth20
passport-github
- Users log in via a link sent to their email.
- Libraries:
magic-link
- Custom implementation with JWT + email service (Nodemailer).
- Uses TOTP (Time-Based OTP) or SMS-based verification.
- Libraries:
speakeasy
(TOTP)node-2fa
- Twilio (for SMS-based 2FA)
- Enterprise authentication (used in corporate environments).
- Libraries:
ldapjs
passport-ldapauth
- Login via Google, Facebook, GitHub, etc.
- Libraries:
passport-google-oauth20
passport-facebook
passport-github2
- Managed auth service by Google (supports email, phone, social logins).
- Library:
firebase-admin
- Uses hardware security keys or biometrics (fingerprint, FaceID).
- Libraries:
fido2-lib
simplewebauthn
helmet
(Secure HTTP headers)bcrypt
/argon2
(Password hashing)rate-limiter-flexible
(Prevent brute-force attacks)
A simple yet secure authentication system built with Express.js using session-based authentication, bcrypt password hashing, and TypeScript.
- β User registration with password hashing
- β Session-based login/logout
- β Protected routes with middleware
- β Secure session configuration
- β TypeScript support
- β HTTP-only cookies for security
npm start
The server will start on http://localhost:3000
Method | Endpoint | Description | Protected |
---|---|---|---|
GET | / |
API status and auth info | No |
POST | /register |
Register new user | No |
POST | /login |
User login | No |
POST | /logout |
User logout | No |
GET | /profile |
Get user profile | Yes |
GET | /dashboard |
User dashboard | Yes |
GET | /session |
Check session status | No |
Sessions are automatically created when users log in successfully:
// Login endpoint creates session
app.post('/login', async (req: Request, res: Response) => {
// ... password validation ...
// π― Session Creation
req.session.userId = user.id; // Store user ID
req.session.username = user.username; // Store username
// Session cookie automatically sent to client
});
What happens:
- Server generates unique session ID
- Session data stored server-side (memory by default)
- Secure HTTP-only cookie sent to client with session ID
- Client automatically sends cookie with future requests
app.use(session({
secret: 'your-secret-key-change-in-production', // π Signs session cookies
resave: false, // Don't save unchanged sessions
saveUninitialized: false, // Don't save empty sessions
cookie: {
secure: false, // Set true for HTTPS in production
httpOnly: true, // π‘οΈ Prevents XSS attacks
maxAge: 24 * 60 * 60 * 1000 // β° 24 hours timeout
}
}));
Session Timeout Options:
// Different timeout configurations
maxAge: 30 * 60 * 1000, // 30 minutes
maxAge: 2 * 60 * 60 * 1000, // 2 hours
maxAge: 24 * 60 * 60 * 1000, // 24 hours
maxAge: 7 * 24 * 60 * 60 * 1000, // 7 days
Every protected route uses the requireAuth
middleware:
const requireAuth = (req: Request, res: Response, next: NextFunction) => {
// π Session Verification
if (req.session.userId) {
next(); // β
Valid session - proceed
} else {
res.status(401).json({ error: 'Authentication required' }); // β Invalid
}
};
// Apply to protected routes
app.get('/dashboard', requireAuth, (req, res) => {
// This only runs if session is valid
});
Session Renewal on Every Request
app.use(session({
secret: 'your-secret-key-change-in-production',
resave: false,
saveUninitialized: false,
rolling: true, // Add this line
cookie: {
secure: false,
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 hours
}
}) as express.RequestHandler);
Manual Session Renewal:
const requireAuth = (req: Request, res: Response, next: NextFunction): void => {
if (req.session.userId) {
req.session.touch(); // This will save the session and renew the cookie
next();
} else {
res.status(401).json({ error: 'Authentication required' });
}
};
app.post('/logout', (req, res) => {
req.session.destroy((err) => {
if (err) {
return res.status(500).json({ error: 'Logout failed' });
}
// ποΈ Clear cookie from client
res.clearCookie('connect.sid');
res.json({ message: 'Logout successful' });
});
});
- bcrypt hashing with salt rounds (10)
- No plain text passwords stored
- Secure comparison using bcrypt.compare()
- HTTP-only cookies - Prevents XSS attacks
- Signed cookies - Prevents tampering
- Secure flag - HTTPS only in production
- Session expiration - Automatic timeout
- Middleware-based authentication
- Early exit for unauthorized requests
- Generic error messages - No information leakage
// Current setup - sessions lost on server restart
const users: User[] = []; // In-memory storage
npm install connect-redis redis
import RedisStore from 'connect-redis';
import { createClient } from 'redis';
const redisClient = createClient();
redisClient.connect();
app.use(session({
store: new RedisStore({ client: redisClient }),
// ... other options
}));
npm install connect-mongo
import MongoStore from 'connect-mongo';
app.use(session({
store: MongoStore.create({
mongoUrl: 'mongodb://localhost:27017/sessions'
}),
// ... other options
}));
npm install connect-pg-simple pg
import pgSession from 'connect-pg-simple';
const PostgreSQLStore = pgSession(session);
app.use(session({
store: new PostgreSQLStore({ /* db config */ }),
// ... other options
}));
npm install session-file-store
import FileStore from 'session-file-store';
const FileStoreSession = FileStore(session);
app.use(session({
store: new FileStoreSession({ path: './sessions' }),
// ... other options
}));
Quick Recommendations:
- Development: Memory Store
- Small Apps: File Store
- Production: Redis Store
- Existing DB: Match your database (MongoDB/PostgreSQL)
1. User Login β 2. Session Created β 3. Cookie Sent
β β β
Credentials Server Storage Client Browser
Validated userId: "123" connect.sid=abc...
username: "john"
4. Future Requests β 5. Session Verified β 6. Access Granted
β β β
Cookie Sent Check Storage Protected Route
connect.sid=abc... Find Session Dashboard/Profile
7. Session Expires β 8. Access Denied β 9. Re-login Required
β β β
TTL Reached 401 Error New Session Cycle
Auto-cleanup Clear Cookie Fresh Start
A simple Node.js + TypeScript demonstration of JWT-based authentication with Express.
# Install dependencies
npm install express jsonwebtoken bcryptjs
npm install -D @types/express @types/jsonwebtoken @types/bcryptjs @types/node typescript ts-node
# Start server
npx ts-node server.ts
Visit http://localhost:3000/demo
for API instructions.
Method | Endpoint | Description | Auth Required |
---|---|---|---|
POST | /register |
Register new user | β |
POST | /login |
Login user | β |
GET | /profile |
Get user profile | β |
GET | /demo |
API documentation | β |
- Register/Login β Receive JWT token
- Include token in
Authorization: Bearer <token>
header - Access protected routes with valid token
curl -X POST http://localhost:3000/register \
-H "Content-Type: application/json" \
-d '{"email":"demo@example.com","password":"password123"}'
curl -X POST http://localhost:3000/login \
-H "Content-Type: application/json" \
-d '{"email":"demo@example.com","password":"password123"}'
curl -X GET http://localhost:3000/profile \
-H "Authorization: Bearer YOUR_JWT_TOKEN"
- Password Hashing: bcrypt with salt rounds
- JWT Tokens: 1-hour expiration
- Protected Routes: Middleware validation
- Error Handling: Proper HTTP status codes
Basic Syntax:
jwt.sign(payload, secret, options)
From Our Example:
const token = jwt.sign({ userId: user.id }, JWT_SECRET, { expiresIn: '1h' });
Detailed Breakdown:
payload
: Data stored in token (user ID, role, permissions)secret
: Secret key for signing (keep secure!)options
: Configuration object for token behavior
String Format (Recommended):
{ expiresIn: '1h' } // 1 hour
{ expiresIn: '30m' } // 30 minutes
{ expiresIn: '7d' } // 7 days
{ expiresIn: '2y' } // 2 years
Numeric Format (Seconds):
{ expiresIn: 3600 } // 3600 seconds = 1 hour
Using 'exp' Claim:
{
userId: 123,
exp: Math.floor(Date.now() / 1000) + (60 * 60) // 1 hour from now
}
Basic Verification (Our Example):
try {
const decoded = jwt.verify(token, JWT_SECRET) as any;
req.userId = decoded.userId;
} catch (error) {
// Handle invalid/expired token
}
With Options:
jwt.verify(token, JWT_SECRET, {
issuer: 'my-app', // Must match token's issuer
audience: 'api-users', // Must match token's audience
algorithms: ['HS256'], // Only allow specific algorithms
clockTolerance: 30, // Allow 30 seconds clock skew
maxAge: '1h' // Additional age limit
});
catch (error) {
if (error instanceof jwt.TokenExpiredError) {
// Token has expired
} else if (error instanceof jwt.JsonWebTokenError) {
// Invalid token format/signature
} else if (error instanceof jwt.NotBeforeError) {
// Token not active yet
}
}
Access Tokens (Short-lived):
- API Access:
15m
-1h
- Sensitive Operations:
5m
Refresh Tokens (Long-lived):
- Mobile Apps:
7d
-30d
- Web Apps:
24h
-7d
Special Purpose:
- Password Reset:
2h
- Email Verification:
24h
Strong Secrets:
// Use environment variables
const JWT_SECRET = process.env.JWT_SECRET;
// Generate secure random secret
const crypto = require('crypto');
const secret = crypto.randomBytes(64).toString('hex');
Minimal Payload:
// Good - minimal data
{ userId: 123, role: 'user' }
// Bad - sensitive data
{ userId: 123, password: 'secret', creditCard: '1234' }
Token Pair Strategy:
// Short access token
const accessToken = jwt.sign(
{ userId: 123, type: 'access' },
JWT_SECRET,
{ expiresIn: '15m' }
);
// Long refresh token
const refreshToken = jwt.sign(
{ userId: 123, type: 'refresh' },
JWT_SECRET,
{ expiresIn: '7d' }
);
const token = jwt.sign(
{
userId: 123,
email: 'user@example.com',
role: 'admin'
},
JWT_SECRET,
{
expiresIn: '1h', // Token expires in 1 hour
issuer: 'my-application', // Who created this token
audience: 'my-app-users', // Who can use this token
subject: 'user-auth', // What this token is for
algorithm: 'HS256', // Signing algorithm
jwtid: 'unique-id-123' // Unique token identifier
}
);
- Keep secrets secure - use environment variables
- Use appropriate expiration times - balance security vs UX
- Handle errors properly - different error types need different responses
- Minimal payloads - don't store sensitive data
- Token refresh strategy - use short access + long refresh tokens
- Validate thoroughly - verify issuer, audience, algorithms