When you know that your API will be used by third part apps, it is reasonable to prepare an authorization server using OAuth2 Framework.
Table of Contents
Installing Django
Common preparation steps: A folder and a Virtual Environment
mkdir rest_api && cd rest_api curl "https://raw.githubusercontent.com/github/gitignore/master/Python.gitignore" >> ./.gitignore git init && git add . && git commit -m 'initial commit' virtualenv -p python3 venv && source venv/bin/activate
Install Django
pip install django
Create a Django Project using django-admin (a collection of settings for an instance of Django, including database configuration, Django-specific options and application-specific settings):
django-admin.py startproject rest_api_project . && cd rest_api_project
Start a Django App using django-admin
django-admin.py startapp rest_api && cd ..
startapp creates a Django app directory structure for the given app name in the current directory or the given destination.
Add the newly created app in INSTALLED_APPS on project’s settings.py file
INSTALLED_APPS = [ #... 'rest_api_project.rest_api' ]
Remove SQLite from DATABASES on project’s settings.py file. Add your own MySQL Db.
DATABASES = { 'default': { 'ENGINE': 'django.db.backends.mysql', 'NAME': 'dbname', 'USER': 'user', 'PASSWORD': 'pass', 'HOST': '127.0.0.1', # Or an IP Address that your DB is hosted on 'PORT': '', }, }
Having a database like this is important because Django gives for free an Admin interface, in that interface you can manage your Models and Applications that you will enable to use your OAuth2 structure that you are creating here. You also will be able to manage tokens issued for users.
Install mysqlclient
pip install mysqlclient
Creating a CustomUser Model
This steps will enables us to create our own CustomUser as recommended here: Using a custom user model in Django
from django.contrib.auth.models import AbstractBaseUser, AbstractUser, BaseUserManager from django.db import models from datetime import datetime from django.utils import timezone class CustomUserManager(BaseUserManager): def create_user(self, email, date_of_birth, gender, device, password=None): """ Creates and saves a User with the given email, date of birth and password. """ if not email: raise ValueError('Users must have an email address') user = self.model( email=self.normalize_email(email), date_of_birth = date_of_birth, gender=gender, device=device ) user.set_password(password) user.save(using=self._db) return user def create_superuser(self, email, date_of_birth, gender='', device='', password=None): """ Creates and saves a superuser with the given email, date of birth and password. """ user = self.create_user( email, password=password, date_of_birth=date_of_birth, gender=gender, device=device ) user.is_admin = True user.save(using=self._db) return user class CustomUser(AbstractBaseUser): device = models.CharField(max_length=150, blank=True) gender = models.CharField(max_length=1, blank=True) username = models.CharField( max_length=150, unique=False, # validators=[username_validator], # error_messages={ # 'unique': _("A user with that username already exists."), # }, ) email = models.EmailField( verbose_name='email address', max_length=255, unique=True, ) date_of_birth = models.DateField() is_active = models.BooleanField(default=True) is_admin = models.BooleanField(default=False) is_superuser = models.BooleanField(default=False) first_name = models.CharField(max_length=30, blank=True) last_name = models.CharField(max_length=150, blank=True) date_joined = models.DateTimeField(default=timezone.now) objects = CustomUserManager() USERNAME_FIELD = 'email' REQUIRED_FIELDS = ['date_of_birth', 'gender'] def __str__(self): return self.email def has_perm(self, perm, obj=None): "Does the user have a specific permission?" # Simplest possible answer: Yes, always return True def has_module_perms(self, app_label): "Does the user have permissions to view the app `app_label`?" # Simplest possible answer: Yes, always return True @property def is_staff(self): "Is the user a member of staff?" # Simplest possible answer: All admins are staff return self.is_admin
Inform Django of this custom Model. Add this setting on settings.py
AUTH_USER_MODEL = 'rest_api.CustomUser'
Make this App Migrations
python manage.py makemigrations rest_api
Migrate models
python manage.py migrate
If everything goes well, you should see something like this in your console:
Operations to perform:
Apply all migrations: admin, auth, contenttypes, sessions
Running migrations:
Applying contenttypes.0001_initial… OK
Applying auth.0001_initial… OK
Applying admin.0001_initial… OK
Applying admin.0002_logentry_remove_auto_add… OK
Applying admin.0003_logentry_add_action_flag_choices… OK
Applying contenttypes.0002_remove_content_type_name… OK
Applying auth.0002_alter_permission_name_max_length… OK
Applying auth.0003_alter_user_email_max_length… OK
Applying auth.0004_alter_user_username_opts… OK
Applying auth.0005_alter_user_last_login_null… OK
Applying auth.0006_require_contenttypes_0002… OK
Applying auth.0007_alter_validators_add_error_messages… OK
Applying auth.0008_alter_user_username_max_length… OK
Applying auth.0009_alter_user_last_name_max_length… OK
Applying auth.0010_alter_group_name_max_length… OK
Applying auth.0011_update_proxy_permissions… OK
Applying sessions.0001_initial… OK
Setup Django’s Admin Interface access.
python manage.py createsuperuser
Set your username, e-mail, password.
Test your setup now
python manage.py runserver
Access the Admin Interface
Install djangorestframework, django-oauth-toolkit and django-cors-middleware
pip install djangorestframework django-oauth-toolkit django-cors-middleware
Add them in INSTALLED_APPS on project’s settings.py file
INSTALLED_APPS = [ #... 'rest_framework', 'oauth2_provider', 'corsheaders' ]
Run migrations for corsheaders (django-oauth-toolkit), oauth2_provider (django-cors-middleware)
python manage.py migrate
Edit the rest_api_project.urls.py and add the highlighted lines:
from django.conf.urls import url, include urlpatterns = [ path('admin/', admin.site.urls)), url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), ]
On project’s settings.py add CorsMiddleware in MIDDLEWARE. It should be placed as high as possible, especially before any middleware that can generate responses such as Django’s CommonMiddleware or Whitenoise’s WhiteNoiseMiddleware. If it is not before, it will not be able to add the CORS headers to these responses.
MIDDLEWARE = ( # ... 'corsheaders.middleware.CorsMiddleware', # ... )
Allow CORS requests from all domains (just for the scope of this tutorial):
#settings.py CORS_ORIGIN_ALLOW_ALL = True
Execute the server and login into the Admin and create an Application
python manage.py runserver
Access: http://localhost:8000/admin/login/?next=/admin/
Under DJANGO OAUTH TOOLKIT click on Applications and Add Application. Set Client Type to Confidential, Authorization grant type to Resource owner password based and give a name to this application that will be our Oauth’s client.
Define the default authentication classes on settings.py. Do this only if you want to apply it globally. To apply protection per view, add a permission_classes attribute in each class. Thats what i’m going to do, so i commented this setting.
# REST_FRAMEWORK = { # 'DEFAULT_AUTHENTICATION_CLASSES': ( # 'oauth2_provider.contrib.rest_framework.OAuth2Authentication', # ), # 'DEFAULT_PERMISSION_CLASSES': ( # 'rest_framework.permissions.IsAuthenticated', # ) # }
Create your custom views
# rest_api/views.py from rest_framework.views import APIView, Response class CustomView(APIView): def get(self, request, format=None): return Response("Some Get Response") def post(self, request, format=None): return Response("Some Post Response")
Create your list of urls for rest_api app – create this urls.py file inside rest_api_project.rest_api
touch rest_api_project/rest_api/urls.py
from django.conf.urls import url from rest_framework import routers from rest_api_project.rest_api.views import CustomView router = routers.DefaultRouter() urlpatterns = [ url(r'customview', CustomView.as_view()), ] urlpatterns += router.urls
Edit rest_api_project.urls to add the child urls highlighted
from django.contrib import admin from django.urls import path from django.conf import settings from django.conf.urls import url, include urlpatterns = [ url(r'^admin/', admin.site.urls), url(r'^o/', include('oauth2_provider.urls', namespace='oauth2_provider')), url(r'^api/', include('rest_api_project.rest_api.urls')), ]
Protecting Views
Add imports on this newly created views
from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope, OAuth2Authentication from rest_framework import viewsets, permissions
And for each viewClass add protection
# require token authentication authentication_classes = [OAuth2Authentication] # control permissions permission_classes = [permissions.IsAuthenticated, TokenHasReadWriteScope]
Test your setup now
python manage.py runserver
If everything is ok for now, you should see this result in your terminal:
Django version 3.0, using settings ‘rest_api_project.settings’
Starting development server at http://127.0.0.1:8000/
Quit the server with CONTROL-C.
Preparing Apache Server on Ubuntu Server
sudo apt update
sudo apt install apache2-dev python3-dev python3-pip libssl-dev mysql-server libmysqlclient-dev virtualenv
sudo apt-get install apache2 sudo chown ubuntu:root /var/www/ && cd /var/www
sudo mysql -u root -p CREATE DATABASE rest_api; use mysql; ALTER USER 'root'@'localhost' IDENTIFIED WITH mysql_native_password BY 'newpassword'; FLUSH PRIVILEGES; exit;
sudo nano /etc/apache2/sites-available/rest-api.conf
<VirtualHost *:80> ServerAdmin admin@rest_api DocumentRoot /var/www/rest_api ServerName rest_api Alias /static /var/www/rest_api/static <Directory /var/www/rest_api/static> Require all granted </Directory> # https://www.digitalocean.com/community/tutorials/how-to-serve-django-applications-with-apache-and-mod_wsgi-on-ubuntu-14-04 # Configure mod_wsgi for Django step 1 <Directory /var/www/rest_api/project> <Files wsgi.py> Require all granted </Files> </Directory> # Configure mod_wsgi for Django step 2 WSGIDaemonProcess api python-home=/var/www/rest_api/venv python-path=/var/www/rest_api WSGIProcessGroup api WSGIScriptAlias / /var/www/rest_api/rest_api_project/wsgi.py WSGIPassAuthorization On ErrorLog ${APACHE_LOG_DIR}/error.log CustomLog ${APACHE_LOG_DIR}/access.log combined </VirtualHost>
Enable Mod_Rewrite
sudo a2enmod rewrite && sudo systemctl restart apache2
wget https://codeload.github.com/GrahamDumpleton/mod_wsgi/tar.gz/4.6.5 # or the version you want
tar -xvf 4.6.5 && cd mod_wsgi-4.6.5 ./configure --with-python=/usr/bin/python3 --with-apxs=/usr/bin/apxs sudo make && sudo make install cd .. && sudo rm -rf mod_wsgi-4.6.5 && sudo rm -rf 4.6.5
sudo nano /etc/apache2/mods-available/wsgi.load
Paste this into the file:
LoadModule wsgi_module /usr/lib/apache2/modules/mod_wsgi.so
Enable the module
sudo a2enmod wsgi
Enable new site
sudo a2ensite rest-api
Disable default Apache site
sudo a2dissite 000-default.conf
Disable default Apache site
sudo systemctl restart apache2
Clone your project into this folder
git clone https://fernandorodriguespro@bitbucket.org/fernandorodriguespro/rest_api.git && cd rest_api
Create the venv on the server
rm -rf venv # remove if you left it on git virtualenv -p /usr/bin/python3 venv/ source venv/bin/activate
Install Python required modules
pip install django djangorestframework django-oauth-toolkit django-cors-middleware mysqlclient
Edit ALLOWED_HOSTS variable of settings.py
ALLOWED_HOSTS = ['*']
Edit DATABASES variable of settings.py
Set DEBUG variable of settings.py to FALSE
Set STATIC FILES on settings.py
mkdir static
STATIC_URL = '/static/' STATIC_ROOT = os.path.join(BASE_DIR, 'static') STATICFILES_DIRS = [ os.path.join(BASE_DIR, 'static'), ]
python manage.py collectstatic
sudo systemctl restart apache2
Execute migrations
python manage.py migrate
Setup Django’s Admin Interface access.
python manage.py createsuperuser
sudo systemctl restart apache2
References
The main doc for this installation process can be found here: Django OAuth Toolkit for Django Rest Framework.
https://docs.djangoproject.com/en/2.2/ref/django-admin/
https://www.django-rest-framework.org/api-guide/
SAntiago
April 10, 2020Your post i excellent project, thanks for your cooperation, could you sen me your project please? my mail is santiagomollinedo1@gmail.com thanks a lot