A startup shipped a project management tool where every logged-in user could access every project. The login system worked perfectly, passwords were hashed, sessions were managed, tokens expired on schedule. But the team never built a permission layer. AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. was solid; authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages. did not exist. Any user could view, edit, or delete any other user's projects just by changing the ID in the URL. The team had confused "logged in" with "allowed."
This confusion is one of the most common security mistakes in web applications, and AI makes it worse. When you prompt an AI to "protect this route," it generates authentication middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it., a tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use. check, and stops there. It answers "who are you?" but never asks "what can you do?"
AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token.: proving identity
Authentication is the first gate. The user provides credentials (username and password, OAuthWhat is oauth?An authorization protocol that lets users grant a third-party app limited access to their account on another service without sharing their password. tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use., APIWhat is api?A set of rules that lets one program talk to another, usually over the internet, by sending requests and getting responses. key), and the server verifies them. If verification succeeds, the server issues a sessionWhat is session?A server-side record that tracks a logged-in user. The browser holds only a session ID in a cookie, and the server looks up the full data on each request. token or JWTWhat is jwt?JSON Web Token - a self-contained, signed token that carries user data (like user ID and role). The server can verify it without a database lookup. that the client attaches to every subsequent request.
Here is the typical request lifecycle with authentication:
// 1. User sends credentials
POST /auth/login { email: "[email protected]", password: "..." }
// 2. Server verifies and returns a token
Response: { token: "eyJhbGciOiJIUzI1NiIs..." }
// 3. Client attaches token to every request
GET /api/projects
Authorization: Bearer eyJhbGciOiJIUzI1NiIs...
// 4. Server verifies the token before processing the requestThe authentication middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it. extracts the token, verifies its signature and expiration, and attaches the user identity to the request object:
function authenticate(req, res, next) {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Not authenticated' });
try {
const decoded = jwt.verify(token, process.env.JWT_SECRET);
req.user = decoded; // { id: 'user_123', email: '[email protected]' }
next();
} catch (err) {
return res.status(401).json({ error: 'Invalid token' });
}
}After this middleware runs, you know who is making the request. You do not yet know whether they are allowed to do what they are asking.
AuthorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages.: enforcing permissions
Authorization is the second gate. It takes the identity established by authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. and decides whether that specific user can perform the requested action on the requested resource.
| Concern | Question | HTTP status on failure | Example |
|---|---|---|---|
| Authentication | Who are you? | 401 Unauthorized | "You need to log in" |
| Authorization | What can you do? | 403 Forbidden | "You don't have permission to delete this project" |
A 401 means "I don't know who you are." A 403 means "I know who you are, and you are not allowed." AI-generated code frequently returns 401 for both cases, which makes debugging harder and leaks information about your auth flow.
The request lifecycle: both gates in order
A well-structured request passes through distinct stages. Each stage has a single responsibility:
// Stage 1: Authentication - who is this?
app.use(authenticate);
// Stage 2: Authorization - can they do this?
app.delete('/api/projects/:id', authorize('project', 'delete'), async (req, res) => {
// Stage 3: Business logic - only runs if both gates pass
await db.projects.delete(req.params.id);
res.json({ success: true });
});When you review AI-generated code, check whether these stages are separate. If authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. and authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages. logic are tangled in the same function, the code is harder to audit, harder to test, and more likely to have gaps.
Where AI conflates the two
When you prompt "add authenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. to this route," AI generates a tokenWhat is token?The smallest unit of text an LLM processes - roughly three-quarters of a word. API pricing is based on how many tokens you use. check. When you prompt "add authorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages.," AI often generates the same token check again, or adds a superficial role check inside the route handlerWhat is route handler?A Next.js file named route.js inside the app/ directory that handles HTTP requests directly - the App Router equivalent of API routes. instead of as separate middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it..
Here is a pattern AI produces frequently:
// AI-generated: authentication and authorization tangled together
app.delete('/api/projects/:id', async (req, res) => {
const token = req.headers.authorization?.split(' ')[1];
if (!token) return res.status(401).json({ error: 'Unauthorized' });
const user = jwt.verify(token, secret);
if (user.role !== 'admin') {
return res.status(401).json({ error: 'Unauthorized' }); // Wrong status code
}
await db.projects.delete(req.params.id);
res.json({ success: true });
});Three problems here. First, authentication and authorization are in the same function, so you cannot reuse either independently. Second, the role check returns 401 instead of 403, conflating "not logged in" with "not permitted." Third, there is no ownership check, even a non-admin who owns the project cannot delete it, while any admin can delete any project.
Separate concerns, separate middlewareWhat is middleware?A function that runs between receiving a request and sending a response. It can check authentication, log data, or modify the request before your main code sees it.
The clean approach uses composable middleware. Each piece does one thing:
// Authentication middleware: verifies identity
function authenticate(req, res, next) {
// ... token verification ...
req.user = decoded;
next();
}
// Authorization middleware: checks permission
function authorize(resource, action) {
return (req, res, next) => {
if (!hasPermission(req.user, resource, action)) {
return res.status(403).json({ error: 'Forbidden' });
}
next();
};
}
// Route uses both, in order
app.delete('/api/projects/:id',
authenticate,
authorize('project', 'delete'),
deleteProjectHandler
);This separation makes it obvious when a route is missing a gate. If you see a route with authenticate but no authorize, you know to ask "does every authenticated user really have permission to do this?"
The mental model
Think of it like a building. AuthenticationWhat is authentication?Verifying who a user is, typically through credentials like a password or token. is the front door, your badge gets you in. AuthorizationWhat is authorization?Checking what an authenticated user is allowed to do, like whether they can delete records or access admin pages. is the access control on each floor, your badge only opens the floors you are allowed on. A building with only a front door and no floor restrictions lets every employee into every room, including the server room, the executive suite, and the janitor's closet. That is what your application looks like when you have authentication but no authorization.