Skip to content
BEE
Backend Engineering Essentials

[BEE-1001] Authentication vs Authorization ​

INFO

Authentication establishes who you are; authorization determines what you are allowed to do. Conflating the two is one of the most common causes of broken access control.

Context ​

Access control failures consistently rank as the most critical web application vulnerability. OWASP's 2021 Top 10 lists Broken Access Control at position #1, and a significant share of those failures trace back to a single conceptual error: treating a verified identity as a granted permission.

The two concepts have distinct definitions in foundational standards:

  • Authentication (AuthN) — "The process of determining the validity of one or more authenticators used to claim a digital identity." (NIST SP 800-63-3, Section 2)
  • Authorization (AuthZ) — "The process of verifying that a requested action or service is approved for a specific entity." (NIST, via OWASP Authorization Cheat Sheet)

NIST is explicit that these are separate concerns: "Authentication does not determine the claimant's authorizations or access privileges; this is a separate decision." (NIST SP 800-63-3, Section 4.3.3)

RFC 7235, which defines the HTTP authentication framework, reinforces the boundary at the protocol level. A 401 response means the client has not authenticated; a 403 response means the server received valid credentials that are insufficient for access — authentication succeeded, authorization failed.

Principle ​

Systems MUST treat authentication and authorization as distinct pipeline stages, executed in order.

  1. AuthN MUST precede AuthZ. A permission check against an unverified identity is meaningless. The system cannot know whose permissions to check until identity is confirmed.

  2. A valid credential MUST NOT be treated as an authorization grant. Proving identity (a token is valid, a password matched) only answers "who is this?" It does not answer "what may this identity do?"

  3. Authorization logic MUST be enforced server-side. Client-side checks are advisory UI only. Every request that reaches a protected resource MUST be re-evaluated against the authorization policy, regardless of prior checks.

  4. Systems SHOULD deny by default. Access MUST be explicitly granted. Absence of a deny rule MUST NOT be interpreted as permission.

  5. Authorization policies SHOULD be externalized from application code into a dedicated policy layer (e.g., RBAC roles, ABAC rules, OPA policies). Hardcoded if user.role == "admin" logic scattered across handlers is brittle and auditable only with difficulty.

Visual ​

The following diagram shows a single HTTP request passing through the identity pipeline as two distinct gates.

The 401 and 403 status codes map directly to the two gates: identity failure versus permission failure.

Example ​

The following pseudocode shows the four-step pipeline in a generic request handler. It is intentionally framework-agnostic.

function handle(request):
    # Step 1 — Extract credential from the request
    credential = extract_credential(request)
    # e.g., parse Bearer token from Authorization header,
    #        read session cookie, read mTLS client certificate

    # Step 2 — Verify identity: authenticate
    principal = verify_credential(credential)
    if principal is null:
        return response(401, "Authentication required")
    # principal is now a verified identity object:
    # { id: "user-42", roles: ["editor"], tenant: "acme" }

    # Step 3 — Check permission: authorize
    action   = derive_action(request)        # e.g., "documents:write"
    resource = derive_resource(request)      # e.g., "doc-99"
    allowed  = policy.check(principal, action, resource)
    if not allowed:
        return response(403, "Forbidden")

    # Step 4 — Execute the request
    return execute(request, principal)

Notice that verify_credential and policy.check are separate calls to separate systems. The token (credential) carries identity claims; the policy engine decides permissions. A token that is cryptographically valid still goes through policy.check — validity is not permission.

Common credential and policy mechanisms ​

ConcernCommon mechanisms
AuthN (identity)Password + hash, JWT/JWS, mTLS client certificate, passkey/FIDO2, SAML assertion
AuthZ (permission)RBAC (roles), ABAC (attributes + rules), ReBAC (relationship graph), OPA/Cedar policies

Common Mistakes ​

1. Checking permissions before verifying identity.

A handler that calls policy.check(request.user_id, ...) without first confirming that request.user_id is genuine trusts attacker-supplied input. An unauthenticated request must be rejected at step 2 before reaching step 3.

2. Treating a valid token as an authorization decision.

A JWT that passes signature verification proves the token was issued by your system. It does not prove the token holder is permitted to perform the requested action. Roles embedded in a token are identity claims — they still MUST be evaluated against the current policy. Policies change; tokens may be stale.

3. Hardcoding authorization logic in application handlers.

# Anti-pattern
if request.user.role == "admin":
    allow()

Scattered if role == X checks cannot be audited consistently, are omitted in new code paths, and cannot be updated without a deployment. Externalize policy evaluation behind a dedicated interface.

4. Using OAuth 2.0 alone for authentication.

OAuth 2.0 is an authorization delegation framework. An access token proves that a resource owner delegated certain scopes — it does not authenticate the user to your application. Use OpenID Connect (OIDC) on top of OAuth 2.0 when you need verified user identity. (See BEE-1003.)

5. Skipping authorization on internal or "trusted" paths.

Internal APIs, background job handlers, and admin-only routes are common blind spots. Every request path that accesses protected data MUST enforce authorization regardless of where the request originates.

  • BEE-1002 Token-Based Authentication -- how tokens establish identity
  • BEE-1003 OAuth 2.0 and OpenID Connect -- delegation vs authentication
  • BEE-1004 Session Management -- maintaining authenticated state
  • BEE-1005 RBAC vs ABAC -- permission models in depth
  • BEE-1006 API Key Management -- API credential patterns
  • BEE-1007 WebAuthn Fundamentals -- modern phishing-resistant authentication
  • BEE-1011 Migrating from Passwords to Passkeys -- rollout playbook for the modern auth model

References ​