django and angular.js with authentication - django

I have a web app that's already written in Django, and it is working quite well. I want to add a few views for angular as the plan is to move into that direction. One issue i'm facing is that some of my controllers require login, with django we normally use the #login_required decorator and everything is fine.
But with angular.js calling the controllers (for api's), the redirect is not happening. I'm assuming I'll have to somehow check if my django user is authenticated directly from angular side. Is there any explanation on how to confirm this on angular? I have been struggling with this and have read the following:
https://docs.angularjs.org/api/ng/service/$http
https://medium.com/#vince_prignano/angular-js-with-django-bde834dbd61e
$routeProvider not triggered after login redirect
http://www.django-rest-framework.org/api-guide/authentication/
Basically, I want to confirm, via Angular, that my user is logged in and if not, redirect them to the login page.
EDIT
I'm implementing a request interceptor as shown here:
Interceptor not working
However, in django #login_required it's returning the html of the redirecting page. Is there a way to get the URL and forward the user there?

Add resolve in your $routeProvider as:
$routeProvider
.when('/', {
templateUrl: '/views/main.html' })
.when('/admin', {
templateUrl: 'views/admin.html',
controller: 'AdminCtrl',
resolve: { loggedin: checkLoggedin } })
.when('/login', {
templateUrl: 'views/login.html',
controller: 'LoginCtrl' })
.otherwise({ redirectTo: '/' }); -
See more at: https://vickev.com/#!/article/authentication-in-single-page-applications-node-js-passportjs-angularjs

As mentioned in the previous answer, it would be best if your Django back-end can issue a 401 response when login is required. Right now it sounds like it's sending a 302, which you can still observe in the browser if you're making an XHR request. As you've found, using $http interceptors in Angular are a common way of looking for a 401 and sending the user to a login page.
I've taken a different approach: implement a service that abstracts this a bit, via a method called $user.get() - it makes a request to a known endpoint (in my case, /api/users/current) and rejects the returned promise if it sees a 401. In your case you could implement a rejection handler that uses window.location.href to send the user to your dedicated login page
Disclaimer: I work at Stormpath and we’ve spent a log of time thinking about this :) In my comments above I’m referring to our Stormpath Angular SDK - you can look at this library to see how I’ve solved this problem.

When defining my app i'm doing this:
$httpProvider.interceptors.push('errorInterceptor');
and The code for that is below:
app.factory('errorInterceptor', ['$q', '$rootScope', '$location',
function ($q, $rootScope, $location) {
return {
request: function (config) {
return config || $q.when(config);
},
requestError: function(request){
return $q.reject(request);
},
response: function (response) {
return response || $q.when(response);
},
responseError: function (response) {
if (response && response.status == 302 && response.data.url) {
window.location = response.data.url;
return;
}
return $q.reject(response);
}
};
}]);
Basically, we can't use login_required. We have to create a new decorator and provide a 302 status with a url.

If you're making APIs calls via AJAX back to the server, most likely you don't want the response to be redirected to the login page.
I have the same use case and made my own decorator to return a 403 when the user is not logged in. You may also use 401 instead if you like (I left it commented out).
I use 403 because 401 seems to imply WWW authentication.
from django.http import HttpResponse, HttpResponseForbidden
def logged_in_or_deny(func):
def check_request(request, *args, **kwargs):
if (request.user.is_authenticated()):
return func(request, *args, **kwargs)
else:
return HttpResponseForbidden('You must be logged in') # 403 Response
#return HttpResponse('You must be logged in', status=401) # 401 Response
return check_request
Then you would protect your view like this:
#logged_in_or_deny
def your_view(request):
# ... your code ...
return HttpResponse('Your normal response :)')
From Angular, it seems like you already know how to use an interceptor to check the response code and redirect the user accordingly. If you use the 403 or 401, you should check the against the response body just in case you respond with similar errors in the future.
While what you already have would work, 302 responses can be used for other reasons. It's better to have an explicit 4xx response rather than a 3xx redirection response since it'll be immediately obvious that it's a client side error (missing authentication).

You can use Http-Auth-Interceptor. Suppose A view requires login and a user makes a request to the view without login then #login_required decorator returns the response with response code 401. In auth interceptor intercept the status code if status code is 401 then redirect user to the login page.
example site: http://witoldsz.github.io/angular-http-auth/

Related

Jsonresponse in Django working in browser but not in PostMan or Angular

