Bonus 2 -- Django DRF + Authentication

NOTE: If you are not signed up to my Django REST Framework email course, this might not make a whole lot of sense to you. Sign up my email course at the bottom of this post.


Now, we get to User Authentication. User Authentication is extremely important when you are creating a REST API. How does it work? Is it safe? Teach me how to do it!!

There are many ways to authenticate using Django REST Framework.

  1. Django Session Authentication
  2. Basic Authentication
  3. Token Authentication
  4. Custom Authentication
  5. OAuth Authentication

In this bonus lesson, we are going implement Basic Authentication. Don’t worry, I’m going to go through all the other authentications in the future. I think they are ALL worth understanding and knowing how to implement!

Is RESTful User Authentication Safe?

The short answer is “yes!”. The longer answer is, “sure, if you use HTTPS”.

There is not much different in REST based User Authentication than there is with normal HTTP Requests. Actually, if you remember an earlier lesson, they are the SAME.

Any request where you are passing around a user password, should be encrypted with HTTPS at the very least!

Fair? Does this make sense? Think of REST based User Authentication and REST APIs in generally simply a HTTP Request without a HTML form. Got it?

Create a New User Serializer

Just as in other lessons, we will start with creating our Serializer. You should be somewhat comfortable with this already:

polls/serializers.py

# ... other imports ...

from django.contrib.auth.models import User

# ... other serializers ...

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'email', 'password')
        extra_kwargs = {'password': {'write_only': True}}

Unlike other Serializers, we have some additional attributes: fields and extra_kwargs. What do these do?

  • fields is an attribute that let’s you control which fields will be returned. As you may or may not know, Django’s User model has a ton of fields. For our purposes, we only want to use those 3.
  • extra_kwargs is an attribute that allows us to control more stuff about the fields that we want to use. In this example, we are saying, “we want our password field to be write only. We don’t want to actually SHOW the password field.”

Note: If you use extra_kwargs, the field you are using in must also be included in the fields attribute.

Let’s create some views!

Create Some Views

Now, we need some views. Let’s create two. The first, will be a view where we can actually create a new user.

polls/api_views.py

class UserCreate(generics.CreateAPIView):
    """
    Create a User
    """
    serializer_class = UserSerializer
    authentication_classes = ()
    permission_classes = ()

What are THOSE attributes? Those tell DRF that we WANT to allow unauthenticated users to our UserCreate view so that unauthenticated users can create new users.

  • authentication_classes = () means that this view shouldn’t be beholden to any authentication schemes.
  • permission_classes = () means that this view shouldn’t be beholden to any special permissions.

So, how do we make sure that our user is authenticated for all other endpoints without having to go through all of the endpoints and adding authentication_classes and permission_classes attributes?

Simple, we go into settings.py and add default authentication classes and default permission classes. Let’s look at this now:

settings.py

# ... all other settings
REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'rest_framework.authentication.BasicAuthentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

Now that we have default authentication and permission classes, any view / endpoint that doesn’t override authentication_classes and permission_classes will use the above defaults.

Now, let’s look at our UserDetail view. As the name suggestions, we just want this to show some User Details.

polls/api_views.py

class UserDetail(generics.RetrieveAPIView):
    """
    Retrieve a User
    """
    queryset = User.objects.all()
    serializer_class = UserSerializer

You should know what this does: generics.RetrieveAPIView will retrieve the User instance and display it.

Now, we need to create some URLs!

Create Some URLs

This is pretty easy, we have our views and our Serializers, now we need URLs to access our views.

polls/urls.py

from django.conf.urls import url
from django.conf import settings
from django.conf.urls.static import static

from . import views, api_views

urlpatterns = [
    # ... all other URLs ...
    url(r'^api/create_user/$', api_views.UserCreate.as_view()),
    url(r'^api/users/(?P<pk>[0-9]+)/$', api_views.UserDetail.as_view()),
] + static(settings.STATIC_URL, document_root=settings.STATIC_ROOT)

At this point, run the application. (python manage.py runserver)

Go to: http://localhost:8000/polls/. You should be asked for a username and password. SUCCESS!!

Now, go to http://localhost:8000/polls/api/create_user/ where you will be able to access. Create a user! SUCCESS!!

This Isn’t Going to Work! I Need to Create a Password Hash!!

Wait one second! This isn’t going to work!

If you look at the database, and you look at the user you created, you may have noticed, that the password isn’t hashed! What gives?

That’s because we need to create our user the Django way. Do you know how you do that? It looks like this:

user = User(
    email = "bob@example.com",
    username = "bob"
)
user.set_password("bob")
user.save()

Notice the set_password("bob") function. It takes in a string and will create and set a hashed version of that password.

Now, how do we do this in Django REST Framework?

We have to override a method in our Serializer class (UserSerializer). We’ll need to override the create method because that’s what we are doing, creating a user.

polls/serializers.py

class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'email', 'password')
        extra_kwargs = {'password': {'write_only': True}}

    def create(self, validated_data):
        user = User(
            email = validated_data["email"],
            username = validated_data["username"]
        )
        user.set_password(validated_data["password"])
        user.save()
        return user

The create method takes in validated_data which is a parameter of our request data. When creating a user we need: email, username, and password.

Now, go back to http://localhost:8000/polls/api/create_user/, create a new user, and look in the database again and you might see something like this: pbkdf2_sha256$20000$274pZgX0GnPS$cYmmC+i3m10XwT5mbCWSelSw4MAHDB8b8UxEw9C2s/Q=

Now, go back to the actual application: http://localhost:8000/polls. Sign in with your new username and password. SUCCESS!! Now you are authenticated and can use the application!

Homework

  1. Implement the above using the project from the last Bonus Lesson
  2. Check your work with mine (download my project here)
  3. Email me with any questions you might have. (me@chrisbartos.com)

This concludes our User Authentication Bonus Lesson!

I hope you learned something going through this course. Don’t worry, if you were upset that I didn’t cover more stuff in Django REST Framework, you now have the basics. You can create some pretty AMAZING apps with what you know now. Let’s recap:

In this course you learned:

  • How to create Serializers
  • How to create DRF Views
  • How to navigate the awesome GUI that you get with Django REST Framework
  • How to add JQuery and AngularJS to your Django Apps
  • How to add Basic Authentication to your REST API.

That kicks ass! You learned a ton!

Stay tuned because you are now subscribed to my mailing list where you’ll start receiving all kinds of awesome things from me! I try to write a blog post once per week with awesome information that will make you a Django pro!

Thanks again!

Chris