Django User registration and CSRF token - django

I am now making about user registration like this. I already allow any permission to make this request. But, when I post from postman, I got
"detail": "CSRF Failed: CSRF token missing or incorrect."
How shall I do?
class RegistrationView(APIView):
""" Allow registration of new users. """
permission_classes = (permissions.AllowAny,)
def post(self, request):
serializer = RegistrationSerializer(data=request.DATA)
# Check format and unique constraint
if not serializer.is_valid():
return Response(serializer.errors,\
status=status.HTTP_400_BAD_REQUEST)
data = serializer.data
# u = User.objects.create_user(username=data['username'],
# email=data['email'],
# password='password')
u = User.objects.create(username=data['username'])
u.set_password(data['password'])
u.save()
# Create OAuth2 client
name = u.username
client = Client(user=u, name=name, url='' + name,\
client_id=name, client_secret='', client_type=1)
client.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)

CSRF have nothing to do with permission classes. APIView are by default csrf-exempt. A very good discussion on APIView and csrf here
Because APIViews can both be used from a browser (which needs CSRF protection) or a server (which does not need CSRF protection) by default the CSRF protection is turned off. However, also by default an APIView has an authentication class called SessionAuthentication. This class will dynamically re-apply django's CSRF middleware if the current request object contains an active user.
So most probably you are getting that error because of you already logged in.

