Cookie authentication flow for a SPA
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.
The 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 asessions
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.