Week 10

Security Mindset

Threat Modeling Basics

Common Backend Vulnerabilities

Handling Sensitive Data

Hashing vs. Encryption

Authentication & Authorization

Spring Security

Practice

Assignment

Back end Track

Under construction

<aside> 🚧

This page is currently under construction. Please check back later.

</aside>

Introduction

In the last chapter, we learned how to securely verify a user's password using bcrypt. That process—verifying a user is who they claim to be—is the first crucial step. But what happens after they log in? How do we remember them on their next request? And how do we control what they are allowed to do?

This is where we move from a single check to a continuous process of managing a user's session and permissions. These two concepts, Authentication and Authorization, are the pillars of application access control.

While often used together, these terms represent distinct stages in the access control process. A secure system must implement both correctly.

Authentication: Verifying Identity

Authentication is the process of confirming that a user is who they claim to be. It answers the question, "Who are you?".

Mechanism: It involves the user presenting one or more credentials (factors of authentication) that are validated by the system.

User Authentication Example: When a user submits a username and password via a login form, the backend authenticates them. It does this by hashing the provided password and comparing it to the securely stored hash in the database for that username. A match confirms the user's identity.

Service Authentication Example: Your backend service needs to fetch data from an external payment gateway API. To do so, your service includes a secret API key in its request header (e.g., Authorization: Bearer <secret_api_key>). The payment gateway authenticates the request by validating this key, confirming that the request is from a trusted and legitimate application.

Outcome: A successful authentication process establishes a trusted identity for the requesting entity. This is often represented by creating a session (for a user) or by simply granting access for that single API call (for a service).

Authorization: Enforcing Permissions

Authorization is the process of determining if an authenticated user has the necessary permissions to perform a specific action or access a particular resource. It answers the question, "What are you allowed to do?".

Mechanism: This process happens after successful authentication. For every incoming request to a protected endpoint, the system checks the authenticated user's identity and associated permissions (e.g., roles, ownership) against an access control policy.

Web Application Example: An authenticated user with userId: 50 makes a GET request to /api/orders/123. The authorization logic on the backend must verify: "Does the user with ID 50 have permission to view order 123?" This could be based on a role (e.g., an ADMIN can view any order) or ownership (the user can only view their own orders). If they lack permission, the server must respond with an error, typically 403 Forbidden.

Outcome: A "Permit" or "Deny" decision for a specific request.

Feature Authentication Authorization
Question "Who are you?" "What are you allowed to do?"
Process Verifying credentials against a trusted source. Enforcing access policies against an authenticated identity.
Output A verified identity, often represented by a session or token. A Permit / Deny decision (e.g., 200 OK vs. 403 Forbidden).
Timing Typically happens once at the beginning of a session (login). Happens on every request to a protected resource.

Authentication and Session Management

HTTP is a stateless protocol. This means the server treats every request as a new, independent event, with no memory of past requests. Therefore, after a user authenticates, we need a mechanism to persist their identity across subsequent API calls. This is known as session management.

There are two primary architectural patterns for session management: stateful sessions and stateless tokens.

Stateful Sessions (Server-Side)

In this model, the server maintains the state of the user's session after login.

  1. A user authenticates with their credentials.
  2. The server verifies the credentials, generates a cryptographically secure, random Session ID, and creates a session record.
  3. This session record, which maps the Session ID to the user's data (e.g., userId, roles), is stored on the server-side (in-memory, in a cache like Redis, or in the database).
  4. The server sends the unique Session ID back to the client, typically within a secure, HttpOnly cookie.
  5. For every subsequent request, the client's browser automatically includes the session cookie.
  6. The server extracts the Session ID, looks up the corresponding session record in its store, and uses the retrieved user data to authorize the request.

Key Characteristic: The session's state and data live on the server. The client only holds an opaque identifier.

Stateless Tokens (Client-Side)

In this model, the server offloads the session state to the client in the form of a secure, self-contained token. The most common implementation is the JSON Web Token (JWT).

  1. A user authenticates with their credentials.
  2. The server constructs a JWT. The JWT's payload contains "claims"—data about the user (like userId, roles) and metadata (like an expiration timestamp, exp).
  3. The server cryptographically signs the JWT using a secret key known only to the server. This signature guarantees the token's authenticity and integrity.
  4. The server sends the full JWT back to the client.
  5. The client stores the JWT (e.g., in memory) and includes it in the Authorization: Bearer <jwt> header of every subsequent request.

• 6. For every request, the server receives the JWT, validates its signature using the secret key, and checks its expiration. If the signature is valid, the server trusts the claims within the payload without needing to query a database or session store.

JWT Structure

A JWT consists of three Base64Url-encoded parts, separated by dots:

  1. Header: JSON object containing metadata about the token, such as the signing algorithm ("alg": "HS256").
  2. Payload: JSON object containing the claims. These are statements about the user and the token itself.Warning: The payload is only encoded, not encrypted. It is publicly readable. Never place sensitive information in a JWT payload.
  3. Signature: Created by hashing the encoded header, the encoded payload, and a secret key. This signature ensures that the token was not tampered with.

Session vs. JWT Tradeoffs

Feature Stateful Sessions Stateless JWTs
State Stateful. Server is responsible for storing and managing session data. Stateless. All required session data is in the token held by the client.
Scalability Can be complex. Requires a shared session store (e.g., Redis) for a distributed system. Highly scalable. Any microservice with the secret key can validate a token independently.
Revocation Trivial. To invalidate a session, delete the record from the server-side store. Complex. A JWT is valid until it expires. Immediate revocation is not inherently supported.
Performance Requires a database/cache lookup on each authenticated request to retrieve session data. No database lookup needed. Signature verification is CPU-bound but typically very fast.

The Modern Standard: Access and Refresh Tokens

The major drawback of JWTs is their difficult revocation. A stolen token is valid until it expires. A long expiration time is a security risk, while a short one degrades user experience. The Access Token / Refresh Token pattern solves this.

This pattern uses two different tokens:

  1. Access Token (JWT):
  2. Refresh Token:

The Authentication Flow:

  1. Login: User submits credentials. The server authenticates them and generates both a short-lived access token and a long-lived refresh token. The refresh token is stored in a refresh_tokens table in the database.
  2. API Access: The client sends the access token in the Authorization header to access protected endpoints. The server validates the JWT on each call.
  3. Token Expiration: After 15 minutes, the access token expires. When the client uses it, the server responds with a 401 Unauthorized error.
  4. Token Refresh: The client's logic catches the 401 error and makes a request to a dedicated endpoint (e.g., /api/auth/refresh). This request includes the refresh token.
  5. Refresh Validation: The server receives the refresh token, looks it up in its database to ensure it is valid and has not been revoked, and then issues a new access token. For enhanced security, it can also issue a new refresh token (token rotation) and invalidate the old one.
  6. Retry Request: The client receives the new access token and automatically retries the original API request that failed. This entire refresh process is seamless to the user.

This hybrid approach provides the stateless scalability of JWTs for most API calls while retaining the critical ability to revoke sessions by invalidating the refresh token in the database, offering a robust balance of security and user experience.

In the last chapter, we learned how to securely verify a user's password using bcrypt. That process—verifying a user is who they claim to be—is the first crucial step. But what happens after they log in? How do we remember them on their next request? And how do we control what they are allowed to do?

This is where we move from a single check to a continuous process of managing a user's session and permissions. These two concepts, Authentication and Authorization, are the pillars of application access control.