I am trying to send a JSON response from Django back-end to my angular front-end.
When I make the request I receive nothing in Postman or Angular but,opening the link in browser seems to be returning the correct result
My View is :
#api_view(['GET'])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When I open http://127.0.0.1:8000/accounts/get_username/ in browser I receive
{"username": "aditya8010"} on the web page.
But when i do a get request using POSTMAN I recieve
{
"username": ""
}
Same with Angular
this.http.get("http://127.0.0.1:8000/accounts/get_username/").subscribe((res) => {
this.username = JSON.stringify(res["username"])
console.log(this.username," ", res)
})
this code also prints an empty username string.
Another thing I have noticed is that my print statement in the view does print anything random I put in there when called from POSTMAN or Browser but when I use request.user.username it doesnt print anything when called by POSTMAN.
And each time the response code is 200
What am I doing wrong.
When you're sending the request you are not providing authentication credentials (i.e. something that identifies the user that is sending the request). How do you obtain this credentials?
You need to establish an authentication method. There are several but I recommend using Token authentication with knox package. Basically, you have an endpoint that logins the user with his username and password (normal json post request) and that endpoint returns a token. This token is what identifies the user. You send this token in the header of each request you need to be authenticated. That means you probably should include an IsAuthenticated permission for the view. In postman:
API view:
from rest_framework.permissions import IsAuthenticated
#api_view(['GET'])
#authentication_classes([IsAuthenticated])
def my_view(request):
print(request.user.username)
return JsonResponse({'username': request.user.username})
When it is in a browser, your login information is remembered in the session. When using postman or Angular, you need to provide the user's information in the request header manually.

Redirect From REST_API Response

I am using Angular and Django in my stack for a website, and after a user registers it emails them a link to activate their account. As of right now everything is working but the link takes the user to the Django rest framework page.
I've been returning responses in my app like this
data = {'success': True, 'message': 'An account has been activated.', 'response': {}}
return Response(data, status=status.HTTP_201_CREATED)
I am curious on how to redirect a user back to the login page which at the current moment would be a localhost page such as http://localhost:4200/#/authentication/login.
From research I have found methods like
return redirect('http://localhost:4200/#/authentication/login')
but I am wanting to keep my responses consistent. Is there a way to redirect a user while still using the rest api Response object?
After thinking about the comment posted by Muhammad Hassan, I realized I was thinking about this all wrong. Instead of sending a Django url I have change the email to send a URL to an Angular page I made and then I just sent an HTTP request from that page to the Django URL.

It is possible to get backend custom error message with d3.json?

I develop a django application where I have a d3js interactive tree. I use d3.json to get the tree data from the django view. I have create a decorator to check if user is logged to allowed the request or not. I have no problem when the user is logged but when the decorator return the jsonResponse with redirection url I have only the status error with the status description.
I read d3js and promise documentation but I'm not found a answer to return a jsonresponse with my custom response.
d3.json(d.data.url).then(function(data) {
// process data no problem
}, function(error){
console.log(error);
});
def check_user_permission_js(view_function):
#wraps(view_function)
def wrapper(request, *args, **kwargs):
if request.user.is_authenticated:
return view_function(request, *args, **kwargs)
messages.warning(request,
"Your account doesn't have access to this page "
+ "or your session has expired. "
+ "To proceed, please login with an account that has access.")
return JsonResponse({'not_authenticated': True,
'redirect_url': settings.LOGIN_URL,'data':[]}, status=500)
return wrapper
I think this question is not specific to d3 or frontend code.
If you are using d3 v5, d3.json is a thin wrapper around the browser fetch API documented here, which seems to be correctly returning a non 200 status code. If the problem is that you're not getting the custom fields provided in JsonResponse to appear in the response returned to the frontend, then the issue is related to usage of the Django JsonResponse method, it isn't something that using a different data fetching library would change.
I No I don't think the error comes from django because I already use JSONresponse with ajax requests that work very well. Exemple:
$.ajax({
url: "/browser/project/get-header",
type: "POST",
success: function(data) {
// process my data
},
error: function(jqXHR){
if (jqXHR.responseJSON.message){
writeMessages(jqXHR.responseJSON.message, 'error');
}
else{
writeMessages(
'An error occur: the sample list can not be loaded!',
'error');
}
}
});
My problem is related to fetch and how d3js returns the error with d3.json.
To temporarily solve my problem I use requests like the one above but in the end I would like to use only the functions of D3JS.

django-rest-framework returning 403 response on POST, PUT, DELETE despite AllowAny permissions

