[PYTHON] [Study memo] OAuth2 integration between django projects

Introduction

This article is a very useful article to study the process of linking OAuth2 between two django projects created in the local environment. This is a summary with some notes added for myself, referring to projects-oauth2 /). Except for the changes in the URLconf and django.contrib.auth writing styles that accompany the django version upgrade, the original article is mostly diverted.

Target

Create two Django projects (test_provider, test_consumer) in your local environment and get (dummy) profile information with OAuth2

project overview

Source code

test_provider --Startup port 8000 --Hold user ID and hashed password --Provide an OAuth2 provider --Ask the user permission to provide information to test_provider -** Use django-oauth-toolkit **

test_consumer --Startup port 8001 --The first web page the user will see --After user authorization, make a request to test_provider and get information -Use ** django-allauth **

Directory structure

test-oauth
│
├── test_consumer
│   ├── templates
|   |   └──index.html
|   |
│   ├── test_consumer
|   |   ├── __init__.py
|   |   ├── settings.py
|   |   ├── urls.py
|   |   └── wsgi.py
|   |
|   ├── testprovider
|   |   ├── __init__.py
|   |   ├── provider.py
|   |   ├── urls.py
|   |   └── wsgi.py
|   |
│   ├── manage.py
│   └── db.sqlite3
├── test_provider
|   ├── templates
|   |   └──registration
|   |      └──login.html
|   |
|   ├── test_provider
|   |   ├── __init__.py
|   |   ├── settings.py
|   |   ├── urls.py
|   |   └── wsgi.py
|   |
|   ├── db.sqlite3
|   └── manage.py
|
├── .gitignore
├── Pipfile
└── Pipfile.lock

environment

Development of OAuth2 provider

Project creation


mkdir test-oauth
$ cd test-oauth
$ pipenv --python 3.8
$ pipenv install django, django-oauth-toolkit, django-cors-headers
$ pipenv shell

(pipenv)$ django-admin.py startproject test_provider
(pipenv)$ cd test_provider

Editing test_provider / settings.py

Added to INSTALLED_APPS

settings.py


INSTALLED_APPS = [
    #add to
    'oauth2_provider',
    'corsheaders',
]

Added to MIDDLEWARE_CLASSES

settings.py


MIDDLEWARE = [
    #add to
    'corsheaders.middleware.CorsMiddleware',
]

As mentioned in the Official Documentation, It seems that you have to write it on the built-in middleware 'django.middleware.common.CommonMiddleware'.

Add to the end

settings.py


CORS_ORIGIN_ALLOW_ALL = True

Editing test_provider / urls.py

test_provider/urls.py



from django.contrib import admin
from django.urls import path, include
from .views import profile_view, top_view

urlpatterns = [
    path('admin/', admin.site.urls),
    path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
    path('', top_view),
    path('accounts/', include('django.contrib.auth.urls')),
    path('api/profile/', profile_view, name='profile'),
]

Because I want to create it quickly, the login function uses ** django.contrib.auth **, which is a built-in authentication function, and throws URL patterns and views in full (templates need to be created by yourself).

Create test_provider / views.py

test_provider/views.py


from django.http import JsonResponse
from oauth2_provider.views.generic import ProtectedResourceView
from django.http import HttpResponse


def top_view(request):
    return HttpResponse("It works!")

class ProfileView(ProtectedResourceView):
    def get(self, request, **kwargs):
        user = request.resource_owner

        return JsonResponse({
            'user_id': user.id,
            'email': user.email,
            'date_joined': user.date_joined,
            'secret_message': 'This is secret message',
        })


profile_view = ProfileView.as_view()

Here, a view that simply displays the top page and a view that returns JSON of user information are defined.

Register the Oauth application on the admin site

(pipenv)$ python manage.py migrate
(pipenv)$ python manage.py createsuperuser
(pipenv)$ python manage.py runserver

Register the OAuth application in advance to get the client ID and client secret of the application. At that time, also register the callback URL of the web application. To access the provider's settings page, log in to the Admin site once and then log in. http://127.0.0.1:8000/o/applications/ Open and add the OAuth application to be linked.

Setting items Set value
Name anything
client Type Confidential
Authorization grant type Authorization code
Redirect urls: http://localhost:8001/accounts/testprovider/login/callback/
(Same as above) http://127.0.0.1:8001/accounts/testprovider/login/callback/

Create a test user

Create a user to use when logging in to OAuth. http://127.0.0.1:8000/admin/auth/user/

Creating an OAuth2 consumer test app

Create an app that requests authentication and authorization from test_provider.

Project creation

(pipenv)$ cd test-oauth

(pipenv)$ django-admin.py startproject test_consumer

(pipenv)$ cd test_consumer

Editing test_consumer / settings.py

Added to INSTALLED_APP

test_consumer/settings.py


INSTALLED_APPS = [
    #add to
   'django.contrib.sites',
   'test_consumer',
   'allauth',
   'allauth.account',
   'testprovider',
]

Add to the end

test_consumer/settings.py


AUTHENTICATION_BACKENDS = (
    'django.contrib.auth.backends.ModelBackend',
    'allauth.account.auth_backends.AuthenticationBackend',
)

