How to check user is authenticated properly in Django and Backbone.js - django

I want to build single page application using Backbone.js and Django.
For checking user is authenticated or not,
I wrote a method get_identity method in django side.
If request.user.is_authenticated is true it returns request.user.id otherwise it returns Http404
In backbone side, I defined a User model and periodically make ajax call to get_identity.
I think it is the most straightforward way to check user is authenticated or not.
For learning single page application, I want to do this operation more sensible and efficient than this way if it is possible.
So what is your advice about this? When I search Django+Backbone.js + User Authentication, I couldn't find any satisfactory result and I really wonder how people do this simple operation.
Any help or idea will be appreciated.
(By the way I tried to read cookie periodically but HttpOnly True flagged cookies are not reacheable in client side.)
Django views.py
def get_identity(request):
if not request.user.is_authenticated():
raise Http404
return HttpResponse(json.dumps({'identity':request.user.id}), mimetype="application/json")
Backbone.js side.
updateUser:function(){
var $self=this;
$.ajaxSetup({async:false});
$.get(
'/get_identity',
function(response){
// update model...
$self.user.id =response.identity;
//check user every five minutes...
$self.user.fetch({success: function() {
$self.user.set('is_authenticated',true);
setTimeout($self.updateUser, 1000*60*1);
}
},this);
}).fail(function(){
//clear model
$self.user.clear().set($self.user.defaults);
setTimeout($self.updateUser, 1000*60*1);
});
$.ajaxSetup({async:true});
}

I had var is_authenticated = {{request.user.is_authenticated}}; in my base.html
and used the global variable to check.
I'm in pursuit of better solution (because this breaks when you start caching).
But you might find it useful.

Related

How do I check if a user has entered the URL from another website in Django?

