DEV Community

Alan West
Alan West

Posted on

5 Authentication Patterns Every Web Developer Should Know in 2026

Web authentication has evolved dramatically. What used to be "just hash the password and store a session" now spans half a dozen distinct patterns, each with real trade-offs. After building auth systems across multiple projects, here's my breakdown of the five patterns that matter most in 2026.

1. Session-Based Authentication

The OG. Server creates a session, stores it (usually in Redis or a database), and hands the client a cookie.

// Express.js session setup
import session from 'express-session';
import RedisStore from 'connect-redis';
import { createClient } from 'redis';

const redisClient = createClient({ url: 'redis://localhost:6379' });
await redisClient.connect();

app.use(session({
  store: new RedisStore({ client: redisClient }),
  secret: process.env.SESSION_SECRET,
  resave: false,
  saveUninitialized: false,
  cookie: {
    secure: true,
    httpOnly: true,
    sameSite: 'lax',
    maxAge: 24 * 60 * 60 * 1000 // 24 hours
  }
}));
Enter fullscreen mode Exit fullscreen mode

Pros: Simple mental model, easy revocation (just delete the session), built-in CSRF protection with SameSite cookies.

Cons: Requires server-side storage, harder to scale across multiple servers without shared session store, doesn't work well for mobile apps.

When to use: Traditional server-rendered apps, internal tools, admin dashboards where you control the entire stack.

2. JWT (JSON Web Tokens)

Stateless tokens that carry claims. The server signs them, and any service with the public key can verify them.

import jwt from 'jsonwebtoken';

// Signing
const token = jwt.sign(
  { userId: user.id, role: user.role },
  process.env.JWT_SECRET,
  { expiresIn: '15m', algorithm: 'RS256' }
);

// Verification middleware
function authenticate(req, res, next) {
  const token = req.headers.authorization?.split(' ')[1];
  if (!token) return res.status(401).json({ error: 'No token provided' });

  try {
    req.user = jwt.verify(token, process.env.JWT_PUBLIC_KEY);
    next();
  } catch (err) {
    return res.status(401).json({ error: 'Invalid token' });
  }
}
Enter fullscreen mode Exit fullscreen mode

Pros: Stateless (no server-side storage), works across microservices, great for APIs.

Cons: Can't be revoked without a blocklist (defeating the "stateless" benefit), token size can get large, XSS risk if stored in localStorage.

When to use: APIs consumed by SPAs or mobile apps, microservice architectures where services need to verify identity without calling an auth service.

3. OAuth 2.0 / OpenID Connect

Delegated authorization. Let Google, GitHub, or your own identity provider handle the heavy lifting.

// Using openid-client library
import { Issuer } from 'openid-client';

const googleIssuer = await Issuer.discover('https://accounts.google.com');
const client = new googleIssuer.Client({
  client_id: process.env.GOOGLE_CLIENT_ID,
  client_secret: process.env.GOOGLE_CLIENT_SECRET,
  redirect_uris: ['https://myapp.com/callback'],
  response_types: ['code'],
});

// Generate auth URL
const authUrl = client.authorizationUrl({
  scope: 'openid email profile',
  state: crypto.randomUUID(),
  nonce: crypto.randomUUID(),
});

// Handle callback
app.get('/callback', async (req, res) => {
  const params = client.callbackParams(req);
  const tokenSet = await client.callback(
    'https://myapp.com/callback',
    params,
    { state: req.session.state, nonce: req.session.nonce }
  );
  const userInfo = await client.userinfo(tokenSet.access_token);
  // Create or update user...
});
Enter fullscreen mode Exit fullscreen mode

Pros: Offload credential management, users don't need another password, get verified email addresses.

Cons: Complex flows, redirect-based (can be frustrating UX), dependency on third-party availability, lots of things to get wrong.

When to use: Consumer-facing apps (social login), B2B apps (SSO with company identity providers), anywhere you want to avoid storing passwords.

4. Passkeys / WebAuthn

The future that's finally here. Cryptographic key pairs stored on the user's device. No passwords, no phishing.

// Registration (simplified)
const options = await generateRegistrationOptions({
  rpName: 'My App',
  rpID: 'myapp.com',
  userID: user.id,
  userName: user.email,
  authenticatorSelection: {
    residentKey: 'preferred',
    userVerification: 'preferred',
  },
});

// Client-side
const credential = await navigator.credentials.create({
  publicKey: options,
});

// Server verification
const verification = await verifyRegistrationResponse({
  response: credential,
  expectedChallenge: options.challenge,
  expectedOrigin: 'https://myapp.com',
  expectedRPID: 'myapp.com',
});
Enter fullscreen mode Exit fullscreen mode

Pros: Phishing-resistant, no passwords to leak, great UX (biometrics or device PIN), cross-device support via hybrid transport.

Cons: Browser support gaps (shrinking fast), need fallback auth method, recovery flow is harder to design.

When to use: Any app that wants the best security UX trade-off. Seriously, start adding passkey support now.

5. Web3 Wallet Authentication

Sign a message with your crypto wallet to prove identity. No central authority involved.

import { verifyMessage } from 'ethers';

// Server generates a nonce
const nonce = `Sign this message to log in to MyApp.\nNonce: ${crypto.randomUUID()}`;

// Client signs with wallet
const signature = await signer.signMessage(nonce);

// Server verifies
app.post('/auth/web3', (req, res) => {
  const { address, signature, message } = req.body;
  const recovered = verifyMessage(message, signature);

  if (recovered.toLowerCase() !== address.toLowerCase()) {
    return res.status(401).json({ error: 'Signature verification failed' });
  }
  // Create session for this address...
});
Enter fullscreen mode Exit fullscreen mode

Pros: Decentralized, user controls their identity, no email/password needed.

Cons: Wallet UX is still rough for non-crypto users, key management falls entirely on the user, limited to Web3-oriented audiences.

When to use: DApps, blockchain-related platforms, communities where users already have wallets.

Choosing the Right Pattern

There's no universal best choice. Most production apps combine multiple patterns:

App Type Primary Secondary
SaaS B2B OAuth/OIDC (SSO) Passkeys + email/password
Consumer mobile Passkeys OAuth (social login)
Internal tool Session-based OAuth (company SSO)
DApp Wallet auth Email link as fallback
API platform JWT API keys

Tools That Support Multiple Patterns

If you're building auth from scratch, you'll want a framework or service that handles multiple patterns out of the box. The good options in 2026 include Auth.js (open-source, great for Next.js), Clerk (managed, excellent DX), Auth0 (enterprise-grade), Authon (self-hostable with multi-pattern support), and Keycloak (battle-tested open-source). Pick based on your hosting preference, budget, and how much control you need.

Final Thoughts

The trend is clear: passwords are dying, passkeys are rising, and most apps need to support at least two auth patterns. Whatever you build, make sure you're not rolling your own crypto, you're using httpOnly cookies or secure token storage, and you're thinking about the recovery flow from day one.

Authentication is one of those things that seems simple until it isn't. Take the time to understand these patterns deeply — your users' security depends on it.

Top comments (0)