sajad torkamani

Preamble

Let’s assume you have a React SPA that works with a Symfony REST API and you need to implement authentication for your SPA.

This post documents how you can implement a cookie-based authentication.

Pre-requisites

  • The SPA and the API should be hosted on the same top-level domain so that cookies
    can be shared between them.
  • The session cookie should be marked as a HTTP-only cookie so it can’t be accessed by JavaScript code. This reduces potential attack surfaces.

Registration flow

1. User submits credentials (email & password) via a login form on a web page.

2. SPA calls an API endpoint to submit the credentials and obtain a session cookie (e.g., POST /api/sessions):

curl --location 'http://localhost:8080/api/sessions' \
--header 'Content-Type: application/json' \
--data-raw '{
    "email": "sajad@hey.com",
    "password": "Password$123"
}'

3. API verifies credentials are correct, generates a unique session ID, and returns a response:

  • 3.1. Because the user provided valid credentials, the API will create a new session.
    This session can be stored in-memory or in the database (e.g., in a sessions MySQL table).
  • 3.2. The session will have a long and unique identifier that’ll be almost impossible to guess.
  • 3.3. The API will return this session identifier in a Set-Cookie header so the client can store it and send it on subsequent requests. If a subsequent request contains a valid session ID, the API can safely trust that the incoming request is for the user associated with that session ID because it’s almost impossible to provide a valid session ID unless the user had successfully authenticated in a previous request.
HTTP/1.1 201 Created
Set-Cookie: sessionId=<some-unique-session-id>; expires=Fri, 02 May 2025 18:14:22 GMT; Max-Age=7200; path=/; httponly; samesite=lax

Credentials correct!

4. Browser containing the SPA knows how to speak HTTP so it respects the protocol by storing the sessionId cookie in the browser for the specified time (it will remove the cookie on 02 May 2025 at 18:14:22 GMT as configured by the expires attribute in the Set-Cookie header response from above).

5. On subsequent requests to the same server, the browser will send the sessionId cookie. The API will have code in place that checks incoming requests for valid session IDs. If they have valid session IDs, it can safely trust that the incoming request is for the user associated with that session ID because it’s almost impossible to provide a valid session ID unless the user had successfully authenticated in a previous request.

Login flow

1. User submits credentials via a login form which sends an API request to the endpoint responsible for verifying credentials.

2. If the credentials are valid, the API generates a unique session ID and sets a session cookie via a Set-Cookie response header.

3. The browser automatically stores cookies in its cookie jar and sends it on subsequent requests to the same server that initially set the cookie.

4. The SPA can redirect user to protected routes now and can rely on the browser sending the correct cookie to the API so that the user is authenticated.

Session management

A “session” represents a user’s authenticated session. Here are some requirements around sessions:

  • When a user first registers or logs in, you create a session.
  • When a user logs out, you destroy the session.
  • When a user session expires, you destroy the session.
    • You’ll need to schedule a job to run periodically to delete sessions that have expired. You can create a expires_at SQL column to store a session’s expiry date. Deleting the session means that when the API needs to verify if the session cookie for an incoming request is valid, it can simplify look up the database to see if a session record with the given session cookie ID exists. If it does, it’s valid. If it doesn’t, it’s invalid.