domdelorenzo / django_auth

Geek Repo:Geek Repo

Github PK Tool:Github PK Tool

General Assembly Logo

Authentication in Django

Authentication comes built in to Django, so there's no need for something like Passport! The built-in authentication system will handle user accounts, groups, and permissions. There are also some view-helpers for common authentication related forms. The default configuration will work great for most projects, but Django's authentication system is also flexible and adaptable for larger projects.

Objectives

By the end of this, developers should be able to:

  • Implement Authentication in a Django project
  • Make some routes require login

Introduction

The Django authentication system handles both authentication and authorization. What's the difference?

  • Authentication: verifying a user is who they say they are
  • Authorization: determining what a user is allowed to do

We'll start by integrating Authentication and include Authorization at the end of the lesson.

Authentication

Django's authentication system is very flexible, giving you a range of implementation options. For instance, you can:

  • Use their off-the-shelf authentication system, which may work for quick prototypes and hackathons
  • Write your own User model and implement an entirely custom authentication system
  • Use the collection of methods and forms provided to implement a mostly custom authentication system, as a good middle ground

We'll start with the off-the-shelf system and then modify it so that we can use custom templates.

Review: Setting up an App in a Django Project

We're going to add Authentication to the Tunr application we built in earlier lessons.

If you want to start from a clean slate, here's the application up through the Django Views lesson you can use as the starter code for this lesson.

Getting Started

Short Checklist:

  • Activate virtual env
  • Create new app
  • Add app to settings.py
  • Create a user through the Django shell

We need to start by creating a new app inside our Django project for authentication. Generally, each app should have a single function or cover a single piece of functionality for our project.

pipenv run django-admin startapp accounts

OR, if we are already in our virtual environment shell (meaning you ran pipenv shell before in your current terminal):

python3 manage.py startapp accounts

