Session Authentication and Single-Page Applications#
TL;DR: Don’t use session authentication in cross-domain AJAX clients, it’s a world of pain.
Just go with
Authorization: Bearer <token> header and be at peace.
This is something that I learned the hard way, during completion of a take-home assignment. I would expect the most the full-stack developers to know this, but I haven’t found an in-depth explanation on the web. The tech stack is Django REST Framework and React combo. React frontend runs on a separate port in the local developer environment to support hot-reloading. In production, frontend is served from CDN, and API is hosted on a separate subdomain.
Session Authentication in Django#
Using Django terminology, “session” in a nutshell:
When user makes the first request to the server, Django session middleware generates a new anonymous session token.
The session data is available for read and write in Django views through
Before returning response, session middleware persists session data to the cache.
Session middleware attaches the session token to the response as a cookie, for example:
On subsequent requests, browser sends the cookie back to the server.
Server fetches session data from a cache.
(Django has other flavors, such as Database-stored session data, and signed cookies, but they both are inferior performance-wise, and are rare in production.)
Session authentication basically means that
request.session["user_id"] is populated in login view.
Django provides convenient attribute
request.user that fetches the
User model by the
The Django sessions framework is entirely, and solely, cookie-based.
Cookie contains a session ID – not the data itself.
Session Authentication in Django REST Framework#
Django REST Framework provides SessionAuthentication built on top of Django cookie-based sessions.
The documentation has a hint:
“Session authentication is appropriate for AJAX clients that are running in the same session context as your website.” – Authentication in Django REST Framework
What “the same session context as your website” means is that the API must be served at the same domain as frontend.
Okay, but does it mean that it won’t work in cross-origin setup? Great question! And a deep rabit hole…
Cross-Origin Resource Sharing (CORS)#
CORS headers allow server to configure what domains (origins) are allowed to send AJAX requests. In absence of response header explicitly stating that this domain is approved, the browser rejects the response.
Thank you, browser. The confusion of this situation comes from the developer unawareness of the possible attacks. Naive server doesn’t care about the domain. Naive frontend doesn’t care about the domain. But intermediary browser cares a lot, and it makes sure frontend won’t sneak any request.
– Okay, okay, we set the headers:
Access-Control-Allow-Credentials: true Access-Control-Allow-Origin: https://www.acme.com
That would do for the production. But not for local development.
Cross-Origin Troubles in Local Development#
In local environment, we have frontend and backend served from
localhost, but from different ports.
3000 for the frontend, and
8000 for the backend.
Session authentication kinda works if you login on the backend port directly.
The cookie is set for
localhost:8000, and the browser passes it to server for every AJAX request coming from
– What if you try to login through AJAX?
Not so much. Requests reach the server, existing cookies for the API domain are attached.
Server is happy, response arrives back to the frontend.
Set-cookie headers. Those get stripped by browser.
I haven’t found a definitive explanation.
Some point to presence of port number.
localhost not having two dots, and therefore not being a proper domain name.
I don’t know exactly what is it, but I tried every solution I found on the web, and got nowhere.
The cookie is not set for the frontend origin. It’s not set for API domain either. It just goes nowhere.
Let me clarify:
Browser attaches existing cookies when making requests to cross-origin API.
API server responds back with
No error or warning is logged in the browser, but the cookie is dropped (only for
On the next request to the API server the old cookies are sent.
Solution 2: Use HTTP Proxy Locally to Make AJAX Same-Origin#
These troubles don’t exist in production. When serving from a proper domain and standard HTTPS port, CORS headers work as expected.
For the local development, you can set up a proxy, so that HTTP requests are sent to the same-origin frontend
and proxied to
localhost:8000 from there.
The CORS protection is implemented only in the browser, and doesn’t exist in server-to-server API calls (such as proxy).