glimfly
security updated

What's the difference between authentication and authorization?

In one sentence:Authentication verifies who a user is, usually at login, while authorization checks what that verified user is allowed to do or see, and building only the first one is why any logged-in user can end up reading data that isn't theirs.

What it actually is

Authentication answers one question: who are you? It’s the part where you type a password, click “Sign in with Google,” or send a token, and the app confirms you’re a real, specific user. Authorization answers a different question: now that the app knows who you are, what are you allowed to do or see? These are two separate systems, and a working login screen only builds the first one.

Think of checking into a hotel. Showing your ID at the front desk proves you’re the person on the reservation. That’s authentication. The key card you get afterward only opens your room and the gym, not every door in the building, and a housekeeper’s key card opens different doors than a guest’s. That’s authorization: your verified identity comes with a specific, limited set of doors, not the whole hotel.

The two get confused because they often happen in the same flow, sometimes the same few lines of code. An app can nail authentication, so only real, logged-in users get past the door, and still skip authorization entirely, so once inside, every guest’s key card opens every room.

Why your AI just did this

When you ask your agent to “add auth,” most agents reach straight for authentication: working with Supabase Auth, NextAuth/Auth.js, Clerk, or Auth0, they build a signup form, a login form, a session cookie or a token, maybe a Google login if you asked for one. That part is usually solid out of the box, because those providers specialize in verifying identity.

Authorization is a layer your agent has to build for your specific app, and it’s easy to skip because nothing throws an error while it’s missing. A route can check “does this request carry a valid session?” and stop there, never asking “does this session’s user actually own the row being requested?” In Supabase, the same gap shows up as a table where login works fine but Row Level Security is left off or set to allow every row, so any signed-in user’s token reads every user’s data through the same API call.

When you’ll run into it

The clearest signal is which HTTP status code comes back. A 401 Unauthorized means the server doesn’t recognize you yet, no valid session or token arrived with the request, and logging in fixes it. A 403 Forbidden means the server knows exactly who you are and blocks the action anyway, because your account isn’t allowed to do this specific thing. Re-authenticating changes nothing. See HTTP status codes for the rest of that family.

The gap that catches vibe coders most often produces no error at all. You’re logged in as one user, you change an ID in the URL or an API call from your own record to someone else’s, and the response hands back their order, profile, or message anyway. That’s the authenticated-but-never-authorized hole: the server confirmed you’re a real user, then returned data regardless of whose it was, because nothing checked ownership.

What to check

  • When your agent says “auth is broken,” open the Network tab and read the actual status code. A 401 means fix login or the token; a 403, or a silent data leak with no error, means fix permissions
  • Log in as two test accounts and try to fetch or edit account A’s data while signed in as account B, by changing an ID in a URL or request body. If it works, you have an authorization hole
  • For Supabase tables, confirm Row Level Security is on and every policy scopes rows to the requesting user’s own ID, not just that the table requires some login
  • For custom API routes, confirm each one checks the requester’s identity against the specific resource requested, not only whether a session exists
  • Apply least privilege to roles: a new signup shouldn’t inherit admin actions by default, only the access that role actually needs
  • If ownership checks are scattered across routes, ask your agent to centralize them in shared middleware instead of repeating, and occasionally forgetting, the same check per endpoint

Say it like a dev

Instead of: “auth is broken, users can see stuff they shouldn’t” Say: “Users are authenticating fine, but there’s no authorization check: user A can read user B’s data by changing an ID. Add an ownership check on this route.”

Instead of: “it says I’m not allowed in” Say: “I’m getting a 403, not a 401, so I’m logged in but blocked from this specific action. Check the role or permission logic on this route.”

Instead of: “lock the app down so only logged-in users can use it” Say: “Add authentication so only logged-in users get in, then add authorization so each user can only read and write their own rows.”

People actually ask

“My agent says 'auth is broken', but which auth, exactly?”

Ask it whether it means authentication (login isn't working, which usually shows up as a 401 in the Network tab) or authorization (login works fine, but access to a specific action or piece of data is blocked, a 403, or worse, no error at all if the wrong person gets through). Authentication bugs live in your login and session code; authorization bugs live in a missing per-user check on your data or routes, so the fix is different for each.

“Why can any logged-in user in my app see other people's data?”

That's the classic gap: your app authenticates people correctly (it knows who's logged in) but never checks authorization (it never asks whether that specific user owns that specific row). It usually means an API route or database table has no ownership check, like a table with Row Level Security left off, or an endpoint that trusts any valid session instead of matching the user's ID to the record's owner. Add a check that compares the logged-in user's ID against the record before returning it.

“I added 'Sign in with Google', does that cover authorization too?”

No. OAuth, and OpenID Connect riding on top of it, handles authentication: confirming which real person is logging in, through Google's own servers. What that person is allowed to do or see inside your app, like which rows or which admin actions, is authorization, and it's logic you still write yourself, whether that's role checks in your code or Row Level Security policies in your database.

Related terms

Go deeper

Checked against

free tool · no signup

Just met this in a real session? Glim reads it in plain words.

Paste what your agent just did, a git diff, your terminal, or its summary, and Glim tells you what changed and what to check before you ship. Nothing stored.

Explain my session →