GICT knowledge base
Authentication vs Authorization: A Developer’s Guide
Published
Authentication and authorization get used interchangeably all the time. Even experienced developers mix them up in conversation. They sound similar, they work closely together, and in most systems they live in the same part of the codebase. But they are doing completely different jobs – and confusing them leads to security gaps that are surprisingly easy to miss.
Authentication answers one question: who are you? It is the process of verifying identity. A user provides credentials – a password, a fingerprint, a one-time code – and the system checks whether those credentials match a known identity.
Authorization answers a different question: what are you allowed to do? Once the system knows who you are, it decides what you can access, what actions you can take, and what data you can see.
The order matters. Authorization cannot happen without authentication first. You cannot decide what someone is allowed to do until you know who they are. But authentication alone is not enough – knowing someone’s identity does not automatically tell you what they should have access to.
This guide covers how both work, the common patterns for implementing each, where they sit in a real application, and the mistakes that come up most often.
Prerequisites
- You build or work on web applications or APIs.
- You have a basic understanding of HTTP and how requests work.
- Some experience with user login systems is helpful but not required.
Authentication – Proving Who You Are
Authentication is the process of verifying that someone is who they claim to be. The user presents something to prove their identity, and the system checks it against what it knows.
Password-Based Authentication
The oldest and still most common approach. A user provides a username or email along with a password. The system looks up the account, checks the password against a stored hash, and either grants or denies access.
The critical detail here is hashing. Passwords should never be stored in plain text. A one-way hashing algorithm like bcrypt, Argon2, or scrypt turns the password into a fixed-length string that cannot be reversed. When the user logs in, the system hashes what they entered and compares it to the stored hash. A match means correct password, no match means wrong one.
Storing plain text passwords is an unforgivable security failure. If your database is ever exposed, every user’s password is immediately visible. With hashing, the attacker gets a list of hashes they then have to crack – which is slow and often impractical with modern algorithms.
Multi-Factor Authentication
A password alone is a single point of failure. If someone guesses it, phishes it, or finds it in a data breach from another site, they are in. Multi-Factor Authentication (MFA) adds a second layer.
The three factors of authentication are:
- Something you know – a password or PIN.
- Something you have – a phone, a hardware key, an authenticator app.
- Something you are – a fingerprint, a face, a retina scan.
Requiring two of these together means stealing a password is not enough on its own. The attacker also needs the second factor, which is typically much harder to get remotely.
Time-based One-Time Passwords (TOTP) – the six-digit codes generated by apps like Google Authenticator – are the most practical MFA option for most web applications. They rotate every 30 seconds and cannot be reused, which makes them resistant to replay attacks.
Token-Based Authentication
After a user successfully authenticates, the server needs a way to recognize them on subsequent requests without making them log in every time. Tokens handle this.
The most common token format is JSON Web Token (JWT). After login, the server generates a signed token containing the user’s ID and any relevant claims. This token gets sent to the client and included in the Authorization header on every subsequent request.
Authorization: Bearer eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...
The server verifies the signature on each request to confirm the token has not been tampered with. If the signature is valid, the server trusts the claims inside without needing to look anything up.
JWTs are stateless – the server stores nothing. The trade-off is revocation. A JWT is valid until it expires. If you need to invalidate one before expiry, you need a server-side blocklist, which reintroduces state.
OAuth and Third-Party Authentication
OAuth is a protocol that lets users authenticate through a third-party provider – Google, GitHub, Microsoft – without sharing their password with your application. The user authenticates with the provider, the provider confirms their identity to your app, and your app creates or links an account.
This is what powers every “Sign in with Google” button. Your application never sees the user’s Google password. Google handles the authentication and tells your app the user is who they say they are.
OAuth is commonly confused with authentication because it is used for login flows. Technically, OAuth is an authorization protocol – it grants your app permission to access resources on behalf of the user. OpenID Connect (OIDC) is the layer built on top of OAuth that handles the identity part specifically.
Authorization – Deciding What You Can Do
Once a user is authenticated, the application needs to figure out what they are allowed to do. That is authorization. The same user who can view their own orders should not be able to view someone else’s. An admin should be able to delete users. A regular user should not.
Role-Based Access Control
Role-Based Access Control (RBAC) is the most widely used authorization model. Users are assigned roles – admin, editor, viewer, moderator – and each role comes with a set of permissions. When a user tries to do something, the system checks their role and whether that role has the required permission.
The appeal of RBAC is simplicity. You define a small number of roles, assign permissions to roles rather than individual users, and users inherit permissions from their role. Adding a new user to a team means assigning them a role. Changing what editors can do means updating the editor role – everyone with that role gets the change automatically.
RBAC works well when user types are distinct and relatively few. It starts to strain when you need fine-grained control – when the same action needs to be allowed for some resources and blocked for others depending on context.
Attribute-Based Access Control
Attribute-Based Access Control (ABAC) makes access decisions based on attributes of the user, the resource, the action, and the environment. Instead of asking “does this role have permission,” ABAC asks “given all the relevant context, should this specific access be allowed.”
A policy might say: a user can edit a document if they are the document’s owner, or if they are in the editor role and the document is in a project they belong to, and the document is not locked.
ABAC is more expressive than RBAC but more complex to implement and reason about. It suits applications with complex, context-dependent access rules – healthcare systems, financial platforms, content management systems with granular permissions.
Resource-Level Authorization
Regardless of the broader model, most applications need to check that a user has access to a specific resource, not just a type of action. A user might have the “view orders” permission globally, but they should only be able to view their own orders, not every order in the system.
This check needs to happen at the data level – not just at the route level. A common mistake is checking whether a user is authenticated and has the right role, but forgetting to verify that the specific resource they are requesting actually belongs to them. This is called an Insecure Direct Object Reference (IDOR) vulnerability, and it is one of the most frequently exploited weaknesses in web applications.
When a user requests resource ID 4521, verify both that they have permission to view resources of that type and that resource 4521 specifically is theirs to view.
Where They Sit in a Real Application
In most web applications, authentication and authorization happen in different places and at different stages of a request.
Authentication typically happens at the edge – in middleware that runs before any route handler. The middleware reads the session cookie or Authorization header, validates it, identifies the user, and attaches that identity to the request context. If authentication fails, the request gets rejected with a 401 Unauthorized before it reaches any application logic.
Authorization happens closer to the resource. After the application knows who the user is, individual route handlers or service methods check whether that user has permission to perform the requested action on the requested resource. Failing authorization returns a 403 Forbidden – the user is known but not permitted.
The HTTP status codes matter here. Returning 403 when authentication fails tells the client there is something worth trying harder to access. Returning 401 when authorization fails tells the client they have the wrong permissions when actually they just need to log in. Using them correctly helps clients handle errors appropriately.
Common Mistakes
- Checking authentication but not authorization – knowing who the user is does not mean they are allowed to do what they are trying to do. Always check both.
- Role checks at the route level only – checking that a user is an admin to access an admin route is not enough if the route handler then performs operations without checking that the specific data belongs to that user. Enforce authorization at the data level too.
- Storing passwords in plain text – there is no acceptable reason for this. Use bcrypt or Argon2 and salt every hash.
- Long JWT lifetimes without refresh tokens – a JWT valid for 30 days is essentially a permanent credential if it gets stolen. Use short-lived access tokens with refresh tokens, so the server has periodic opportunities to revoke access.
- Trusting client-supplied roles or permissions – if the client can tell the server what role it has, the authorization model is broken. Roles and permissions live on the server. The client presents an identity, and the server decides what that identity is allowed to do.
- Not logging authentication events – failed login attempts, password resets, MFA bypasses, and privilege escalations should all be logged. Without an audit trail, detecting and investigating attacks is nearly impossible.
Conclusion
Authentication and authorization are distinct problems that need to be solved separately and in the right order. Authentication confirms identity. Authorization decides what that identity can do.
Most security issues in this space come from one of two places – implementing either concept incorrectly, or implementing one while neglecting the other. A system with strong authentication but weak authorization can still be exploited by authenticated users accessing data they should not reach. A system that checks permissions without properly verifying identity is trusting claims it cannot back up.
Get authentication right with strong hashing, MFA where it matters, and short-lived tokens. Get authorization right with a consistent model, resource-level checks, and proper use of HTTP status codes. Treat them as the separate concerns they are, and most of the common vulnerabilities in this space become much harder to introduce.
Related guides
How to Protect SSH with CrowdSec on Ubuntu
Protect SSH with CrowdSec on Ubuntu. Connect log detection to firewall enforcement and test temporary blocking without losing administrator access.
Website SecurityHow to Secure the Ollama API with Nginx
Protect a private Ollama API with Nginx authentication and restricted routes. Verify allowed requests, denied management calls and direct-port isolation.
Website SecurityHow to Protect a Web App with Authelia
Protect an app with Authelia and Nginx. Keep the backend private, configure password authentication and test permitted and denied user access.
Website SecurityHow to Manage Let’s Encrypt SSL Certificates with Certbot Commands
Learn to manage Let's Encrypt SSL certificates using Certbot commands for installation, renewal, and configuration to keep your site secure.
