I have been struggling and doing research for couple of days. I am currently use Django with Django rest_framework and django-allauth to build backend APIs and the authentication workflow. Everything runs prefect by hosting everything onto the same origin VM(A).
Now due to requirement, I need to separate frontend login and backend API into two different origin. (A & B) APIs are behind the proxy.
Instead of using django-allauth default server rendered login page(which auto set the csrf in {% csrf_token %}) I have made a simple login.html and put it into host A under apache /var/www/html/:
<form class="login" method="POST" action="https://B.com/accounts/login/">
<input id="csrf" type="hidden" name="csrfmiddlewaretoken">
<p>
<label for="id_login">Login:</label>
<input type="text" name="login" placeholder="Username or e-mail" autocomplete="email" required="" id="id_login">
</p>
<p>
<label for="id_password">Password:</label>
<input type="password" name="password" placeholder="Password" autocomplete="current-password" required="" id="id_password">
</p>
<button type="submit">Sign In</button>
</form>
<script type="text/javascript">
window.onload = function(){
// Get csrf from B and set into cookie 'getToken' API is defined below
$.ajax({
url: "https://B.com/getToken",
type: 'GET',
dataType: 'json',
success: function(res) {
console.log(res);
$('#csrf').val(res.token);
document.cookie = 'csrftoken=' + res.token;
}
})
};
</script>
When the login.html is rendering it will make a GET request to the backend(B) API (/getToken) to fetch the csrftoken and save into the cookie and csrfmiddlewaretoken
tag, when the login form is submitted csrftoken will be sent along with it.
and in the backend, I have installed corsheaders
. and configured setting.py as following
from composed_configuration import ConfigMixin, ComposedConfiguration
from configurations import values
class Config(ConfigMixin):
CORS_ALLOW_CREDENTIALS = True
CORS_ORIGIN_ALLOW_ALL = False
CORS_ORIGIN_WHITELIST = (
'https://A.com',
)
CSRF_TRUSTED_ORIGINS = values.ListValue(environ=True, default=['https://A.com'])
@staticmethod
def before_binding(configuration: ComposedConfiguration) -> None:
configuration.REST_FRAMEWORK['DEFAULT_AUTHENTICATION_CLASSES'] += [
'rest_framework.authentication.TokenAuthentication'
]
and to get csrf token I made an urls.py:
from django.middleware.csrf import get_token
from django.http import HttpResponse
import json
from django.urls import path
def getToken(request):
token = get_token(request)
return HttpResponse(json.dumps({'token': token}), content_type="application/json,charset=utf-8")
path('getToken', getToken, name='getToken')
When I submit the form with username and password with csrf in the tag, I still get django 403 Forbidden error CSRF token missing or incorrect
.
Any idea, work around appreciated.
To someone who encountered the same issue, it is solved by adding crossDomain: true
and xhrFields: {withCredentials: true}
when get token in the frontend
In the backend add CSRF_COOKIE_SECURE = True
CSRF_COOKIE_SAMESITE = None
SESSION_COOKIE_SAMESITE = None
option in the setting.py (by default the samesite is set to lax
).