NOTE: we can't call it auth, because that's the name of the app where Django keeps all the authentication functionality (we can't have two apps with the same name).

Once our app is created, we need to:

  1. Add it to our list of INSTALLED_APPS in settings.py

    INSTALLED_APPS = [
     'django.contrib.admin',
     'django.contrib.auth',
     'django.contrib.contenttypes',
     'django.contrib.sessions',
     'django.contrib.messages',
     'django.contrib.staticfiles',
     'tunr',
     'django_extensions',
    + 'accounts',
    ]
  2. Add LOGIN_REDIRECT_URL = '/' to settings.py

    • The default LOGIN_REDIRECT_URL is /accounts/profile/, but we want to redirect the user to the homepage instead.
    • I add this line right above INSTALLED_APPS but the order does not matter.
  3. Include the accounts app urls in tunr_django/urls.py

    urlpatterns = [
     path('admin', admin.site.urls),
     path('', include('tunr.urls')),
    + path('', include('accounts.urls')),
    ]
  4. Create a urls.py file inside of the accounts app`

Log In and Log Out

Let's start by implementing log in and log out.

Django provides a set of views that we can used inside of 'django.contrib.auth.urls', all we need to do is set up our accounts/urls.py file to use it:

# accounts/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views

urlpatterns = [
    # Login / Log Out
    path('accounts/login/',
         auth_views.LoginView.as_view(template_name='accounts/login.html'),
         name='login'),
    path('accounts/logout/',
         auth_views.LogoutView.as_view(template_name='accounts/logout.html'),
         name='logout'),
]

We're creating two routes here, one for logging in and one for logging out. The auth_views variable holds all of the views provided by Django for handling authentication functionality. We're passing in an explicit reference to the template that we'd like to use (login.html and logout.html).

Our next step is to create those two templates:

  1. Make a templates/ directory inside of your accounts app (i.e. accounts/templates/)
  2. Create an accounts/ directory inside of the templates/ directory (i.e. accounts/templates/accounts)
  3. Create two html files: login.html and logout.html.

Here is the source code for the two html files you just created.

The login template:

<!-- accounts/templates/accounts/login.html -->
{% extends 'tunr/base.html' %} {% block content %} {% if form.errors %}
<p>Your username and password do not match. Please try again.</p>
{% endif %}

<form method="post" action="{% url 'login' %}">
  {% csrf_token %} {{form.as_p}}

  <input type="submit" value="login" />
</form>

{% endblock %}

The logged out template, which will be displayed to the user when they log out:

<!-- accounts/templates/accounts/logout.html -->
{% extends 'tunr/base.html' %} {% block content %}
<p>Logged out!</p>
<a href="{% url 'login' %}">Click here to login again.</a>
{% endblock %}

Testing the Views

Let's test out our views and templates. Make sure your app is running and visit /accounts/login/. You should see our login page! Try to log in with the user you created in the previous lesson. Alternatively, create a new user in the Django admin panel and try to sign in with this new user.

python3 manage.py createsuperuser

Let's update the header menu in tunr/templates/tunr/base.html:

<!-- tunr/templates/tunr/base.html -->
<!-- ... -->
<nav>
  <a href="{% url 'song_list' %}">Songs</a>
  <a href="/">Artists</a>
+   <div class="user-info">
+     {% if user.is_authenticated %} Welcome, {{ user.username }}
+     <a href="{% url 'logout' %}">Log out</a>
+     {% else %}
+     <a href="{% url 'login' %}">Login</a>
+     {% endif %}
+   </div>
</nav>
<!-- ... -->

We're updating the <nav> element in the base template so that it changes based on whether or not a user is logged in. If a user is logged in, they'll see a welcome message and a link to log out. If they are not logged in, they'll see links to log in or sign up.

Once you're able to log in, try logging out by visiting /accounts/logout/. You should see the logout template we just created.

Right now, there is no indication whether the user is logged in or not. We will fix that shortly!

Sign Up

Back in accounts/urls.py, we want to add a view and url for users to sign up and create an account. Django provides most of what we'll need to implement authentication, but generally assumes that you'll create users through the Admin panel.

That's not helpful for us, we want users to be able to create accounts on their own. To do so, we'll need to implement a custom view.

Inside accounts/urls.py, add the following route:

# accounts/urls.py
from django.urls import path
from django.contrib.auth import views as auth_views
+from . import views


urlpatterns = [
    # Login / Log Out
    path('accounts/login',
         auth_views.LoginView.as_view(template_name='accounts/login.html'),
         name='login'),
    path('accounts/logout',
         auth_views.LogoutView.as_view(template_name='accounts/logout.html'),
         name='logout'),
+    # Sign Up
+    path('accounts/signup', views.sign_up, name="signup"),
]

We're adding a route for accounts/signup that will use the sign_up view we're about to create.

Navigate to accounts/views.py:

from django.shortcuts import render, redirect
from django.contrib.auth import login, authenticate
from django.contrib.auth.forms import UserCreationForm

# Create your views here.


def sign_up(request):
    if request.method == 'POST':
        form = UserCreationForm(request.POST)
        if form.is_valid():
            user = form.save()
            login(request, user)
            return redirect('artist_list')
    else:
        form = UserCreationForm()
    return render(request, 'accounts/signup.html', {'form': form})

Take 5 minutes to read through this code and understand what it does. Write down any questions you have about this code block and we will discuss them when 5 minutes is up.

We have our sign up view in place, now we need to implement the template. Create a new file at accounts/templates/accounts/signup.html and add the following:

{% extends "tunr/base.html" %} {% block content %}

<form method="post" action="{% url 'signup' %}">
  {% csrf_token %} {{form.as_p}}

  <input type="submit" value="Sign Up" />
</form>

{% endblock %}

Let's add a link to Sign Up to the header in turn/base.html:

<!-- tunr/templates/tunr/base.html -->
<!-- ... -->
<nav>
  <a href="{% url 'song_list' %}">Songs</a>
  <a href="/">Artists</a>
  <div class="user-info">
    {% if user.is_authenticated %} Welcome, {{ user.username }}
    <a href="{% url 'logout' %}">Log Out</a>
    {% else %}
    <a href="{% url 'login' %}">Login</a>
+    <a href="{% url 'signup' %}">Signup</a>
    {% endif %}
  </div>
</nav>
<!-- ... -->

Give it a shot! You should be able to sign up as a new user and sign in and out as that user.

Authorization

Now that users can sign up for our application and sign in and out of the account they've created, let's make it so that some routes require being authorized (signed in) in order to visit them.

Django makes this relatively straightforward for us.

Requiring Authentication

To make a view require a user be authenticated, we just need to import the login_required decorator that Django provides. Add the following to the top of tunr/views.py to import the login_required decorator:

# tunr/views.py
from django.contrib.auth.decorators import login_required

What's a decorator? A decorator is a function called on a function in order to change it's behavior. In this case, it adds an if statement to each function: if the user is logged in, continue with the view logic, otherwise redirect them to the login page. The syntax looks like this:

+@login_required
def artist_create(request):
    if request.method == 'POST':
        form = ArtistForm(request.POST)
        if form.is_valid():
            artist = form.save()
            return redirect('artist_detail', pk=artist.pk)
    else:
        form = ArtistForm()
    return render(request, 'tunr/artist_form.html', {'form': form})

Go ahead and add the decorator (@login_required) to every create, update, and delete path.

Now when you try to visit any of the create/edit forms, you should be redirected to the login page. (Unless you're logged in, which is the point!)

Updating our Views

We're almost finished - one last finishing touch!

When you get redirected to the login page, two things happen:

  1. You don't see a message explaining why you were sent to the login page
  2. When you login, you get redirected back to the homepage (rather than the page you were trying to visit in the first place)

These are relatively small issues but they're also small and easy to fix, so let's fix them. We're going to update both login.html and signup.html.

First, make the following additions to login.html:

{% extends "tunr/base.html" %}

{% block content %}

  {% if form.errors %}
    <p>Your username and password didn't match. Please try again.</p>
  {% endif %}

+  {% if next %}
+      <p>Please login to see this page.</p>
+  {% endif %}

  <form method="post" action="{% url 'login' %}">
    {% csrf_token %}
    {{form.as_p}}

    <input type="submit" value="login" />
+  <input type="hidden" name="next" value="{{ next }}" />
  </form>
{% endblock %}

Take a minute to read through and understand these additions before moving on.

Next, make the following addition to signup.html:

{% extends "tunr/base.html" %}

{% block content %}

  <form method="post" action="{% url 'signup' %}">
      {% csrf_token %}
      {{form.as_p}}

      <input type="submit" value="Sign Up" />
+      <input type="hidden" name="next" value="{{ next }}" />
  </form>

{% endblock %}

We're all set! We've added basic authentication, so that users can sign up for an account in our application and then log in and out of the account they create.

Django's authentication system is very powerful and we've only scratched the surface here. A number of things that are normally very tricky to implement are made very simple, like creating groups of users and assigning users different permissions. For more, check out the documentation in the Additional Resources section!

Social Authentication

Now that we have a basic authentication setup in place, let's expand on it so that users can sign in using their favorite social media app.

Social Authentication is using social media platforms (like Facebook and Twitter) for your authentication. For the remainder of class, work on getting social authentication working in tunr using social-auth-app-django.

Use the documentation or this walkthrough.

Important Note:

  • To install a package, rather than running pip install <package-name> like most tutorials out there will tell you to do, run: pipenv install <package-name>.

  • Run manage.py commands using python3: python3 manage.py <command>

Additional Resources

  1. All content is licensed under a CC­BY­NC­SA 4.0 license.
  2. All software code is licensed under GNU GPLv3. For commercial use or alternative licensing, please contact legal@ga.co.

About