Ajax Authentication with Tastypie Django - ajax

I followed this to create my ajax authentication. The ajax does not send the POST data; it sends an empty querydict. If I explicitly write my username and password in my ajax view, it logins in and out perfectly... but that is useless.
I found the answer. This has been updated for Django 1.5.1. The code below works.
#Ajax_Views.py
from django.contrib.auth.models import User
from django.contrib.auth import authenticate, login, logout
from django.http import HttpRequest
from django.conf.urls import url
from django.utils import simplejson
from tastypie.http import HttpUnauthorized, HttpForbidden
from tastypie.utils import trailing_slash
from tastypie.resources import ModelResource
from tastypie.authorization import Authorization
class UserResource(ModelResource):
class Meta:
queryset = User.objects.all()
fields = ['first_name', 'last_name', 'email']
allowed_methods = ['get', 'post']
resource_name = 'user'
authorization = Authorization()
def prepend_urls(self):
return [
url(r"^(?P<resource_name>%s)/login%s$" %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('login'), name="api_login"),
url(r'^(?P<resource_name>%s)/logout%s$' %
(self._meta.resource_name, trailing_slash()),
self.wrap_view('logout'), name='api_logout'),
]
def login(self, request, **kwargs):
self.method_check(request, allowed=['post', 'ajax'])
data = self.deserialize(request, request.body, format=request.META.get('CONTENT_TYPE', 'application/json'))
username = data.get('username', '')
password = data.get('password', '')
user = authenticate(username=username, password=password)
if user:
if user.is_active:
login(request, user)
return self.create_response(request, {
'success': True
})
else:
return self.create_response(request, {
'success': False,
'reason': 'disabled',
}, HttpForbidden )
else:
return self.create_response(request, {
'success': False,
'reason': 'incorrect',
}, HttpUnauthorized )
def logout(self, request, **kwargs):
self.method_check(request, allowed=['get'])
if request.user and request.user.is_authenticated():
logout(request)
return self.create_response(request, { 'success': True })
else:
return self.create_response(request, { 'success': False }, HttpUnauthorized)
#Jquery/Ajax
$('#send').click(function(e){
e.preventDefault();
data = {
"username": $('#username').val(),
"password": $('#password').val()
};
$.ajax({
type: "POST",
url: "http://127.0.0.1:8000/api/user/login/",
data: JSON.stringify(data),
dataType: "json",
contentType: "application/json",
success: function(data) {console.log(data)},
error: function (rs, e) {console.debug(rs)}
});
});
#The HTML
<input type='text' id='username' />
<input type='password' id='password'/>
<input type='submit' id='send' class='btn' href='#'>Send</a>

I'm trying to build out a front end in Backbone for a Django 1.5 app.
I understand the Tastypie stuff and have been able to get that working, but I'm not sure how the page knows if the user is logged in or not when the page is first visited. I am using Session storage - is this incompatible with a JavaScript front end? Do I need to manually store the CSRF token and delete it after the user logs in, am I forced to use Django's (non-Ajax) for login/logout and then redirect to a protected, django served page with the JavaScript app code?
I am serving the JavaScript from django now, to get the CSRF token.. So I think I'm on the right path.

Related

Extra information gets lost when updating user context