I'm using a django-oneall to allow social login session authentication on my site. While it isn't one of the suggested auth providers for django-rest-framework, rest_framework.authentication.SessionAuthentication uses django's default session authentication. so I thought it should be fairly simple to integrate.
On the permissions side, ultimately I'll use IsAdmin, but for development purposes, I just had it set to IsAuthenticated. When that returning 403s, I relaxed the permissions to AllowAny, but still no dice. Here's my rest framework config:
settings.py
REST_FRAMEWORK = {
'DEFAULT_AUTHENTICATION_CLASSES': (
'rest_framework.authentication.SessionAuthentication',
),
'DEFAULT_PERMISSION_CLASSES': (
'rest_framework.permissions.AllowAny',
# 'rest_framework.permissions.IsAuthenticated',
# 'rest_framework.permissions.IsAdminUser',
),
'PAGE_SIZE': 100,
'DEFAULT_FILTER_BACKENDS': (
'rest_framework.filters.DjangoFilterBackend',
),
}
EDIT:
I got this working based on the answer below. It turns out that rest_framework expects both the csrftoken cookie and a a X-CSRFToken Header of the same value, I setup my front-end code to send that header for all ajax requests and everything worked fine.
Django REST Framework returns status code 403 under a couple of relevant circumstances:
When you don't have the required permission level (e.g. making an API request as an unauthenticated user when DEFAULT_PERMISSION_CLASSES is ('rest_framework.permissions.IsAuthenticated',).
When you doing an unsafe request type (POST, PUT, PATCH or DELETE - a request that should have side effects), you are using rest_framework.authentication.SessionAuthentication and you've not included your CSRFToken in the requeset.
When you are doing an unsafe request type and the CSRFToken you've included is no longer valid.
I'm going to make a few demo requests against a test API to give an example of each to help you diagnose which issue you are having and show how to resolve it. I'll be using the requests library.
The test API
I set up a very simple DRF API with a single model, Life, that contains a single field (answer, with a default value of 42). Everything from here on out is pretty straight forward; I set up a ModelSerializer - LifeSerializer, a ModelViewSet - LifeViewSet, and a DefaultRouter on the /life URL route. I've configured DRF to require user's be authenticated to use the API and to use SessionAuthentication.
Hitting the API
import json
import requests
response = requests.get('http://localhost:8000/life/1/')
# prints (403, '{"detail":"Authentication credentials were not provided."}')
print response.status_code, response.content
my_session_id = 'mph3eugf0gh5hyzc8glvrt79r2sd6xu6'
cookies = {}
cookies['sessionid'] = my_session_id
response = requests.get('http://localhost:8000/life/1/',
cookies=cookies)
# prints (200, '{"id":1,"answer":42}')
print response.status_code, response.content
data = json.dumps({'answer': 24})
headers = {'content-type': 'application/json'}
response = requests.put('http://localhost:8000/life/1/',
data=data, headers=headers,
cookies=cookies)
# prints (403, '{"detail":"CSRF Failed: CSRF cookie not set."}')
print response.status_code, response.content
# Let's grab a valid csrftoken
html_response = requests.get('http://localhost:8000/life/1/',
headers={'accept': 'text/html'},
cookies=cookies)
cookies['csrftoken'] = html_response.cookies['csrftoken']
response = requests.put('http://localhost:8000/life/1/',
data=data, headers=headers,
cookies=cookies)
# prints (403, '{"detail":"CSRF Failed: CSRF token missing or incorrect."}')
print response.status_code, response.content
headers['X-CSRFToken'] = cookies['csrftoken']
response = requests.put('http://localhost:8000/life/1/',
data=data, headers=headers,
cookies=cookies)
# prints (200, '{"id":1,"answer":24}')
print response.status_code, response.content
Just for anyone that might find the same problem.
If you are using viewsets without routers like:
user_list = UserViewSet.as_view({'get': 'list'})
user_detail = UserViewSet.as_view({'get': 'retrieve'})
Django Rest framework will return 403 unless you define permission_classes at a class level:
class UserViewSet(viewsets.ModelViewSet):
"""
A viewset for viewing and editing user instances.
"""
permission_classes= YourPermisionClass
Hope it helps!
For completeness sake, there is one more circumstance under which DRF returns code 403: if you forget to add as_view() to the view declaration in your urls.py file. Just happened to me, and I spent hours until I found where the issue was, so maybe this addition can save some time for someone.
For those that aren't able to even access their csrftoken from Javascript:
In my case I wasn't able to get the csrftoken from my Javascript code to be able to set it in my ajax POST. It always printed null. I finally discovered that the django CSRF_COOKIE_HTTPONLY environment variable was set to True.
From the Django Documentation
CSRF_COOKIE_HTTPONLY: If this is set to True, client-side JavaScript will not be able to access the CSRF cookie."
Changing CSRF_COOKIE_HTTPONLY to False allowed me to finally get the csrftoken.
https://docs.djangoproject.com/en/4.1/ref/settings/#std-setting-CSRF_COOKIE_HTTPONLY
One more situation that someone may find is that you get a 403 error on an AllowAny route when you pass an token as null in the "Authorization" header in your request. For example, you may want to allow anyone to use the route but also want to know if the person that used the route is an authenticated user.
E.g.
if (token) {
headers = {
"Content-Type": "application/json",
"Authorization": "Token " + token
}
} else {
headers = {
"Content-Type": "application/json"
}
}

Cookies not working with an AJAX call from jQuery to Django

I have a Django site using a 5-star rating system for voting (I use django-ratings) and I would like to store the votings of the users with AJAX calls.
On the client side I have a JavaScript function sending a GET request to a URL:
$.ajax({
url: url,
success: function(data) {
alert('Load was performed.');
}
});
On the server side I have code setting the cookie:
def vote(request, slug, rating):
# Some irrelevant code...
response = HttpResponse('Vote changed.')
response.set_cookie('vote', 123456)
return response
The problem is that the cookie is never set in the browser.
What I am doing wrong?
Thanks!
Are sure that your problem is about Cross-site request forgery protection? most ajax requests rejected django by that. Don't you have any error messages?