Software Engineering·July 21, 2026·4 min read

Authentication and Authorization

Authentication verifies who you are. Authorization decides what you're allowed to do. This guide explains both, how sessions and tokens work, and why you should use an auth provider instead of building your own.

authauthenticationauthorizationsecurityengineering

Authentication answers "who are you?" — verifying identity at login. Authorization answers "what are you allowed to do?" — deciding whether an already-identified user can perform a specific action.

They're the gatekeepers of every app with user accounts. If Cartara surfaced this in your diff, you likely added login logic, protected a route, or implemented role-based access.


Authentication vs. Authorization

These two words are often used interchangeably, but they're different things:

  • Authentication (AuthN) — verifying identity. Happens at login via passwords, passkeys, or "Sign in with Google."
  • Authorization (AuthZ) — checking permissions. Happens on every sensitive action after login. Can this user delete that post? Access this admin page?

A common security mistake is authenticating users correctly but then forgetting to authorize — letting any logged-in user access any other user's data just by changing an ID in the URL.


How Authentication Works

Passwords

The user proves identity with something they know. If you store passwords, they must be hashed with a slow algorithm (bcrypt, Argon2) — never stored in plain text. In practice, you rarely need to handle passwords directly anymore (see "Use an auth provider" below).

Passkeys

Passkeys replace passwords with a cryptographic key pair tied to the user's device, unlocked by biometrics or a PIN. The private key never leaves the device, making passkeys phishing-resistant — there's no shared secret to steal.

Passkeys are now mainstream: supported by Apple, Google, and Microsoft, and a solid default choice for new apps.

Social Login (OAuth)

"Sign in with Google/Apple/GitHub" lets a trusted provider vouch for the user's identity, so you never handle their password. Convenient for users and offloads security to the provider. Built on OAuth 2.0 and OpenID Connect.

Multi-Factor Authentication (MFA)

Requiring a second factor on top of the first — an authenticator app, hardware key, or biometric. Prefer app-based (TOTP) or hardware/passkey factors over SMS, which is vulnerable to SIM-swapping. Enable MFA for any app handling sensitive data.


Sessions vs. Tokens

After login, the app needs to remember the user across requests. Two main approaches:

Session cookies (stateful) — the server creates a session, stores it in a database or Redis, and gives the browser a cookie with the session ID. Each request sends the cookie; the server looks it up. Easy to revoke but requires server-side storage.

JWTs / tokens (stateless) — the server issues a signed token containing the user's identity. The client sends it with each request and the server verifies the signature — no lookup needed. Scales well, but harder to revoke: a valid token works until it expires, so you can't easily force a logout. The usual fix is short-lived access tokens paired with longer-lived refresh tokens.

Rule of thumb: session cookies are simpler and safer for web apps. Tokens are better for APIs, mobile clients, and service-to-service calls.


How Authorization Works

Once you know who the user is, you decide what they can do:

Role-Based Access Control (RBAC) — users are assigned roles (admin, editor, viewer), and permissions are granted to roles. Covers most apps. "Admins can delete; viewers can only read."

Ownership checks — the most common per-request check: "does this record belong to this user?" Easy to forget, and forgetting it is a top cause of data leaks.

Row Level Security (RLS) — a database-level safety net available in Postgres (and Supabase) that enforces "users can only see their own rows" at the database itself — so even a bug in your app code can't accidentally expose another user's data.


What You'll See in Your Code

Auth-related code commonly appears in diffs as:

Middleware that checks authentication:

// Protect a route — reject requests with no valid session
app.use('/api/protected', requireAuth);

function requireAuth(req, res, next) {
  if (!req.user) return res.status(401).json({ error: 'Unauthorised' });
  next();
}

Ownership checks before returning data:

const order = await db.orders.findById(req.params.id);
if (order.userId !== req.user.id) {
  return res.status(403).json({ error: 'Forbidden' });
}

Role checks:

if (req.user.role !== 'admin') {
  return res.status(403).json({ error: 'Admin access required' });
}

Use an Auth Provider — Don't Build It Yourself

The most important advice: use a battle-tested auth provider rather than building authentication yourself. Auth is deceptively hard — password reset flows, email verification, session management, MFA, OAuth, secure token handling. Getting any of it subtly wrong can expose every user.

ProviderNotes
ClerkPolished drop-in UI, passkeys, MFA, org support. Fast to integrate
Supabase AuthBuilt into Supabase; pairs naturally with its Postgres + RLS
Auth0 / OktaMature, enterprise-grade, very flexible
Firebase AuthGood social login and mobile support
WorkOSAimed at B2B/enterprise SSO (SAML, SCIM)

These handle the hard parts correctly and stay current with evolving standards so you don't have to.


Common Pitfalls

Authorizing on the client only. Hiding an admin button in the UI is not security — every check must be enforced on the server. The client can always be bypassed.

Insecure direct object references (IDOR). Changing /orders/123 to /orders/124 to see someone else's order. Always verify ownership server-side.

Storing tokens in localStorage. This exposes them to cross-site scripting attacks. Use secure, HttpOnly cookies instead.

Long-lived, overly broad tokens. Scope tokens narrowly and keep access tokens short-lived.


Related concepts

Web Application Security
The most critical web application attack classes — SQL injection, XSS, broken access control, CSRF, and more — explained plainly with concrete defenses for each.
Rate Limiting and Throttling
Rate limiting caps how many requests a client can make in a given window. It protects against abuse, runaway costs from paid APIs, and accidental self-inflicted overload.
Secrets and Config Management
Every app has settings that change between environments and sensitive values that must never leak. This guide explains the difference between config and secrets, where each should live, and how to manage rotation.

Turn shipping into understanding

Cartara measures what your team actually learns from every AI coding session.

Join the waitlist