Skip to content

Instantly share code, notes, and snippets.

@yheihei
Last active December 6, 2021 00:51
Show Gist options
  • Star 1 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save yheihei/a4ccea11e01d158b417c18b47322c8fc to your computer and use it in GitHub Desktop.
Save yheihei/a4ccea11e01d158b417c18b47322c8fc to your computer and use it in GitHub Desktop.
Django REST Framework+django-oauth-toolkitでOAuth認証をする

Django REST Framework+django-oauth-toolkitでOAuth認証をする

参考

Getting started — Django OAuth Toolkit 1.5.0 documentation

環境設定

$ pwd
/Users/yhei/point_management_system
$ django-admin startproject pms
$ tree
.
└── pms
    ├── manage.py
    └── pms
        ├── __init__.py
        ├── asgi.py
        ├── settings.py
        ├── urls.py
        └── wsgi.py

パッケージインストール

$ pip install django-oauth-toolkit djangorestframework


$ code pms/pms/settings.py
INSTALLED_APPS = (
    'django.contrib.admin',
    ...
    'oauth2_provider',
    'rest_framework',
)

...

OAUTH2_PROVIDER = {
    # this is the list of available scopes
    'SCOPES': {'read': 'Read scope', 'write': 'Write scope', 'groups': 'Access to your groups'}
}

REST_FRAMEWORK = {
    'DEFAULT_AUTHENTICATION_CLASSES': (
        'oauth2_provider.contrib.rest_framework.OAuth2Authentication',
    ),
    'DEFAULT_PERMISSION_CLASSES': (
        'rest_framework.permissions.IsAuthenticated',
    )
}

マイグレーション

$ python manage.py migrate
$ python manage.py createsuperuser
$ python manage.py runserver

エンドポイント作成

$ code pms/pms/urls.py
"""pms URL Configuration

The `urlpatterns` list routes URLs to views. For more information please see:
    https://docs.djangoproject.com/en/3.0/topics/http/urls/
Examples:
Function views
    1. Add an import:  from my_app import views
    2. Add a URL to urlpatterns:  path('', views.home, name='home')
Class-based views
    1. Add an import:  from other_app.views import Home
    2. Add a URL to urlpatterns:  path('', Home.as_view(), name='home')
Including another URLconf
    1. Import the include() function: from django.urls import include, path
    2. Add a URL to urlpatterns:  path('blog/', include('blog.urls'))
"""
from django.urls import path, include
from django.contrib.auth.models import User, Group
from django.contrib import admin
admin.autodiscover()

from rest_framework import generics, permissions, serializers

from oauth2_provider.contrib.rest_framework import TokenHasReadWriteScope, TokenHasScope

# first we define the serializers
class UserSerializer(serializers.ModelSerializer):
    class Meta:
        model = User
        fields = ('username', 'email', "first_name", "last_name")

class GroupSerializer(serializers.ModelSerializer):
    class Meta:
        model = Group
        fields = ("name", )

# Create the API views
class UserList(generics.ListCreateAPIView):
    permission_classes = [TokenHasReadWriteScope]
    queryset = User.objects.all()
    serializer_class = UserSerializer

class UserDetails(generics.RetrieveAPIView):
    permission_classes = [TokenHasReadWriteScope]
    queryset = User.objects.all()
    serializer_class = UserSerializer

class GroupList(generics.ListAPIView):
    permission_classes = [TokenHasScope]
    required_scopes = ['groups']
    queryset = Group.objects.all()
    serializer_class = GroupSerializer

# Setup the URLs and include login URLs for the browsable API.
urlpatterns = [
    path('admin/', admin.site.urls),
    path('o/', include('oauth2_provider.urls', namespace='oauth2_provider')),
    path('users/', UserList.as_view()),
    path('users/<pk>/', UserDetails.as_view()),
    path('groups/', GroupList.as_view()),
]

permission_classesTokenHasReadWriteScope にする。サンプルの IsAuthenticated を入れると、ユーザーでログインしないとアクセスできなくなり、アプリケーションの認証とは言えなくなる。

adminにログイン

アプリケーション登録

トークン取得

$ curl -X POST -H "Authorization: Basic N3JKTm5PRGpmdnpHWnZmYUxnaXM1bmVXMkxIUTdmRjV5UTJFcDcwZzpZM3czWkllaGlMaE9rVEs1dzc3S1ljMFlrRzdJWVlDSFM3eVVNYTBURDdPMlc2MXJNQms0YlRQSXBtVnJpaTNZcU9SUFF0REloSGd4VUVPQUJqUG1HdmtqQlVhMGlvYmtLMDRJckxha0NVb1dKT1RlMVBHTXd2cDVyMVIzU0xSSg==" -H "Content-Type: application/x-www-form-urlencoded" -d "grant_type=client_credentials" http://localhost:8000/o/token/

{"access_token": "qBsF1AEStGDwSMZdGd99qpo5x3km8W", "expires_in": 36000, "token_type": "Bearer", "scope": "read write groups"}

Authorization: Basic XXX のXXXの部分はクライアントID:シークレットをBase64エンコードしたものが入る

APIコールしてみる

Authorization: Basic アクセストークンを指定してAPIコールしてみる

$ curl -H "Authorization: Bearer qBsF1AEStGDwSMZdGd99qpo5x3km8W" http://localhost:8000/users/
[{"username":"yhei","email":"yheihei0126@gmail.com","first_name":"","last_name":""}

違うトークンでAPIコールして取得できないこと

$ curl -H "Authorization: Bearer hoge" http://localhost:8000/users/
{"detail":"Authentication credentials were not provided."}

トークンなしでAPIコールして取得できないこと

$ curl http://localhost:8000/users/
{"detail":"Authentication credentials were not provided."

トークンが失効したら401が返ること

adminからトークンを失効させた後にAPIコールする。

$ curl -H "Authorization: Bearer qBsF1AEStGDwSMZdGd99qpo5x3km8W" -i "http://localhost:8000/users/"
HTTP/1.1 401 Unauthorized
Date: Tue, 18 May 2021 13:54:16 GMT
Server: WSGIServer/0.2 CPython/3.8.5
Content-Type: application/json
WWW-Authenticate: Bearer realm="api",error="invalid_token",error_description="The access token has expired."
Vary: Accept
Allow: GET, POST, HEAD, OPTIONS
X-Frame-Options: DENY
Content-Length: 58
X-Content-Type-Options: nosniff

{"detail":"Authentication credentials were not provided."}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment