Skip to main content

What is the difference between authorization and authentication?

Authentication confirms who you are. Authorization decides what you can do after that.

Theory

TL;DR

  • Authentication is showing your ID at the door; authorization is checking if your wristband allows VIP access
  • Authentication always runs first, authorization second. Skip auth, skip authz too
  • Wrong HTTP code is a red flag in interviews: 401 = "prove who you are", 403 = "I know who you are, but no"
  • JWT carries both: identity (auth) and roles/scopes (authz) in one token

Quick example

javascript
const express = require('express'); const app = express(); // Authentication: who are you? app.post('/login', (req, res) => { if (req.body.password === 'secret') { res.json({ token: 'user-jwt-token' }); // identity confirmed } else { res.status(401).send('Invalid credentials'); // 401 = auth failed } }); // Authorization: what can you do? app.get('/admin', (req, res) => { const token = req.headers.authorization; if (token !== 'Bearer admin-token') { return res.status(403).send('Access denied'); // 403 = authz failed } res.send('Admin data'); });

401 when login fails. 403 when the user is logged in but lacks the role. Two different problems, two different codes.

Key difference

Authentication answers "Are you who you claim to be?" It checks credentials: a password, a JWT, a fingerprint. Authorization answers "Can you do this?" It checks permissions: roles, scopes, ACL entries. One confirms identity, the other enforces access rules. No authentication means no authorization, because you have no identity to check permissions against.

When to use

  • Public page (landing, blog): skip both
  • User profile page: authenticate the user, then authorize access to their own data only
  • Admin dashboard: authenticate + check admin role
  • API with rate limits: authenticate to identify the caller, authorize based on their plan tier

Comparison table

AspectAuthenticationAuthorization
QuestionWho are you?What can you do?
TimingFirst (login)Second (resource access)
MechanismsPasswords, JWT, OAuth, biometricsRBAC, ACL, scopes in JWT
Failure code401 Unauthorized403 Forbidden
LibrariesPassport.js, Auth0, NextAuth.jsCasbin, CASL, node-acl
When to useEntry gate (prove identity)Room keys (grant specific access)

How the mechanism works

In Express with Passport.js, the auth middleware reads credentials from the request body or OAuth callback, verifies them against a database or provider, and generates a JWT with the user's role embedded. The authz middleware (CASL or Casbin) then decodes that token, pulls the role, and checks it against the policy for the requested resource. If the role does not match, it returns 403 and stops there.

Common mistakes

Using 401 for authorization failures.

javascript
// Wrong if (!hasPermission) res.status(401).send('No access'); // Correct if (!hasPermission) res.status(403).send('Forbidden');

401 tells the client to re-authenticate (retry with valid credentials). 403 tells them they are authenticated but blocked. The wrong code triggers re-login flows the client does not need.

Storing roles in JWT without a short expiry.

javascript
// Wrong: token lives forever, role changes are ignored jwt.sign({ role: 'admin' }, secret); // Better: short-lived token + refresh with DB check jwt.sign({ role: 'admin' }, secret, { expiresIn: '15m' });

If a user's role gets downgraded in the database, a long-lived token still grants admin access. I've seen this hit teams after an employee was removed from a project but their token stayed valid for days.

Running authorization before authentication.

javascript
// Wrong order app.get('/admin', checkRole, verifyToken); // Correct order app.get('/admin', verifyToken, checkRole);

Check the role before verifying the token, and a crafted request with a fake role: admin header slips right through.

Treating an active session as proof of permissions.

Sessions confirm the user logged in. They do not say what that user can do right now. If a role changes after login, the session still reflects the old state unless you re-check the database on each request.

Real-world usage

  • Express.js: Passport.js for auth, express-jwt + CASL for authz
  • React/Next.js: NextAuth.js for auth, useAbility from CASL for component-level authz
  • AWS Lambda: Cognito for auth, IAM policies + API Gateway for authz
  • Spring Boot: Spring Security handles both; @PreAuthorize enforces method-level authz
  • Microservices: stateless JWT across services, short-lived tokens + Redis token blacklist for revocation

Follow-up questions

Q: What HTTP status do you return for each failure?
A: 401 for authentication failures (the client needs to log in or send valid credentials). 403 for authorization failures (identity confirmed, access blocked).

Q: How does JWT fit into both processes?
A: JWT is the carrier. Authentication generates it after verifying credentials. Authorization reads it to extract roles and scopes. One token, two jobs.

Q: How does OAuth 2.0 relate?
A: OAuth handles authentication via a provider (Google, GitHub). The returned access token carries scopes that drive authorization on your API.

Q: What is the difference between RBAC and ABAC?
A: RBAC grants permissions based on a role (admin can delete). ABAC adds attributes like time, location, or resource owner to create more granular rules.

Q: A user's admin role was revoked in the database, but their JWT still has role: admin. What do you do?
A: Use short-lived access tokens (15 minutes) plus a refresh token flow. On each refresh, re-check the database for the current role. Or maintain a token blacklist in Redis and validate against it on every request.

Examples

Basic: login + admin guard

javascript
const jwt = require('jsonwebtoken'); const secret = 'mysecret'; // Authentication: verify credentials, issue token app.post('/login', (req, res) => { if (req.body.user === 'admin' && req.body.pass === 'pass') { const token = jwt.sign({ role: 'admin' }, secret, { expiresIn: '15m' }); res.json({ token }); } else { res.status(401).json({ error: 'Invalid credentials' }); } }); // Authorization: check role from token app.get('/users', authenticateToken, (req, res) => { if (req.user.role !== 'admin') { return res.status(403).json({ error: 'Admin access required' }); } res.json({ users: ['alice', 'bob'] }); }); function authenticateToken(req, res, next) { const token = req.headers.authorization?.split(' ')[1]; if (!token) return res.status(401).json({ error: 'Token required' }); jwt.verify(token, secret, (err, user) => { if (err) return res.status(401).json({ error: 'Invalid token' }); req.user = user; next(); }); }

authenticateToken handles authentication: it verifies the token and attaches the decoded user to the request. The role check inside /users is authorization. Same user, different question.

Advanced: stale role in JWT after DB change

javascript
// Token issued with admin role const token = jwt.sign( { userId: 1, role: 'admin' }, secret, { expiresIn: '1h' } // too long ); // 30 seconds later: role changed to 'user' in DB // The token still says 'admin' app.get('/secure', (req, res) => { jwt.verify(token, secret, (err, decoded) => { if (decoded.role === 'admin') { res.send('Admin access granted'); // Wrong: trusts stale token } }); }); // Fix: short-lived tokens + DB check on refresh app.post('/refresh', async (req, res) => { const user = await db.users.findById(decoded.userId); // fresh role from DB const newToken = jwt.sign( { userId: user.id, role: user.role }, secret, { expiresIn: '15m' } ); res.json({ token: newToken }); });

Long-lived tokens are the most common source of stale authorization data in distributed systems. Short expiry forces a DB sync on refresh. More refresh requests, but correct permissions on every call.

Short Answer

Interview ready
Premium

A concise answer to help you respond confidently on this topic during an interview.

Finished reading?