Practice this topic in a realistic system design interview
Authentication and authorization sound similar, so people mix them up all the time. They are two different security checks, and a system has to get both right.
Authentication answers: "Who is making this request?" It proves identity, usually by checking a password, token, certificate, or device credential.
Authorization answers: "Is this caller allowed to do this action on this specific resource?" It decides what an already-identified caller may do.
The order matters. First you identify the caller. Then you decide what that caller can access.
A user can be correctly signed in and still have no right to read a particular invoice, delete a record, export data, or use an admin action.
Many serious security bugs happen when a system verifies identity but forgets to check access to the specific resource. That lets any logged-in user reach data that should be off limits.
This chapter explains how authentication and authorization work, where they happen in a request, and how to avoid the most common mistakes.
Consider a user opening an invoice in a SaaS application.
Authentication may tell the system this is user_42 from tenant_a. Authorization must still check whether user_42 can read invoice_123, whether the invoice belongs to tenant_a, and whether any extra rule applies.
Do not treat login as permission to do everything. Login only tells the system who the caller is.
Authentication verifies that a caller controls something the system accepts as proof.
For people, that proof may be a password, passkey, security key, one-time code, or biometric unlock. For machines, it may be a client certificate, signed JWT, API key, workload identity token, or cloud IAM role.
Authentication does not always prove a person's real-world identity. Many products only need to know that the same account came back. Regulated systems may need stronger checks, such as verifying legal identity or requiring a stronger login method.
Authentication factors are usually grouped as:
| Factor | Meaning | Examples |
|---|---|---|
| Something you know | A secret the user remembers | Password, PIN |
| Something you have | A device or login credential the user controls | Security key, authenticator app, passkey-capable device |
| Something you are | A biometric trait used locally to unlock a login credential | Fingerprint, face unlock |
Biometrics need one important clarification. In well-designed systems, the fingerprint or face scan is not sent to your server. It unlocks a local device credential, often a cryptographic key. The server verifies a signed challenge, which proves the device has the right private key, not a copy of the user's biometric data.
These are common authentication methods, how they work, and where they usually fit.
| Method | How it works | Notes |
|---|---|---|
| Password login | User submits a username and password | Use strong password hashing and MFA for sensitive accounts |
| Passkeys / WebAuthn | Device signs a challenge with a private key | Phishing-resistant and preferred for high-value accounts |
| Session cookie | Server stores a session and sends a cookie with a random session ID | Common for browser apps |
| Bearer token | Client sends a token with each request | Common for APIs and mobile apps |
| OAuth 2.0 | Lets one app access an API on behalf of a user or client | OAuth is about access, not login |
| OpenID Connect | Authentication layer on OAuth 2.0 | Common for "Sign in with Google" style login |
| SAML | XML-based federated login | Common in enterprise SSO |
| mTLS / workload identity | Machine proves possession of a certificate or platform-issued identity | Common for service-to-service authentication |
The right method depends on the client, risk, and environment. A browser SaaS app, mobile app, public API, and internal service mesh usually should not all use the same authentication method.
A password login usually involves several checks: the password, account state, a possible second factor, and then the session that gets created.
A production authentication flow checks more than the password. It confirms that the account is active, verifies the password hash, decides whether MFA is required, and looks for unusual login signals that may require a stronger check.
It may also check whether the account or credential has been compromised. Only then does it create, rotate, or deny the session.
Passwords should be stored with a dedicated password hashing algorithm such as Argon2id, bcrypt, or scrypt. Do not store plaintext passwords. Do not use fast general-purpose hashes such as SHA-256 by themselves for password storage. Fast hashes make password cracking easier if the password database leaks.
After authentication succeeds, the system usually creates a session or issues a token.
For browser applications, a random session ID in a Secure, HttpOnly, SameSite cookie is often the simplest safe default. The server stores the session data and can revoke it immediately.
For APIs, mobile apps, service-to-service calls, and federated login systems, tokens are common. Some tokens are random references that the server must look up. Others are self-contained signed tokens such as JWTs.
Where you store the session or token is part of the security design. Browser-accessible storage such as localStorage is exposed to JavaScript and is risky if an XSS bug exists. Cookies need CSRF defenses. JWTs need careful expiration, audience validation, key rotation, and a revocation plan.
There is no free storage location. Each option moves the risk.
Authorization decides whether a caller is allowed to perform an action. The caller may be a signed-in user, a service account, a device, or an anonymous client.
Authorization applies even to public endpoints. A public endpoint simply gives anonymous callers a small, intentional set of actions.
A complete authorization decision usually needs four things:
Authentication identifies the subject. Authorization evaluates the rest.
Authorization models differ mainly in what they base the decision on. Real systems often combine them.
| Model | Decision is based on | Example |
|---|---|---|
| RBAC | Roles mapped to permissions | billing_admin can invoices:refund |
| ABAC | Attributes of subject, resource, action, and environment | Allow if user.department == document.department |
| ReBAC | Relationships between subjects and objects | Allow if user is a member of the project |
| ACL | Per-resource access lists | Document grants Alice read/write |
| Policy-based access | Rules written as policies and evaluated by a policy engine | OPA, Cedar, cloud IAM policies |
For example, RBAC may grant invoices:read, while ABAC or ReBAC checks that the invoice belongs to the user's tenant or project.
Tracing a delete request shows where authorization sits: the API identifies the caller, loads the target resource, then decides whether this caller may act on that resource.
The API loads resource details before the final authorization decision. Checking only the user's role is not enough. The system must also check the target resource.
This is the difference between "the user has a delete permission somewhere" and "the user may delete this specific post."
An editor tries to delete a blog post. The full flow looks like this:
DELETE /posts/42.42.posts:delete?403 Forbidden.A signed-in user who lacks access gets 403 Forbidden. A request with missing or invalid authentication usually gets 401 Unauthorized.
The names are imperfect, but the distinction is useful: 401 means the caller has not presented valid credentials. 403 means the caller is known but not allowed.
These practices turn the concepts into habits that hold up once a system has many endpoints, services, and users.
Gateways, middleware, and load balancers can validate sessions and tokens early. That is useful.
Authorization still belongs close to the protected operation. Background jobs, admin endpoints, GraphQL resolvers, file downloads, and internal APIs need checks too. Attackers only need one missed path.
If no rule explicitly allows the action, deny it.
New endpoints should start closed. New resources should start private. New roles should start with no permissions.
Do not rely on the frontend hiding buttons. Do not rely on a route being hard to guess. Do not rely on IDs being random.
Every protected request should be checked on the server. This includes static files, exports, webhooks, background jobs, and service-to-service calls.
A user may be allowed to view an account with a normal session but require stronger authentication to change MFA settings, rotate API keys, export data, or approve payments.
This is called step-up authentication. It means asking for stronger proof, such as MFA, before a sensitive action.
Access changes must take effect predictably. A disabled user, a password or MFA reset, a removed role, a compromised token, a stolen session, or an employee leaving the company should all remove access in a clear and timely way.
Server-side sessions are easy to revoke. Self-contained tokens need short lifetimes, refresh-token rotation, revocation lists, or permission-version checks.
Log authentication and authorization events with enough detail to investigate incidents. Record login successes and failures, MFA challenges and failures, and every session creation and revocation. Capture permission denials, sensitive actions that were allowed, and any change to a role or policy.
Avoid logging secrets, passwords, full tokens, or sensitive personal data. Logs should help investigation without becoming another source of compromise.
Most access-control bugs are not in the happy path.
Test requests that arrive with no credentials, invalid credentials, or an expired session or token. Test an authenticated user who has no permission, a correct role pointed at the wrong tenant, and a correct role pointed at the wrong resource owner. Test a revoked role, a locked or restricted resource, and a service account calling a user-only endpoint.
Authorization tests should be first-class tests, not a few accidental controller checks.
Access-control bugs usually come from familiar mistakes, not exotic attacks.
OAuth 2.0 is for delegated access to APIs. OpenID Connect adds the identity layer used for login.
People often say "OAuth login" casually, but if the application needs to sign in a user, the implementation should be OpenID Connect.
A token is not trustworthy just because it looks like a JWT. The server must validate the signature, issuer, audience, expiration, algorithm, and key ID.
For opaque tokens, the server must look them up or ask the issuer whether they are valid.
The UI can improve user experience by hiding unavailable actions. It cannot protect data. All real enforcement must happen server-side.
admin or editor is rarely enough information. The system also needs resource scope: tenant, owner, project, environment, region, workflow state, or data sensitivity.
Internal services still need authentication and authorization. A compromised service, misconfigured job, or leaked service token can cause the same damage as an external attacker.
Authentication and authorization are tied to account lifecycle. When people join, change teams, or leave, their access must change too.
If those workflows are manual, access will drift. Connect identity, HR, directory, and application authorization systems where the risk justifies it.
Authentication establishes identity. Authorization controls access.
Good systems keep those decisions separate, validate both on the server, deny by default, and check access against the specific resource being requested.
Use strong authentication for account access, step-up authentication for sensitive actions, and explicit authorization checks for every protected operation.
The hard part is not the login screen. The hard part is making sure every path that touches data asks, "Is this caller allowed to do this here?"
10 quizzes