You may find more details here:
https://docs.djangoproject.com/en/1.7/ref/contrib/csrf/
Form:
<form action="." method="post">{% csrf_token %}
AJAX:
Get CSRF from cookie:
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
JQuery AJAX:
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
function sameOrigin(url) {
// test that a given url is a same-origin URL
// url could be relative or scheme relative or absolute
var host = document.location.host; // host + port
var protocol = document.location.protocol;
var sr_origin = '//' + host;
var origin = protocol + sr_origin;
// Allow absolute or scheme relative URLs to same origin
return (url == origin || url.slice(0, origin.length + 1) == origin + '/') ||
(url == sr_origin || url.slice(0, sr_origin.length + 1) == sr_origin + '/') ||
// or any other URL that isn't scheme relative or absolute i.e relative.
!(/^(\/\/|http:|https:).*/.test(url));
}
$.ajaxSetup({
beforeSend: function(xhr, settings) {
if (!csrfSafeMethod(settings.type) && sameOrigin(settings.url)) {
// Send the token to same-origin, relative URLs only.
// Send the token only if the method warrants CSRF protection
// Using the CSRFToken value acquired earlier
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
}
});

As you are using Session Authentication, it usually requires CSRF to be checked. So you must pass the CSRF token in the X-CSRFToken header.
Since you are in fact have not logged in, you have to use csrf_exempt decorator.
from django.views.decorators.csrf import csrf_exempt
class RegistrationView(APIView):
permission_classes = (permissions.AllowAny,)
#csrf_exempt
def post(self, request):
# Do what needs to be done
pass

Related

store UUID in session using middleware

I want to tag all requests with a UUID
(if the request doesn't have it in the first place).
I want to store the UUID in the session, so I wrote this middleware.
class MachineIDMiddleware:
"""
tags requests with machine UUIDs.
The machine-ID is set in the session.
"""
MID_KEY = "machine_id"
def __init__(self, get_response):
self.get_response = get_response
def __call__(self, request):
print(request.session.get(self.MID_KEY))
if self.MID_KEY not in request.session:
# set the machine-ID for the request
# if it has not been set already (making
# sure that it is serializable).
next_id = str(uuid.uuid4())
request.session[self.MID_KEY] = next_id
return self.get_response(request)
However, from my client, I noticed that the UUID keeps changing for every request.
From my client, I noticed that the sessionid cookie also changed for every request made.
As a result, a new UUID was generated for every request.
This is not what I want, though. I want to maintain only one UUID per person (who might be anonymous).
How can I achieve this?
Thanks a lot!
EDIT
export const Adapter = axios.create({
baseURL: baseURL,
headers: {
"Content-Type": "application/json"
}
});
Adapter.interceptors.request.use(
(request) => {
const token = tokenSelector(store.getState());
if (token) {
request.headers.Authorization = `Token ${token}`;
}
return request;
},
(error) => {
return Promise.reject(error);
}
);
Adapter.interceptors.response.use(
(response) => {
return response;
},
(error) => {
// handle unauthorized errors.
if (error.response.status === 401) {
store.dispatch(clearToken());
history.replace(SLUGS.login);
}
// handle internal server errors.
if (error.response.status === 500) {
toast.dark("Something went wrong. Please try again later.");
}
// handle server ratelimits.
if (error.response.status === 429) {
toast.dark("You are being ratelimited.");
}
return Promise.reject(error);
}
);
This is how I send requests from the frontend.
I use axios. I checked my cookies in the developer tools panel
and couldn't see the sessionid cookie there.
EDIT 2
Chrome devtools shows me the following error and is not
setting the sessionid cookie properly. Is this the reason maybe?
** Answer (SOLVED)**
setting the following variables in my settings.py file
made sure that chrome set the cookies correctly.
# CORS configuration
ALLOWED_HOSTS = ["*"]
CORS_ALLOW_ALL_ORIGINS = True
CSRF_COOKIE_SAMESITE = 'None'
SESSION_COOKIE_SAMESITE = 'None'
CSRF_COOKIE_SECURE = True
SESSION_COOKIE_SECURE = True
CORS_ALLOW_CREDENTIALS = True
SESSION_COOKIE_HTTPONLY = False
Here is your correct __call__ function
def __call__(self, request):
print(request.session.get("MID_KEY"))
if "MID_KEY" not in request.session:
next_id = str(uuid.uuid4())
request.session["MID_KEY"] = next_id.
return self.get_response(request)
The key shall be a string (constant) not a variable

Cookies works in Postman , but not in browser

I created a login API using Django rest framework and then used session auth.
When i sent request via Postman , i get csrftoken and sessionid cookies.
and i was able to access content on backend.
OK fine.
But when i built small login form html and called that API for logging in. It worked.
I see COOKIES IN RESPONSE BUT COOKIES ARE NOT SET IN CHROME BROWSER.
Under Storage section in dev tools cookies are empty.
when i tried to access content(other views/apis) , i was not able to..
I think its because of Cookies are not being stored in browser..
Been on this like 5 days. Please can Someone explain about cookies not being saved.?
View.py
class Login(APIView):
authentication_classes = [SessionAuthentication,]
def post(self, request, format=None):
username = request.POST.get("username", "")
print(request.session)
password = request.POST.get("password", "")
user = authenticate(request,username=username,password=password)
if user is not None:
login(request,user)
print(user)
return Response('Yes')
else :
return Response('No')
class List(APIView):
authentication_classes = [SessionAuthentication,]
permission_classes = [IsAuthenticated,]
def get(self, request, format=None):
return Response("Ark")
My Axios Request for login :
let s = this;
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Authorization", "Basic cjox");
myHeaders.append("Access-Control-Allow-Credentials","*");
var urlencoded = new URLSearchParams();
var requestOptions = {
method: 'POST',
credentials: 'same-origin',
headers: myHeaders,
body: urlencoded,
redirect: 'follow'
};
axios.post("http://127.0.0.1:8000/api/login/",urlencoded,{headers:myHeaders},{withCredentials: true})
.then(res=>{
console.log(res.headers);
})
My other request :
var myHeaders = new Headers();
myHeaders.append("Content-Type", "application/x-www-form-urlencoded");
myHeaders.append("Access-Control-Allow-Credentials","*");
var urlencoded = new URLSearchParams();
var requestOptions = {
method: 'GET',
credentials: 'same-origin',
headers: myHeaders,
redirect: 'follow'
};
axios.get("http://127.0.0.1:8000/api/d/",{headers:myHeaders},{withCredentials: true});

Django AJAX login without re-direct

Yes, there is a similar question without resolution to the actual question.
My users may be trying out the app and then decide to register for an account to save. Once registered I need to log them in without redirecting or reloading the page. I've got the registration working and logging them in is no problem, but what do I return to the client in order for the logged in session to be active in the browser? I'm not looking to add any other frameworks or libraries to the stack, please.
Django View (some omitted)
class CheckAuthTokenAjaxView(FormView):
form_class = AuthTokenForm
def post(self, *args, **kwargs):
form = self.form_class(self.request.POST)
u = get_user_model().objects.filter(email=email).first()
u.email_confirmed = True
u.save()
login(self.request, u)
# What should I be sending back?
return JsonResponse({"success": True}, status=200)
JS
Doc.Register.checkAuthTokenForm = function(event) {
event.preventDefault();
var form = document.getElementById('authtoken-form');
var data = new FormData(form);
d3.json('/users/ajax/checkAuthToken/', {
method: 'post',
body: data,
headers: {
// "Content-type": "charset=UTF-8",
"X-CSRFToken": Doc.CSRF,
}
})
.then(result => {
if (result.hasOwnProperty('errors')) {
// ...
} else {
// WHAT DO I DO HERE?
}
});
}

Django, CORS, CSRF - am I doing it right?

My setup (local) is the following:
Vue.js running on localhost:8080 (npm run serve)
REST API built with Django running on localhost:8000 (./manage-py runserver)
In order to enable this to work I've made the following additions:
ALLOWED_HOSTS = [
...
'localhost',
'localhost:8000',
'localhost:8080',
]
INSTALLED_APPS = [
...
'rest_framework',
'corsheaders',
]
MIDDLEWARE = [
...
'django.contrib.sessions.middleware.SessionMiddleware',
'corsheaders.middleware.CorsMiddleware',
'django.middleware.common.CommonMiddleware',
...
]
CORS_ORIGIN_WHITELIST = (
'localhost:8080',
'localhost:8000',
)
CORS_ALLOW_CREDENTIALS = True
from corsheaders.defaults import default_headers
CORS_ALLOW_HEADERS = default_headers + (
'credentials',
)
One of my API functions:
#ensure_csrf_cookie
def try_login(request):
# this is just to get the initial CSRF token:
if request.method == "GET" or request.method == "OPTIONS":
return JsonResponse({'status': 'ok'})
# else, an actual login request:
else:
data = JSONParser().parse(request)
user = authenticate(request, username=data['user'] , password=data['pass'])
if user is not None:
login(request, user)
return JsonResponse({'login_succ': 'ok'});
else:
return JsonResponse({'login_succ': 'fail'});
Finally, in Vue:
api: function(endpoint, method, data) {
var headers = new Headers();
headers.append('content-type', 'application/json');
if (... this is not the first request ever ...)
{
csrftoken = document.cookie.replace(/(?:(?:^|.*;\s*)csrftoken\s*\=\s*([^;]*).*$)|^.*$/, "$1");
headers.append('X-CSRFToken', csrftoken);
}
method = method || 'GET';
var config = {
method: method,
body: data !== undefined ? JSON.stringify(data) : null,
headers: headers,
};
config['credentials'] = 'include';
return fetch(endpoint, config)
.then(response => response.json())
.catch((error) => { console.log(...); });
},
trylogin: function() {
// initial request: just to get the CSRF token
this.api(".../login/", "GET").then(
response => {
this.api(".../login/", "POST", {'username': ..., 'password': ...} ).then(
response => {
if ("login_succ" in response && res["login_succ"] == "ok")
{} // user is logged in
}
);
}
);
}
What happens now, afaiu, is that my initial API request (which does not have to be pointed to the endpoint equal to the subsequent POST request, right?) gets the CSRF token as a cookie. Every subsequent request reads this cookie and sets the X-CSRFToken header. The cookie itself is also being sent in the subsequent requests. I do not understand why is the token needed in both places.
Is this approach correct? Is everything I've done necessary? (Are there redundant parts?) I'm especially interested in the way that I should acquire the token in the first place, and in general with the token's lifecycle.
Thank you.

Getting 403 on POST

I am learning to work with Django Rest Framework and following the tutorial. I have create a simple index based on the tutorial, that works for GET, but not for POST:
#api_view(['GET','POST'])
def game_list(request):
if request.method == 'GET':
games = Game.objects.all()
serializer = GameSerializer(games, many=True)
return Response(serializer.data)
elif request.method == 'POST':
serializer = GameSerializer(data=request.data)
if serializer.is_valid():
serializer.save()
return Response(serializer.data, status=status.HTTP_201_CREATED)
return Response(serializer.errors, status=status.HTTP_400_BAD_REQUEST)
I set the default settings to AllowAny:
REST_FRAMEWORK = {
# Use Django's standard `django.contrib.auth` permissions,
# or allow read-only access for unauthenticated users.
'DEFAULT_PERMISSION_CLASSES': [
'rest_framework.permissions.AllowAny'
]
}
but I still get a HTTP 403 when I try to POST anything, using the Firefox RESTClient. I read that I have to add a X-CSRFToken header and cookie for this to work, but I do not have those.
From documentation:
By default, a ‘403 Forbidden’ response is sent to the user if an incoming request fails the checks performed by CsrfViewMiddleware. This should usually only be seen when there is a genuine Cross Site Request Forgery, or when, due to a programming error, the CSRF token has not been included with a POST form.
Also, as stated if the official documentation, CSRF is enabled by default, and you need to add a X-CSRFToken in your AJAX requests.
Here is the code from the documentation:
// using jQuery
function getCookie(name) {
var cookieValue = null;
if (document.cookie && document.cookie != '') {
var cookies = document.cookie.split(';');
for (var i = 0; i < cookies.length; i++) {
var cookie = jQuery.trim(cookies[i]);
// Does this cookie string begin with the name we want?
if (cookie.substring(0, name.length + 1) == (name + '=')) {
cookieValue = decodeURIComponent(cookie.substring(name.length + 1));
break;
}
}
}
return cookieValue;
}
var csrftoken = getCookie('csrftoken');
function csrfSafeMethod(method) {
// these HTTP methods do not require CSRF protection
return (/^(GET|HEAD|OPTIONS|TRACE)$/.test(method));
}
Take in mind that the documentation suggest to use ajaxSetup method from jquery, which is not a recommended way to do it because it can alter the way that others scripts uses the ajax function, so it's better to add the specific CSRF's code in your custom JS code like this:
$.ajax({
method: 'POST',
url: 'your.url.com/',
beforeSend: function(xhr, settings) {
if (!WU._csrfSafeMethod(settings.type) && !this.crossDomain) {
xhr.setRequestHeader("X-CSRFToken", csrftoken);
}
},
success: function(msg)
{}
});
Reference: https://docs.djangoproject.com/en/1.9/ref/csrf/#ajax