I've been fighting with this issue for days now and I just can't solve it. My app is built on React and Django Rest Framework. I'm authenticating users with JWT - when the user logs into the app, the React Auth context gets updated with some info about the tokens and I include some extra information in the context (namely the user email and some profile information) so that I have it easily accessible.
How I am doing this is by overwriting TokenObtainPairSerializer from simplejwt:
class MyTokenObtainPairSerializer(TokenObtainPairSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
# Add custom claims
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
return token
On the frontend in my AuthContext.js:
const loginUser = async (email, password, firstLogin = false) => {
const response = await fetch(`${baseUrl}users/token/`, {
method: "POST",
headers: {
"Content-Type": "application/json",
"X-CSRFToken": csrfToken,
},
body: JSON.stringify({
email,
password,
}),
});
const data = await response.json();
if (response.status === 200) {
setAuthTokens(data);
setUser(jwt_decode(data.access));
localStorage.setItem("authTokens", JSON.stringify(data));
if (firstLogin) {
history.push("/profile");
} else {
history.push("/");
}
} else {
return response;
}
};
Up to this point it works perfectly fine and my ReactDevTools show me that the AuthContext has all the data:
Now to the issue - once the access token has expired, the next API call the user makes gets intercepted to update the token. I do this in my axiosInstance:
const useAxios = () => {
const { authTokens, setUser, setAuthTokens } = useContext(AuthContext);
const csrfToken = getCookie("csrftoken");
const axiosInstance = axios.create({
baseURL,
headers: {
Authorization: `Bearer ${authTokens?.access}`,
"X-CSRFToken": csrfToken,
"Content-Type": "application/json",
},
});
axiosInstance.interceptors.request.use(async (req) => {
const user = jwt_decode(authTokens.access);
const isExpired = dayjs.unix(user.exp).diff(dayjs()) < 1;
if (!isExpired) return req;
const response = await axios.post(`${baseURL}users/token/refresh/`, {
refresh: authTokens.refresh,
});
// need to add user info to context here
localStorage.setItem("authTokens", JSON.stringify(response.data));
setAuthTokens(response.data);
setUser(jwt_decode(response.data.access));
req.headers.Authorization = `Bearer ${response.data.access}`;
return req;
});
return axiosInstance;
};
export default useAxios;
But the extra information is not there. I tried to overwrite the TokenRefreshSerializer from jwt the same way as I did it with the TokenObtainPairSerializer but it just doesn't add the information
class MyTokenRefreshSerializer(TokenRefreshSerializer):
#classmethod
def get_token(cls, user):
token = super().get_token(user)
token["email"] = user.email
token["information"] = Profile.objects.get(user=user).information
print(token)
return token
It doesn't even print the token in my console but I have no clue what else I should try here.
Before anyone asks, yes I specified that the TokenRefreshView should use the custom serializer.
class MyTokenRefreshView(TokenRefreshView):
serializer_class = MyTokenRefreshSerializer
However, after a while of being logged into the application, the email and information key value pairs disappear from the context.
Any idea about how this can be solved will be much appreciated!

Django CSRF Token Missing Only in Production

I am getting a missing CSRF_Token error that only occurs in production mode on my server. However everything works great when I am running it from my computer terminal using the runserver command. I've read through many of the other questions pertaining to this with no luck. It seems that my case is slightly different than others, since it works locally but not in production.
I get the error when submitting an Ajax form that submits to the "submit" in views.py. Does anybody know what could be causing this? Also, looking at my cookies in Production mode, the CSRF_Token is not even there to begin with. Locally it is. Thanks for any help.
Here is my views.py
from django.shortcuts import render
from django.http import HttpResponse
def index(request):
return render(request, 'index.html')
def submit(request):
#Receive Request
inputone = request.POST['randominfo']
inputtwo = request.POST['randominfo2']
#Some more code here that setups response.
#Deleted since Im posting to StackOverflow
return response
Code Pertaining to the Ajax Submit
$(function () {
$.ajaxSetup({
headers: { "X-CSRFToken": getCookie("csrftoken") }
});
});
function getCookie(c_name)
{
if (document.cookie.length > 0)
{
c_start = document.cookie.indexOf(c_name + "=");
if (c_start != -1)
{
c_start = c_start + c_name.length + 1;
c_end = document.cookie.indexOf(";", c_start);
if (c_end == -1) c_end = document.cookie.length;
return unescape(document.cookie.substring(c_start,c_end));
}
}
return "";
}
function submitAjax(event){
$.ajax({
type:'POST',
url:'/submit/',
data:{
randominfo:document.getElementById('Random').innerHTML,
randominfo2:document.getElementById('Random2').innerHTML,
},
dateType: 'json',
success:function() {
# Url here
}
})
};
Solution that fixed this problem.
Adding "from django.views.decorators.csrf import ensure_csrf_cookie" in views.py and then "#ensure_csrf_cookie" above the view that returns the html file that contained the ajax form
The error ocurs because you are not setting the csrf token, to prevent this we have to check some details
First of all, you have to set the csrf token to your form, in your html you have to set some as follow:
<form id="id" name="form">
{% csrf_token %}
<!-- Form body here -->
</form>
Second the approach to set the csrf cookie to your request header is ok, i only suggest that instead you set your data field one by one, use method serialize of jquery
data: $("#your-form-id").serialize()
I would like to recommend you to read this post about ajax request with django that is very helpful
There are 2 things you can do:
1.) Submit a CSRF token in your ajax call. You have to use a getCookie() javascript function to get it. Luckily the django documentation has some code you can copy and paste.
javascript
$.ajax({
type:'POST',
url:'/submit/',
data:{
randominfo:document.getElementById('Random').innerHTML,
randominfo2:document.getElementById('Random2').innerHTML,
'csrfmiddlewaretoken': getCookie('csrftoken'), // add this
...
2.) Disable csrf for your /submit view. You can do this with a decorator. Note that this is less secure so make sure there's no confidential data.
views.py:
from django.views.decorators.csrf import csrf_exempt
...
#csrf_exempt
def your_submit_view(request):
#view code

Django with request in ajax

This is my request in ajax,
var req=$.ajax({
type: "POST",
url: '/create/travel', // or just url: "/my-url/path/"
data: {
csrfmiddlewaretoken: document.getElementsByName('csrfmiddlewaretoken')[0].value,
startPlace:nameStart,
startLng:lngStart,
startLat:latStart,
endPlace:nameEnd,
...
}
});
Then, it is my view
def createT(request):
if request.is_ajax():
#print(request.POST['board'])
print(Car.objects.get(number_plate=request.POST['number_plate']))
travel1=Travel.objects.create(
name=request.POST['name_travel'],
startPlace=request.POST['startPlace'],
...
)
return render_to_response('travel/meTravel.html',{},context_instance=RequestContext(request))
But django not go to the template, i dont know how i go to other view or open other page, other template,
Simply when i come to this view, i cannot open other view, single django stay in the same page. =$
Help me! Tanks ,
finally, I make a form and send the normal request, Tanks for all...
class TravelForm(ModelForm):
class Meta:
model = Travel
exclude=("responsible",)
widgets = {
'date': DateTimeWidget (attrs={'id':"date"},use_tz=True,bootstrap_version=3)
}
def __init__(self, user, *args, **kwargs):
super(TravelForm, self).__init__(*args, **kwargs)
self.fields['number_plate'].queryset = Car.objects.filter(proprietor=user)
and my view
def TravelCreate(request):
if request.method == "POST":
form = TravelForm(request.user.id, request.POST)
print("POST HERE-----------------=)")
print(form.is_valid())
if form.is_valid():
obj=form.save(commit=False)
obj.responsible = request.user
obj.save()
return HttpResponseRedirect('/TravelsMe')
else:
form = TravelForm(request.user.id)
return render_to_response('travel/travel_form.html', {'form':form}, context_instance=RequestContext(request))
And some code of ajax,

Ajax Django Login/Authentication without re-direct

I am trying to have an authentication set-up similar to that of StackOverflow, where the normal browsing is never affected unless there are some privileged actions which requires authentication (Do not bother users until then).
It should be as "Log In" if not logged in or "UserName" if logged in.
The relevant part of base.html (from fallr.net) (extended by index.html) looks like :
<script type="text/javascript">
//<![CDATA[
$(document).ready(function(){
var methods = {
forms : function(){
var login = function(){
var user = $(this).children('form').children('input[type="text"]').val();
var pass = $(this).children('form').children('input[type="password"]').val();
var dataString = '&username=' + $('input[name=username]').val() + '&password=' + $('input[name=password]').val();
if(user.length < 1 || pass.length < 1){
alert('Invalid!\nPlease fill all required forms');
} else {
alert('username: '+user+'\npassword: '+pass);
$.ajax({
type: "POST",
url: "/login",
dataType: "html",
data: {
username : user,
password : pass,
csrfmiddlewaretoken : '{{ csrf_token }}'
},
success: function(json){alert (json.server_response);},
error: function(xhr,errmsg,err) { alert(xhr.status + ": " + xhr.responseText); }
});
$.fallr('hide');
return false;
}
}
$.fallr('show', {
icon : 'secure',
width : '320px',
content : '<h4>Sign in</h4>'
+ '<form>'
+ '<input name="username" placeholder="username" type="text"/'+'>'
+ '<input name="password" placeholder="password" type="password"/'+'>'
+ '</form>',
buttons : {
button1 : {text: 'Submit', onclick: login},
button4 : {text: 'Cancel'}
}
});
}
};
//button trigger
$('a[href^="#fallr-"]').click(function(){
var id = $(this).attr('href').substring(7);
methods[id].apply(this,[this]);
return false;
});
// syntax highlighter
hljs.tabReplace = ' ';
hljs.initHighlightingOnLoad();
});
//]]>
</script>
The urls.py looks like :
from django.conf.urls import patterns, include, url
#from triplanner.views import *
# Uncomment the next two lines to enable the admin:
# from django.contrib import admin
# admin.autodiscover()
urlpatterns = patterns('',
url(r'^$', main_page),
url(r'^login$',ajax_login),
url(r'^login/$','django.contrib.auth.views.login'),
url(r'^logout/$', logout_page),
# our application page
url(r'^account/',include('tripapp.urls')),
)
Also, '^login/$' is the previous implementation for learning which I want to replace with Ajax login.
And my views.py:
# -*- coding: utf-8 -*-
from django.contrib.auth import logout
from django.http import HttpResponseRedirect
from django.shortcuts import render_to_response
from django.contrib.auth import authenticate, login
#from django.http import HttpResponse
from django.template import RequestContext
#from django.utils import simplejson
def main_page(request):
return render_to_response('index.html', context_instance=RequestContext(request))
def logout_page(request):
"""
Log users out and redirect them to the main page
"""
logout(request)
return HttpResponseRedirect('/')
def ajax_login(request):
"""
This view logs a user in using the POST data.
"""
if request.method == 'POST':
print request.POST['username']
username = request.POST['username']
password = request.POST['password']
print username
print password
user = authenticate(username=username, password=password)
if (not user is None) and (user.is_active):
login(request, user)
response_dict = {}
response_dict.update({'server_response': username})
#return HttpResponse(simplejson.dumps(response_dict),mimetype='applicaion/javascript')
return render_to_response('index.html',{'username' : user}, context_instance=RequestContext(request))
# Set Session Expiry to 0 if user clicks "Remember Me"
#if not request.POST.get('rem', None):
# request.session.set_expiry(0)
#data = username
else:
return render_to_response('index.html', context_instance=RequestContext(request))
I am getting a 403 Error like "[20/Aug/2013 00:29:20] "POST / HTTP/1.1" 403 2294"
UPDATE NUMBER 1:
With the changed urls.py, views.py and javascript I am able to get a 200 response, but it gives alert window saying undefined and alerting me "Prevent this page from creatng dialog boxes"
The approach I use is to have a Tastypie api layer and require authentication for the APIs. If the API call fails because of authentication, the client can request the user to log-in via the ajax login method.
You can log-in a user via ajax using this gist
So, it looks like your current problem is with this: alert (json.server_response);. You may want to look into changing your $.ajax dataType parameter to json.
To quote the docs:
The type of data that you're expecting back from the server. If none is specified, jQuery will try to infer it based on the MIME type of the response (an XML MIME type will yield XML, in 1.4 JSON will yield a JavaScript object, in 1.4 script will execute the script, and anything else will be returned as a string). The available types (and the result passed as the first argument to your success callback) are:...
"html": Returns HTML as plain text; included script tags are evaluated when inserted in the DOM.
"json": Evaluates the response as JSON and returns a JavaScript object. The JSON data is parsed in a strict manner; any malformed JSON is rejected and a parse error is thrown. As of jQuery 1.9, an empty response is also rejected; the server should return a response of null or {} instead. (See json.org for more information on proper JSON formatting.)

Ajax and Django render_to_response (How to render to response inside success function)

Currently I am trying to implement a login validation system. I am using ajax so that users can get a response without being redirected to another page. My ajax function sends email and password that user has inputted, and get message in callback function, which can be in three types: email, password, or the actual HttpResponse object. But I have no idea how to render the given http response object using ajax and jquery. Is location.href an option? I am pasting the code below.
In javascript:
function loginSubmit(email, password) {
var d= "email=" + email + "&password=" + password;
$.ajax({
url: "/login",
type: "POST",
dataType: "text",
data: d,
success: function(m) {
if (m == "email") {
$("#emailMessage").html("There is no account associated with this email address.");
$("#emailError").show();
$("#emailError").fadeOut(5000, function() {});
} else if (m == "password") {
$("#emailMessage").html("There is no account associated with this email address.");
$("#emailError").show();
$("#emailError").fadeOut(5000, function() {});
} else {
}
}
});
}
in view function:
def login(request):
json = request.POST
e = json['email']
p = json['password']
u = User.objects.filter(email=e)
if (len(u)):
up = User.objects.filter(email=e, password=p)
if (len(up)):
return render_to_response('profile.html', context_instance=RequestContext(request))
else:
data = "password"
c = RequestContext(request, {'result':data})
t = Template("{{result}}")
datatype=u"application/javascript"
return HttpResponse(t.render(c), datatype)
else:
data = "email"
c = RequestContext(request, {'result':data})
t = Template("{{result}}")
datatype=u"application/javascript"
return HttpResponse(t.render(c), datatype)
p.s. Currently I am using a dummy template and HttpResponse to send data to the ajax success callback function. Is there a more efficient way to accomplish this (send back json data)? I will wait for your replies guys!
from django.contrib.auth import authenticate, login as auth_login
def login(request):
# Use authentication framework to check user's credentials
# http://djangosnippets.org/snippets/1001/ for auth backend
user = authenticate(
email = request.POST['email'],
password = request.POST['password'], )
if user is not None:
# Use Auth framework to login user
auth_login(request, user)
return render_to_response('profile.html',
context_instance=RequestContext(request))
else:
# Return Access Denied
# Never return bad email/bad password. This is information leakage
# and helps hackers determine who uses your platform and their emails.
return HttpResponse("Failed: Bad username or password", status=403)
function loginSubmit(email, password) {
$.ajax({
url: "/login",
type: "POST",
data: {email:email, password:password},
success: function(data) {
var returned_html = $(data);
$("#target_profile_area").clear().append(returned_html);
},
error: function(jqXHR) {
if (jqXHR.statusCode == 403) {
$("#loginMessage").text("Your login details are incorrect");
} else {
$("#loginMessage").text("Error Contacting Server");
}
$("#loginError").show();
$("#loginError").fadeOut(5000, function() {});
}
});
}

Resources