Layers of Authentication
This is the third segment of the Django, jQuery & Ajax series, continuing where Custom Django Views for Happier Ajax left off. This time we're going to look at proper handling of authentication in Ajax applications.
The short answer to "How do I handle authentication in Ajax applications?" is the same way you do in every other application, but there is a long answer as well.
Because I appear to specialize in long answers to questions that usually only receive short answers, this seems right up my alley.
Two Layers of Authentication, and Two Flavors
When we think about authentication in Django, the first thing we might consider is presenting different parts of templates for authenticated and non-authenticated users.
{% if user.is_authenticated %}
<p> Hi there { { user.get_full_name }}. Welcome to Notes! </p>
{% for note in notes %}
<p>{ { note }}</p>
{% endfor %}
{% else %}
<p>
We don't take kindly to strangers around here.
<a href="/register/">Try registering!</a>
</p>
{% endif %}
So in this case we're authenticating to prevent unwanted data viewing.
{% if user.is_authenticated %}
<form type="POST" action="/create/">
<label for="title">Title</label>
<input type="text" name="title">
<input type="submit" value="create">
</form>
{% else %}
<p> Nothing to see here. </p>
{% endif %}
Now in this case you might think we are preventing data modification, but that isn't the case. If an attacker discovered the URL for the form, they could still manually POST data themselves.
What we're doing there is not to protect our system, but rather enhancing our user interface by hiding unavailable functionality. This is important because we don't want our UIs to mislead our users, but doesn't increase application security.
The third and final flavor, is to perform authentication at the view layer.
python create(request):
if request.user.is_authenticated():
return render_template("my_app/i_trust_you.html")
return render_template("my_app/no.html")
# or even more simply
from django.contrib.auth.decorations import login_required
@login_required
def create(request):
return render_template("my_app/i_trust_you.html")
Between these three scenarios, the layers and flavors of authentication overlap a bit, but we end up with a few important concepts to consider:
- For modification, we must perform authentication at the view layer.
- For presentation, we may perform authentication at either the view or template layer.
- Synchronizing the user interface with permissions does not provide security for your app, but does provide security from your rioting users triyng to stab you.
You can boil those ideas into two simple questions: should I expose this? (at the user interface layer) and should I permit this? (at the Api level).
Time to start the Notes inquisition.
Setting up User Authentication
But, first we have to break for a tea party. Well, maybe it isn't exactly a tea party, but we do have to setup account creation.
The first step is to open up settings.py
and verify that django.contrib.auth
is already
in the INSTALLED_APPS
list. If it isn't there,
go ahead and add it.
Second, we need to setup the urls for the login and logout
actions. Open up ajax_tut/urls.py
, and add these two
urls:
# Authentication Views
(r'^accounts/login/$', 'django.contrib.auth.views.login'),
(r'^accounts/logout/$', 'django.contrib.auth.views.logout'),
Then we need to create some simple templates.
From the project directory, ajax_tut/
, start by
making two folders:
mkdir templates
mkdir templates/registration
Then we need to create two simple templates. First
registration/login.html
:
{% extends "base.html" %}
{% block content %}
{% if form.errors %}
<p>Your username and password didn't match. Please try again.</p>
{% endif %}
<form method="post" action=".">
<table>
<tr><td>{ { form.username.label_tag }}</td><td>{ { form.username }}</td></tr>
<tr><td>{ { form.password.label_tag }}</td><td>{ { form.password }}</td></tr>
</table>
<input type="submit" value="login" />
<input type="hidden" name="next" value="{ { next }}" />
</form>
{% endblock %}
and second registration/logged_out.html
:
{% extends "base.html" %}
{% block content %}
<p> You have been logged out. </p>
{% endblock %}
Now that we have the templates created, we also need to
set the LOGIN_REDIRECT_URL
in our settings.py
file.
Open up ajax_tut/settings.py
and add this line:
LOGIN_REDIRECT_URL = u"/"
Then we'll modify our base template, base.html
to
display a login / logout link (and a navigation link
to return to the front page).
Right after the beginning of the <body>
tag,
insert this html:
<div class="navbar">
<a href="/">Home</a>
<span> | </span>
{% if user.is_authenticated %}
<a href="/accounts/logout/">Logout</a>
{% else %}
<a href="/accounts/login/">Login</a>
{% endif %}
</div>
Now if you go run the devel server, you can see that we can login, logout, and that the login|logout button is display the correct option.
Should I Expose This?
The first of the two questions we are going to answer is "Should I exposure this?"
In this case it means we'll look at our notes/note_list.html
and notes/note_detail.html
templates and hide editing capabilities
from users who are not logged in.
We'll start with the notes/note_list.html
template.
What we need to do is simply wrap the <div class="new">
div
and <script>
tags in template if statements so that they look
like:
{% if user.is_authenticated %}
<div class="new">
<h2> Create a new note. </h2>
<form method="post" action="/create/">
<label for="title">Title</label> <input type="text" name="title" id="title">
<label for="slug">Slug</label> <input type="text" name="slug" id="slug">
<input id="create" type="submit" value="create note">
</form>
</div>
{% endif %}
and this:
{% if user.is_authenticated %}
<script>
// lots of JavaScript here
</script>
{% endif %}
respectively.
Then go back to the app and view how the frontpage changes when you view it logged in and logged out.
Next we need to do the same for notes/note_detail.html
template.
First we'll exclude the JavaScript like we did before:
{% if user.is_authenticated %}
<script>
// lots more JavaScript
</script>
{% endif %}
Then we need to handle the displayed content as well. Because non-authenticated users can't make changes, they shouldn't be shown inputs or textfields. Instead we'll just display the content in a couple of <span>
s and a <p>
.
<div class="detail">
{% if user.is_authenticated %}
<form method="post" action="update/">
<div class="text">
<label for="title">Title</label>
<input type="text" name="title" id="title" value="{ { object.title }}">
<label for="slug">Slug</label>
<input type="text" name="slug" id="slug" value="{ { object.slug }}">
</div>
<textarea name="text" id="text">{ { object.text }}</textarea>
<input class="submit" type="submit" value="update note">
</form>
{% else %}
<span>Title: { { object.title }}</span>
<span>Slug: { { object.slug }}</span>
<p> { { object.text }} </p>
{% endif %}
</div>
Now run the app and play around a bit with logging in and out and looking at different notes (and the note listing).
Our UI now indicates to users what permissions are available, but we still have to actually enforce those permissions. On to the next section.
Should I Permit This?
The last step in adding authentication is to answer the second question: "Should I permit this?"
All four of our custom views allow creating or modifying data, so we have a pretty simple answer: no, no, no, no.
Accomplishing that involves five more lines of code in notes/views.py
.
First add this import at the top:
from django.contrib.auth.decorators import login_required
and then add that decorator to each of the views:
@login_required
def create_note(request):
# etc etc
@login_required
def ajax_create_note(request):
# etc etc
@login_required
def update_note(request):
# etc etc
@login_required
def ajax_update_note(request):
# etc etc
Save the application, and go ahead and test that the views don't work by
manually typing in the corresponding urls at /create/
, note/<slug>/update/
and so on.
Now our precious notes are actually secure.
Download
You can download the present state of the Git repository here.
Moving Onward
Although we haven't made a big deal about it, it's impressive to note that this little test application is now correctly operating in four different user scenarios: JavaScript no-auth, JavaScript auth, no-JavaScript no-auth, and no-JavaScript, auth.
When we design systems carefully, than we can focus only on the simple dichotomies (JavaScript versus no JavaScript, authenticated versus not authenticated), instead of having to handle the exponential blowout of dealing with the larger picture.
Hmm. Okay. Done pontificating.
With that this third entry in the Django, jQuery & Ajax series comes to a close. This was originally going to be the last entry in the series, but I had an idea I've been rolling around in my head that sounds both fun and helpful, so I decided to add one last hurrah.