React Native & Django authentication - Part 1

These services scale wonderfully and are fully managed by the provider, but sometimes the provided user model does not offer the required flexibility or you simply don’t like passing state between the service and your backend.

If this is the case, you’re in the right place.

NOTE: This post assumes you have basic knowledge of both Django and React Native.

Let’s get to the point.

1. Setting up Django with Rest-Framework

1.1 Installing the required packages

In your terminal, create and activate a python3 virtual environment:

pip install virtualenv virtualenvwrappermkdir django-rn-auth && cd django-rn-authmkvirtualenv -p $(which python3) django-rn-auth

And install the required python packages:

pip install django djangorestframework

Once you’ve installed the required packages, simply create a project, let’s call it backend:

django-admin startproject backendcd backend

and the API app:

django-admin startapp api

At this point your directory structure should look like this:

../backend    ├── api    │   ├── admin.py    │   ├── apps.py    │   ├── __init__.py    │   ├── migrations    │   │   └── __init__.py    │   ├── models.py    │   ├── tests.py    │   └── views.py    ├── backend    │   ├── __init__.py    │   ├── settings.py    │   ├── urls.py    │   └── wsgi.py    ├── db.sqlite3    └── manage.py

1.2 Configuring the application settings

Now let’s modify the settings.py file to start using the Django Rest Framework, we’ll stick to most of the defaults for simplicity, but the following need to be added/modified:

NOTE: These settings are appropriate for development only, do not use this in production!

# backend/settings.pyALLOWED_HOSTS = ['*']INSTALLED_APPS = [    ...    'rest_framework',    'rest_framework.authtoken',]REST_FRAMEWORK = {    'DEFAULT_AUTHENTICATION_CLASSES': (        'rest_framework.authentication.TokenAuthentication',    ),    'DEFAULT_PERMISSION_CLASSES': (        'rest_framework.permissions.IsAuthenticated',    )}

Now that the application is ready, we must run the initial migration and create an admin user:

python manage.py migratepython manage.py createsuperuser

You can now navigate to http://127.0.0.1:8000/admin and login.

1.3 Creating our authentication API

In order to allow users to access our API, we will need 3 views, registrationlogin and logout

The workflow is fairly simple, upon registration the React Native app will genereate a POST request to the registration endpoint with the new user data (username/email and password) and if properly validated, the endpoint will create a new user and an API token, that can subsequently be used to authenticate requests.

On login, the user credentials will be sent via a POST request and the token will be generated and returned if the user had previously logged out, else the existing token will be returned.

Lastly, the logout endpoint will consist of a GET request, when the request is authenticated, our backend will delete the API token corresponding to the user and return a 200 status code. If this succeeds we will need to route back our user to the login screen on the app.

1.3.1 Setting up the serializers

Serializers are a way of transforming your django models into a standard format, in this case JSON, that can be transformed back to a model object on request.

We will begin by serializing the default User model that Django provides.

Create a file name serializers.py in your backend/api/ directory.

# backend/api/serializers.pyfrom django.contrib.auth import get_user_modelfrom rest_framework import serializersclass CreateUserSerializer(serializers.ModelSerializer):    username = serializers.CharField()    password = serializers.CharField(write_only=True,                                     style={'input_type': 'password'})    class Meta:        model = get_user_model()        fields = ('username', 'password', 'first_name', 'last_name')        write_only_fields = ('password')        read_only_fields = ('is_staff', 'is_superuser', 'is_active',)    def create(self, validated_data):        user = super(CreateUserSerializer, self).create(validated_data)        user.set_password(validated_data['password'])        user.save()        return user

1.3.2 Setting up the views

# backend/api/views.pyfrom django.contrib.auth import get_user_modelfrom rest_framework.generics import CreateAPIViewfrom rest_framework.permissions import AllowAnyfrom rest_framework.response import Responsefrom rest_framework.authtoken.models import Tokenfrom rest_framework import statusfrom rest_framework.views import APIViewfrom api.serializers import CreateUserSerializerclass CreateUserAPIView(CreateAPIView):    serializer_class = CreateUserSerializer    permission_classes = [AllowAny]    def create(self, request, *args, **kwargs):        serializer = self.get_serializer(data=request.data)        serializer.is_valid(raise_exception=True)        self.perform_create(serializer)        headers = self.get_success_headers(serializer.data)        # We create a token than will be used for future auth        token = Token.objects.create(user=serializer.instance)        token_data = {"token": token.key}        return Response(            {**serializer.data, **token_data},            status=status.HTTP_201_CREATED,            headers=headers        )class LogoutUserAPIView(APIView):    queryset = get_user_model().objects.all()    def get(self, request, format=None):        # simply delete the token to force a login        request.user.auth_token.delete()        return Response(status=status.HTTP_200_OK)

1.3.3 Adding the API URLs

Create a urls.py file inside the api directory and add the following contents:

# backend/api/urls.pyfrom django.conf.urls import urlfrom rest_framework.authtoken.views import obtain_auth_tokenfrom .views import CreateUserAPIView, LogoutUserAPIViewurlpatterns = [    url(r'^auth/login/$',        obtain_auth_token,        name='auth_user_login'),    url(r'^auth/register/$',        CreateUserAPIView.as_view(),        name='auth_user_create'),    url(r'^auth/logout/$',        LogoutUserAPIView.as_view(),        name='auth_user_logout')]

Make sure you import these URLs in the project wide backend/backend/urls.py by adding the following to the urlpatterns:

# backend/backend/urls.py...from django.urls import path, includefrom django.conf.urls import urlurlpatterns = [    ...    url(r'api/', include('api.urls')),]

If you now run the server:

python manage.py runserver

And navigate to the http://127.0.0.1:8000/api/auth/login/ endpoint on the browser, you should be greeted with the following message:

{"detail" : "Method \"GET\" not allowed."}

Not to worry, we haven’t authenticated our request as the endpoint only accepts a POST request with the user credentials.

Open up a new terminal window and authenticate as follows (replacing the fields between <>). The output should be the auth token:

curl -X POST -d "username=<YOUR_USERNAME>&password=<YOUR_PASSWORD>" http://127.0.0.1:8000/api/auth/login/# Output-> {"token":"a64824a0dca3cf8f1c4fc0ccd4eccb635a001346"}

You can subsequently use this token to authenticate requests as follows on other endpoints:

curl -X GET http://127.0.0.1:8000/api/mynewendpoint/ -H 'Authorization: Token <YOUR_TOKEN>'

Let’s test the registration and logout endpoints:

# Registrationcurl -X POST -d "username=<NEW_USER>&password=<PASSWORD>" http://127.0.0.1:8000/api/auth/register/-> {"username":"testuser","token":"228c77007621a0d479cb7a8db60925e644e5d7fa"}# Logoutcurl -X GET http://127.0.0.1:8000/api/auth/logout/ -H 'Authorization: Token <YOUR_TOKEN>'

In part 2 we will focus on building the React Native application and biding it to our custom authentication backend.

All the code for this project can be found in this repo

Post a Comment

Previous Post Next Post