Authentication answers who the user is. Authorization answers what that user is allowed to do. Mixing the two leads to serious bugs, like showing the right account but allowing access to the wrong resource.
Sessions and JWTs
Cookie sessions keep the session record on the server and send the browser an opaque session id.
type Session = { id: string; userId: string; expiresAt: Date;};
Sessions are easier to revoke. JWTs can be useful for distributed systems, but revocation, rotation, and claim freshness require careful design.
TIP
For most web apps, start with secure, HTTP-only, same-site cookies backed by server-side sessions. It is simple, revocable, and fits browser security well.
Authorization Checks
Check permissions close to the operation, not only in the UI.
function canEditProject(user: { id: string; role: string }, project: { ownerId: string }) { return user.role === "admin" || project.ownerId === user.id;}async function updateProject(userId: string, projectId: string, name: string) { const user = await users.findById(userId); const project = await projects.findById(projectId); if (!project || !user || !canEditProject(user, project)) { throw new Error("Forbidden"); } return projects.update(projectId, { name });}
WARNING
Hiding a button in the frontend is not authorization. Attackers can still call the API directly.
Further Learning
Search these terms to go deeper:
“session authentication vs JWT” — trade-offs for browser apps
“role based access control vs attribute based access control” — permission modeling options
“HTTP only secure same site cookies” — browser session cookie protections
Authentication and Authorization
Two words that sound similar but mean different things:
Authentication — who are you? (logging in)
Authorization — what are you allowed to do? (permissions)
Mixing these up causes real bugs — like correctly recognizing who someone is, but letting them touch data that isn’t theirs.
How the server remembers you’re logged in
The simplest, most common approach: after login, the server gives your browser a session cookie — a little id that says “you’re user #42.” The server keeps the actual session info on its side:
type Session = { id: string, userId: string, expiresAt: Date }
TIP
For most web apps, start with secure, HttpOnly cookie sessions. They’re simple, and — importantly — you can instantly cancel one (log someone out) just by deleting it server-side.
You’ll also hear about JWTs (tokens that carry the info inside themselves, signed so they can’t be faked). They’re handy for some systems, but harder to instantly “cancel” — sessions are usually the simpler choice to start with.
Checking permissions — always on the server
Never rely on hiding a button in the UI to protect something. Anyone can call your API directly, bypassing your frontend entirely:
function canEditProject(user, project) { return user.role === 'admin' || project.ownerId === user.id}async function updateProject(userId, projectId, name) { const user = await getUser(userId) const project = await getProject(projectId) if (!canEditProject(user, project)) throw new Error('Forbidden') // ...proceed}
WARNING
Hiding a button in the frontend is not security. Always check permissions again on the server, on every request.
In one sentence
Authentication confirms who someone is (usually via a secure session cookie), authorization decides what they’re allowed to do — and permission checks must always happen on the server, never just in the UI.
Want to go deeper?
Switch to Expert mode above for JWT trade-offs, password hashing, and role-based access control.