What is Authentication vs. Authorization?
Authentication is proving who you are (logging in). Authorization is checking what you're allowed to do (permissions). Both are essential - and confusing them breaks security.
The two questions
Every secure system asks two questions:
| Question | Concept | Example |
|---|---|---|
| “Who are you?” | Authentication | Logging in with email/password |
| “Can you do this?” | Authorization | Checking if you can delete a post |
They’re different concerns:
- You can be authenticated (logged in) but not authorized (no permission)
- You can’t be authorized without being authenticated first
- Different users have different authorizations
Authentication methods
1. Username + Password
The classic. You prove identity by knowing a secret.
// User submits credentials
const response = await fetch('/api/login', {
method: 'POST',
body: JSON.stringify({
email: 'user@example.com',
password: 'hunter2' // Never store plaintext!
})
});
// Server checks password hash, returns session tokenProblems: Passwords get reused, phished, and leaked. Use a proper auth service that handles hashing, rate limiting, and breach detection.
2. API Keys
Simple authentication for services. One key per app/user.
// Include key in request headers
const response = await fetch('https://api.anthropic.com/v1/messages', {
headers: {
'x-api-key': process.env.ANTHROPIC_API_KEY
}
});Problems: API keys are often all-or-nothing (full access or none). They don’t expire automatically. They’re easy to leak in code.
3. OAuth / Social Login
“Login with Google/GitHub/etc.” The user authorizes your app through the provider.
1. User clicks "Login with Google"
2. Redirected to Google's login page
3. User approves: "Allow MyApp to see your email"
4. Google redirects back with a code
5. Your server exchanges code for user infoBenefit: You never see the user’s password. The provider handles authentication.
4. Magic Links
Email a one-time login link. No password to remember.
1. User enters email
2. You send email with link: https://app.com/login?token=abc123
3. User clicks link
4. Token is verified, user is logged inBenefit: No passwords to leak or forget.
5. JWTs (JSON Web Tokens)
A token containing user info, cryptographically signed. The server can verify it without database lookups.
// JWT structure: header.payload.signature
// eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.
// eyJ1c2VySWQiOiIxMjM0NTYifQ.
// SflKxwRJSMeKKF2QT4fwpMeJf36POk6yJV_adQssw5c
// Server verifies signature, trusts payload
const decoded = jwt.verify(token, process.env.JWT_SECRET);
console.log(decoded.userId); // "123456"Benefit: Stateless - no session database needed. Risk: Can’t revoke until expiration (use short lifetimes).
Authorization patterns
1. Role-Based Access Control (RBAC)
Users have roles. Roles have permissions.
const roles = {
admin: ['read', 'write', 'delete', 'manage-users'],
editor: ['read', 'write'],
viewer: ['read']
};
function canDelete(user) {
return roles[user.role].includes('delete');
}Good for: Most apps. Simple to understand and implement.
2. Attribute-Based Access Control (ABAC)
Permissions based on attributes of user, resource, and context.
function canEdit(user, post) {
// User is owner OR user is admin OR user is editor of this category
return post.authorId === user.id ||
user.role === 'admin' ||
user.editableCategories.includes(post.category);
}Good for: Complex rules that depend on resource properties.
3. Resource Ownership
Users can only access their own stuff.
// Get only posts by the authenticated user
const posts = await db.post.findMany({
where: { authorId: currentUser.id }
});Good for: Multi-tenant apps, user data isolation.
The 401 vs 403 distinction
This trips everyone up:
| Code | Name | Meaning |
|---|---|---|
| 401 | Unauthorized | You’re not authenticated (we don’t know who you are) |
| 403 | Forbidden | You’re authenticated but not authorized (we know you, but no) |
app.delete('/api/posts/:id', async (req, res) => {
// Step 1: Authentication
const user = await getAuthenticatedUser(req);
if (!user) {
return res.status(401).json({ error: 'Not authenticated' });
}
// Step 2: Authorization
const post = await getPost(req.params.id);
if (post.authorId !== user.id && user.role !== 'admin') {
return res.status(403).json({ error: 'Not authorized to delete this post' });
}
// Both passed: proceed
await deletePost(post.id);
return res.status(200).json({ success: true });
});Implementing auth in your app
Option 1: Use an auth service (recommended)
Services like Clerk, Auth0, Firebase Auth, or Supabase Auth handle:
- Password hashing and storage
- Session management
- OAuth integrations
- Security best practices
- Multi-factor authentication
// With Clerk in Next.js
import { auth } from '@clerk/nextjs/server';
export async function POST(request) {
const { userId } = await auth();
if (!userId) {
return new Response('Unauthorized', { status: 401 });
}
// userId is authenticated, proceed with authorization...
}Option 2: Build it yourself (only if necessary)
If you must, use established libraries:
- Node.js: Passport.js, bcrypt, jose (JWTs)
- Python: Flask-Login, itsdangerous, PyJWT
Never:
- Store passwords in plain text
- Hash passwords with MD5 or SHA1 (use bcrypt or argon2)
- Roll your own crypto
Middleware pattern
Most frameworks support auth middleware - check authentication once, apply to many routes:
// Express middleware
function requireAuth(req, res, next) {
const user = verifySession(req.headers.authorization);
if (!user) {
return res.status(401).json({ error: 'Not authenticated' });
}
req.user = user;
next();
}
// Apply to routes
app.get('/api/profile', requireAuth, getProfile);
app.post('/api/posts', requireAuth, createPost);
app.delete('/api/posts/:id', requireAuth, requireAdmin, deletePost);Common auth mistakes
Storing passwords in plain text: Use bcrypt or argon2.
Not validating JWTs: Always verify the signature and check expiration.
Authorization in the frontend only: The frontend can hide UI, but the backend MUST enforce rules. Users can call APIs directly.
Overly broad permissions: Give minimum necessary access. Don’t make everyone an admin.
Not rate limiting login attempts: Attackers can brute-force passwords. Add delays after failed attempts.
Further reading
- What is an API key? : Simple authentication for APIs
- What is HTTPS? : Protecting credentials in transit
- What is a 404 error? : Understanding 401 and 403 codes
Frequently asked questions