I want an effect to be applied when a user is entering my website. So therefore I want to check for when a user is coming from outside my website so the effect isnt getting applied when the user is surfing through different urls inside the website, but only when the user is coming from outside my website
You can't really check for where a user has come from specifically. You can check if the user has just arrived on your site by setting a session variable when they load one of your pages. You can check for it before you set it, and if they don't have it, then they have just arrived and you can apply your effect. There's some good examples of how sessions work here: https://developer.mozilla.org/en-US/docs/Learn/Server-side/Django/Sessions
There's a couple of ways to handle this. If you are using function based views, you can just create a separate util function and include it at the top of every page, eg,
utils.py
def first_visit(request):
"""returns the answer to the question 'first visit for session?'
make sure SESSION_EXPIRE_AT_BROWSER_CLOSE set to False in settings for persistance"""
if request.session['first_visit']:
#this is not the first session because the session variable is used.
return False
else:
#This is the first visit
...#do something
#set the session variable so you only do the above once
request.session[first_visit'] = True
return True
views.py
from utils.py import first_visit
def show_page(request):
first_visit = first_visit(request)
This approach gives you some control. For example, you may not want to run it on pages that require login, because you will already have run it on the login page.
Otherwise, the best approach depends on what will happen on the first visit. If you want just to update a template (eg, perhaps to show a message or run a script on th epage) you can use a context processor which gives you extra context for your templates. If you want to interrupt the request, perhaps to redirect it to a separate page, you can create a simple piece of middleware.
docs for middleware
docs for context processors
You may also be able to handle this entirely by javascript. This uses localStorage to store whether or not this is the user's first visit to the site and displays the loading area for 5 seconds if there is nothing in localStorage. You can include this in your base template so it runs on every page.
function showMain() {
document.getElementByID("loading").style.display = "none";
document.getElementByID("main").style.display = "block";
}
const secondVisit = localStorage.getItem("secondVisit");
if (!secondVisit) {
//show loading screen
document.getElementByID("loading").style.display = "block";
document.getElementByID("main").style.display = "none";
setTimeout(5000, showMain)
localStorage.setItem("secondVisit", "true" );
} else {
showMain()
}

django-channels: differentiate between different `AnonymousUser`s

Unfortunately I'm using django-channels channels 1.1.8, as I missed all the
updates to channels 2.0. Upgrading now is unrealistic as we've just
launched and this will take some time to figure out correctly.
Here's my problem:
I'm using the *message.user.id *to differentiate between authenticated
users that I need to send messages to. However, there are cases where I'll
need to send messages to un-authenticated users as well - and that message
depends on an external API call. I have done this in ws_connect():
#channel_session_user_from_http
def ws_connect(message):
# create group for user
if str(message.user) == "AnonymousUser":
user_group = "AnonymousUser" + str(uuid.uuid4())
else:
user_group = str(message.user.id)
print(f"user group is {user_group}")
Group(user_group).add(message.reply_channel)
Group(user_group).send({"accept": True})
message.channel_session['get_user'] = user_group
This is only the first part of the issue, basically I'm appending a random
string to each AnonymousUser instance. But I can't find a way to access
this string from the request object in a view, in order to determine who
I am sending the message to.
Is this even achievable? Right now I'm not able to access anything set in
the ws_connect in my view.
EDIT: Following kagronick's advice, I tried this:
#channel_session_user_from_http
def ws_connect(message):
# create group for user
if str(message.user) == "AnonymousUser":
user_group = "AnonymousUser" + str(uuid.uuid4())
else:
user_group = str(message.user.id)
Group(user_group).add(message.reply_channel)
Group(user_group).send({"accept": True})
message.channel_session['get_user'] = user_group
message.http_session['get_user'] = user_group
print(message.http_session['get_user'])
message.http_session.save()
However, http_session is None when user is AnonymousUser. Other decorators didn't help.
Yes you can save to the session and access it in the view. But you need to use the http_session and not the channel session. Use the #http_session decorator or #channel_and_http_session. You may need to call message.http_session.save() (I don't remember, I'm on Channels 2 now.). But after that you will be able to see the user's group in the view.
Also, using a group for this is kind of overkill. If the group will only ever have 1 user, put the reply_channel in the session and do something like Channel(request.session['reply_channel']).send() in the view. That way it doesn't need to look up the one user that is in the group and can send directly to the user.
If this solves your problem please mark it as accepted.
EDIT: unfortunately this only works locally but not in production. when AnonymousUser, message.http_sesssion doesn't exist.
user kagronick got me on the right track, where he pointed that message has an http_session attribute. However, it seems http_session is always None in ws_connect when user is AnonymousUser, which defeats our purpose.
I've solved it by checking if the user is Anonymous in the view, and if he is, which means he doesn't have a session (or at least channels can't see it), initialize one, and assign the key get_user the value "AnonymousUser" + str(uuid.uuid4()) (this way previously done in the consumer).
After I did this, every time ws_connect is called message will have an http_session attribute: Either the user ID when one is logged in, or AnonymousUser-uuid.uuid4().

Is there a django shorcut for required params in the view

I am developing a django app with a large amount of views. But every time I write a view I have to cater the possibility of params being not provided.. I am writing the same code again and again. I was wondering if there was a django shortcut that could do the job and return a standard error message if a param was not provided.. I want to do something like..
#required_params({'get': ['param1', 'param2'], 'post': ['param3', 'param4']})
def my_view(request):
# Do my stuff
A possible solution for this would be to make use of the 'decorator_from_middleware' functionality: https://docs.djangoproject.com/en/dev/ref/utils/#django.utils.decorators.decorator_from_middleware
It would allow you to process the request on a per view basis and assert that the parameters from the requet meet your criteria and if not return a standard error response.

forcing session expiry on browser close with django-social-auth

I am looking for a way to force a re-login when a user who has logged in using FB/Google onto my site closes the browser. I was reading https://django-social-auth.readthedocs.org/en/latest/configuration.html, and I don't think:
SOCIAL_AUTH_EXPIRATION = 'expires'
or
SOCIAL_AUTH_SESSION_EXPIRATION = True
really does what I am looking for. I tried to add a custom pipeline this way which sets expiry time to 0 as the last thing in the pipelines:
def expire_session_on_browser_close(backend, details, response, social_user, uid, user, request, *args, **kwargs):
request.session.set_expiry(0)
SOCIAL_AUTH_PIPELINE = (
'social_auth.backends.pipeline.social.social_auth_user',
#'social_auth.backends.pipeline.associate.associate_by_email',
'social_auth.backends.pipeline.user.get_username',
'social_auth.backends.pipeline.user.create_user',
'social_auth.backends.pipeline.social.associate_user',
'social_auth.backends.pipeline.social.load_extra_data',
'social_auth.backends.pipeline.user.update_user_details',
'useraccount.pipeline.expire_session_on_browser_close',
)
But it doesn't seem to take effect. Setting
SESSION_EXPIRE_AT_BROWSER_CLOSE = True
has no effect either.
On a similar note, my site also allows user to login "traditionally" and I am able to have
request.session.set_expiry(0)
do the trick there, and users are forced to login when they close the browsers. Just doesn't work with FB/Google logins.
Any thoughts?
Thanks!
Edit:
If I go and muck around with:
UserSocialAuthMixin::expiration_datetime()
from
db\base.py
and force it to return 0, my issue gets resolved.
But this is bad, bad hackery. Is there a better, more elegant way?
Thanks!
Oh man, I over-engineered to this to the Nth degree. All I needed to do was set:
SOCIAL_AUTH_SESSION_EXPIRATION=False
This way if the Provider's response contains 'expires' or whatever SOCIAL_AUTH_EXPIRATION contains, django-social-auth won't call set_expiry() on that parsed value.
Additionally, I also set the pipeline function (as seen in my original question) so that I could set my own expiry (0 in my case).

Ajax to login users...callback function not working

This is my second foray into Ajax and I'm not quite sure how to pull this off.
So I have a modal window that opens when an anonymous user attempts to perform a certain task. The window contains a user signup form that I then $.post to my Django login view. If username/password are valid, user is logged in an status code of 1 is returned as the response. IF not, a status of 0 is returned.
When I try to do it outside of js, it works. However, within my script, it fails. I think that it has to do with the response content_type and how I'm interpreting it. I'm not sure.
def login_async(request):
if request.method=='POST' and len(request.POST['username'])<20 and len(request.POST['password'])<20:
username=request.POST.get('username', '') #probably need to script tags here
password=request.POST.get('password', '')
user=auth.authenticate(username=username, password=password)
if user is not None:
auth.login(request,user)
status=1
response=HttpResponse()
response['Content_Type']="text/html"
response.write(status)
return response
else:
status=0
response=HttpResponse()
response['Content_Type']="text/html"
response.write(status)
return response
$('input#login').click(function(event){
$.post("/login_async/", {username:$('input[name=username]').val(), password:$('input[name=password]').val()}, //could also use $(this).serialize() here to capture all form inputs
function(data){
if(data==1){
$('#login').dialog("close");
}
});
});
What's the problem here? I initially tried to return the response as JSON but I couldn't figure out how to make serialize.serializers("json",status) work. I kept getting an error.
One last question...
If I get a valid status (user is signed in), that will influence the behavior of modal windows on the page. The modal windows open based on logged in status. If a user is signed in, one set of windows open on a click event, and vice versa. This toggle is dependent on Django context {% user.is_authenticated %}.
That context renders only once right, on page load? Can I do anything to communicate the status change to the modal windows that's secure from easy hacks?
It should be Content-Type. If that doesn't fix your problem, try adding a dataType parameter at the end of the $.post call.
$.post("/login_async/", {}, function(data){
// fancy stuff
}, "html");
Yeah I thought it over more and realized that I should just unbind the existing click event handlers on the modal windows and then bind new event handlers depending on the ajax response.
I love programming