Authentication vs authorization
Authentication establishes identity — you are the holder of this account. Authorization establishes permission — this account may do this thing. Authentication happens once per session; authorization happens on every action, and it is the one people forget.
What does authentication actually produce?
A session: a signed token your app receives after a successful login. The token names the user and has an expiry. Every later request carries it, and the server verifies the signature rather than looking the password up again.
- 1User proves identity: password, magic link, or a provider like Google.
- 2The auth service issues a token with an expiry.
- 3The browser stores it and sends it with each request.
- 4The server verifies the signature and reads the user id from it.
- 5The token expires and is silently refreshed, or the user is signed out.
Where does authorization happen?
Everywhere the answer depends on who is asking: which rows a query returns, whether a delete is allowed, whether an admin page renders. It must be enforced on the server or in the database. Front-end checks are a convenience for the user, not a security control — hiding a button does not protect the action behind it.
| Check | Where it belongs | Why |
|---|---|---|
| Hide the admin link | Front end | Cosmetic only |
| Refuse the admin action | Server or database | Anyone can call the endpoint directly |
| Return only your rows | Database access rules | Cannot be bypassed by editing code |
Where should roles be stored?
In their own table, keyed by user, checked server-side. Never in a field the user can edit, and never in browser storage. A role kept on a profile row that the user is allowed to update is a privilege-escalation bug waiting to be found.
The test: could a determined user change this value themselves? If yes, it is not a permission system.
How do the two fail differently?
| Failure | Type | Signal |
|---|---|---|
| No token sent | Authentication | 401, and the same result signed out |
| Expired token | Authentication | 401 after a period of inactivity |
| Valid user, wrong record | Authorization | 403, or an empty result from access rules |
| Role missing | Authorization | Works for you, fails for a new account |
What do AI-built apps get wrong here?
- Checking permission in the component and nowhere else.
- Storing an `is_admin` flag on an editable profile row.
- Fetching data with admin credentials to make a query work, quietly removing every access rule.
- Leaving a callback URL pointing at localhost, so login works in development and dead-ends in production.
Frequently asked questions
- Is OAuth authentication or authorization?
- Both, historically. OAuth was designed to delegate permission, and sign-in-with-Google layers identity on top of it.
- Why am I logged out after a redirect?
- The session usually has not hydrated yet, or the redirect landed on a different origin than the one holding the session.
- Do I need roles for a small app?
- Not always. Owner-based rules — you may touch your own rows — cover most small apps without any role system at all.
Practice this in MessyDev
Reading it once helps. Doing it once sticks. These are the hands-on parts of MessyDev that cover the same ground.
Keep going
- Row Level Security explainedWhat Row Level Security is, why it is on by default in hosted databases, how a policy is evaluated, and the mistakes that make tables silently empty or dangerously open.
- Why am I getting a 401 error?A 401 means the server could not identify you. The causes, how to tell which one you have, and how to confirm the fix.
- 401 vs 403 vs 404 vs 429 vs 500Five status codes that cover most failures in an AI-built app, what each one is really saying, and who has to fix it.