SITE_ID = 1
SESSION_COOKIE_NAME = 'test-consumer-session-id'
#Redirect to top page after login
LOGIN_REDIRECT_URL = '/'

Specify the transition destination after authentication with ** LOGIN_REDIRECT_URL **.

Edit test_consumer / urls.py

test_consumer/urls.py



from django.contrib import admin
from django.urls import path, include
from django.views.generic import TemplateView

urlpatterns = [
    path('admin/', admin.site.urls),
    path('', TemplateView.as_view(template_name='index.html')),
    path('accounts/', include('allauth.urls')),
]

Creating a template

(pipenv)$ mkdir test_consumer/templates

index.html


<!DOCTYPE html>
{% load socialaccount %}
{% load account %}

<html>
<head>
    <meta charset="utf-8">
    <title>testprovider</title>
</head>
<body>
{% if user.is_authenticated %}
Welcome{% user_display user %}Mr.<br />
  {% for sa in user.socialaccount_set.all %}
    {{ sa.extra_data }}<br />
  {% endfor %}
{% else %}
  <a href="{% provider_login_url "testprovider" %}">
Login with Test Provider
  </a>
{% endif %}
</body>
</html>

To check the operation, use ** {% if user.is_authenticated%} ** to display the login link when logged out and the user information when logged in.

Creating a testprovider adapter

Inheriting the Provider class by referring to Implementation of django-allauth for login for various providers Implement the testprovider adapter. [Addition] Providers that aren't in providers of django-allauth can rewrite this area to create a ** self-made oleore social login feature. ** **

testprovider/provider.py


from allauth.socialaccount import providers
from allauth.socialaccount.providers.base import ProviderAccount
from allauth.socialaccount.providers.oauth2.provider import OAuth2Provider

class TestAccount(ProviderAccount):

    def to_str(self):
        dflt = super(TestAccount, self).to_str()
        return self.account.extra_data.get('name', dflt)

class TestProvider(OAuth2Provider):
    id = 'testprovider'
    name = 'Test Provider'
    account_class = TestAccount

    def get_default_scope(self):
        return ['read', 'write']

    def get_site(self):
        settings = self.get_settings()
        return settings.get('SITE', 'testprovider')

    def extract_uid(self, data):
        uid = str(data['user_id'])
        return uid

    def extract_common_fields(self, data):
        return dict(username=data.get('email', 'no name'))

providers.registry.register(TestProvider)

testprovider/urls.py


from allauth.socialaccount.providers.oauth2.urls import default_urlpatterns
from .provider import TestProvider

urlpatterns = default_urlpatterns(TestProvider)

testprovider/views.py



import requests

from allauth.socialaccount.providers.oauth2.views import (OAuth2Adapter,
                                                          OAuth2LoginView,
                                                          OAuth2CallbackView)
from allauth.socialaccount.providers import registry

from .provider import TestProvider

from django.conf import settings

server_url_prefix = getattr(
    settings, 'TEST_PROVIDER_URL_PREFIX',
    'http://127.0.0.1:8000')

class TestOAuth2Adapter(OAuth2Adapter):
    provider_id = TestProvider.id
    access_token_url = server_url_prefix + '/o/token/'
    authorize_url = server_url_prefix + '/o/authorize/'
    profile_url = server_url_prefix + '/api/profile/'

    def complete_login(self, request, app, token, **kwargs):
        provider = registry.by_id(app.provider)
        resp = requests.get(self.profile_url,
                            params={'access_token': token.token})

        extra_data = resp.json()
        return self.get_provider().sociallogin_from_response(
            request, extra_data)

oauth2_login = OAuth2LoginView.adapter_view(TestOAuth2Adapter)
oauth2_callback = OAuth2CallbackView.adapter_view(TestOAuth2Adapter)

Initial data registration on admin site

(pipenv)$ python manage.py migrate
(pipenv)$ python manage.py createsuperuser
(pipenv)$ python manage.py runserver 8001

http://127.0.0.1:8001/admin Click + Add in [SOCIAL ACCOUNTS]> [Social applications] Set the "client ID" and "client secret" of the application registered on the test_provider side

Setting items Set value
Provider Test Provider
Name Test Provider
Client id The Client id I just created
Secret key Secret key I made earlier
Key Sky
Sites example.Select com

Operation check

http://127.0.0.1:8001 Log in as the test user you created earlier with a browser different from the one you logged in with test_provider.

[Login with Test Provider]> "Authorize"

If the ID linkage is successful, the redirect destination http://127.0.0.1:8001 after successful login set in ** test_consumer / settings.py ** will be redirected, and the user information that was not seen in the logout state will be displayed. It is supposed to be displayed.

At the end

Next, I'm going to develop this content to create a social login function for service providers that is not provided by ** django-allauth **.

Recommended Posts

[Study memo] OAuth2 integration between django projects
Django Learning Memo
LPIC101 study memo
django tutorial memo
heroku deployment memo (Django)
Twitter OAuth with Django
Django memo # 1 from scratch
Job Scheduling @ Study Memo
Shell script @ study memo
[Memo] Django development environment