Skip to content

Authentication for Web Apps: Sessions vs JWT, Refresh Tokens and Passkeys

Security7 min readBy the Nexzem team

How to choose between server sessions and JWTs, use refresh tokens safely, apply OAuth and OIDC correctly and add passkeys to a web application.

In this article
  1. 01Authentication decisions that are hard to undo
  2. 02Server-side sessions
  3. 03JSON Web Tokens
  4. 04Verifying JWTs correctly
  5. 05Refresh tokens and rotation
  6. 06Where to store tokens in the browser
  7. 07OAuth 2.0 and OpenID Connect
  8. 08Passkeys: phishing-resistant sign-in
  9. 09Adding passkeys step by step
  10. 10Multi-factor authentication and recovery
  11. 11A sensible default

Authentication decisions that are hard to undo

Authentication touches every request, every client and every security review, so early choices tend to stick for years. The main questions are how a signed-in user is remembered between requests, how long that lasts, how you revoke it and how people prove who they are in the first place. This guide walks through each, from classic sessions to passkeys.

A useful principle throughout: use well-tested libraries and identity providers for the cryptography and protocol details, and spend your own effort on the decisions that depend on your product.

Server-side sessions

With sessions, the server creates a random session ID after login, stores session data in a database or a cache such as Redis and sends the ID to the browser in a cookie. On each request, the server looks up the ID. Revoking access is immediate: delete the session. Sessions are simple, battle-tested and a great default for traditional web apps and for single-page apps served from the same site as their API.

A common worry is that sessions do not scale. In practice, a lookup in a shared store such as Redis is fast enough for nearly every application, session stores replicate easily, and the ability to revoke a session instantly is worth far more than the lookup it costs. Choose JWTs because you need stateless verification across services or clients, not because sessions seem old-fashioned.

  • Set the cookie with HttpOnly so JavaScript cannot read it
  • Set Secure so it is only sent over HTTPS
  • Use SameSite=Lax or Strict to limit cross-site requests, and add CSRF tokens for state-changing requests where needed
  • Issue a new session ID at login to prevent session fixation
  • Expire idle sessions and offer a list of active sessions users can revoke

JSON Web Tokens

A JWT is a signed token containing claims such as the user ID, expiry, issuer and audience. Any service with the verification key can check it without a database lookup, which suits APIs consumed by mobile apps, third parties and multiple backend services. Our JWT decoder shows what a token contains, which is a reminder that the payload is only encoded, not encrypted.

The trade-off is revocation. A JWT stays valid until it expires, so a stolen token works until then unless you add a denylist, which brings back the database lookup JWTs were meant to avoid. The practical answer is short-lived access tokens, measured in minutes, combined with refresh tokens.

Verifying JWTs correctly

Most JWT vulnerabilities come from incomplete verification rather than broken cryptography. Use a maintained library and configure it strictly:

  • Accept only the algorithm you expect, never whatever the token header claims
  • Verify the signature with the right key, fetching public keys from the issuer's JWKS endpoint when using an identity provider
  • Check expiry, issuer and audience on every request
  • Keep tokens small, with no sensitive personal data in the payload

Refresh tokens and rotation

A refresh token is a long-lived credential used only to obtain new access tokens. Because it is powerful, protect it carefully. Rotate it on every use: each refresh returns a new refresh token and invalidates the old one. If an old refresh token is ever presented again, treat it as theft and revoke the whole token family, forcing a new login.

Store refresh tokens server-side as hashed values with their family, device and expiry, so they can be listed and revoked. Set an absolute lifetime as well as an idle timeout, so a stolen token cannot be refreshed forever.

Where to store tokens in the browser

Tokens in localStorage or sessionStorage can be read by any script running on the page, so a single cross-site scripting bug exposes them. For browser apps, a safer pattern is the backend-for-frontend: the browser holds only an HttpOnly session cookie, and a small server-side component holds the access and refresh tokens and calls APIs on the user's behalf. On mobile, store tokens in the iOS Keychain or Android Keystore-backed storage, never in plain preferences files.

OAuth 2.0 and OpenID Connect

When users sign in with Google, Microsoft or a corporate identity provider, or when you use a hosted identity service, you are using OAuth 2.0 and OpenID Connect. OAuth handles delegated authorization; OpenID Connect adds an ID token that tells your app who the user is. Use the authorization code flow with PKCE for web, single-page and mobile apps. The older implicit flow is no longer recommended by the OAuth 2.0 Security Best Current Practice (RFC 9700) and is dropped from the OAuth 2.1 draft.

Validate the state parameter to prevent cross-site request forgery in the login flow, register exact redirect URIs, and request only the scopes you need. For business customers, supporting single sign-on through SAML or OpenID Connect is often a sales requirement.

Passkeys: phishing-resistant sign-in

Passkeys replace passwords with public key cryptography under the WebAuthn and FIDO2 standards. During registration, the user's device creates a key pair bound to your domain, keeps the private key and sends you the public key. At sign-in, your server sends a random challenge, the device signs it after the user confirms with a fingerprint, face or PIN, and the server verifies the signature. Nothing reusable crosses the network, and a fake site on another domain cannot use the passkey, which makes passkeys resistant to phishing.

Platforms sync passkeys through services such as iCloud Keychain and Google Password Manager, so users keep them when they change phones.

Adding passkeys step by step

Libraries such as SimpleWebAuthn for Node.js and py_webauthn for Python handle the protocol details. A typical rollout looks like this:

  • Offer passkey creation after a normal sign-in, from account settings or a prompt
  • On the server, generate registration options with your relying party ID (your domain) and a fresh challenge, then verify the response and store the credential ID and public key
  • At sign-in, generate authentication options, verify the signed response and check the signature counter where the authenticator provides one
  • Use conditional mediation so passkeys appear in the browser's autofill suggestions
  • Allow several passkeys per account and keep a secure recovery path, such as email verification plus another factor

Multi-factor authentication and recovery

Until most users have passkeys, password accounts need multi-factor authentication. Authenticator apps using TOTP are stronger than SMS codes, which can be intercepted through SIM swapping. Require MFA for administrators and for sensitive actions such as changing payout details.

Account recovery is where many systems are weakest. A strong login with a weak password reset flow is still weak. Rate limit recovery attempts, notify users by email of security changes and avoid revealing whether an email address has an account.

A sensible default

For most new web apps: use a mature identity provider or framework library, server-side sessions or a backend-for-frontend for browsers, short-lived access tokens with rotating refresh tokens for mobile and API clients, MFA for staff, and passkeys offered to everyone. Nexzem's application security reviews check authentication flows against exactly these practices.

Planning something similar?

Get a straight answer on scope, cost and timeline.

Talk to the team

Tell us what you're building.

A solutions consultant replies within one business day with next steps, a rough estimate and a suggested team.