dackdive's blog

新米webエンジニアによる技術ブログ。JavaScript(React), Salesforce, Python など

Django: python-social-authでTwitter認証を実装する

Django アプリケーションにソーシャルログイン機能を実装して、Heroku アプリケーションとして動かしてみる。

例として Twitter によるログインを試してみるが、Python Social Auth は他にも GoogleFacebook などの主要な Web サービスでの認証をサポートしている。
また、サポートしているフレームワークDjango に限らず色々ある。

最初、Django なら Django Social Auth がいいかと思ったんだけど deprecated だと書いてあったので
推奨されている Python Social Auth を使う。

Python Social Auth のドキュメントはこちら。
http://psa.matiasaguirre.net/docs/index.html

コードは https://github.com/zaki-yama/django-twitter-auth に公開してある。


Twitter API の Consumer Key と Consumer Secret を取得

https://apps.twitter.com/ にアクセスし、「Create New App」で新しくアプリケーションを作成する。
Website, Callback Url の入力を求められるがとりあえずなんでもいい(はず)。
ただし、http://localhost:8000 とかだと invalid URL だと怒られた。(http://127.0.0.1:8000 は OK)

「Keys and Access Tokens」から Consumer Key と Consumer Secret が取得できるので、控えておく。

f:id:dackdive:20160209005809p:plain

プロジェクトを作成する

今回は Heroku 用のアプリケーションを作成するので、以下のように Heroku 用テンプレートから Django プロジェクトを作成する。

$ django-admin.py startproject --template=https://github.com/heroku/heroku-django-template/archive/master.zip --name=Procfile twitter_sample


python-social-auth をインストール

パッケージをインストールする。

$ pip install python-social-auth

自分は Heroku 開発の時は virtualenv を使っているので以下のようにした。

$ virtualenv venv
$ source venv/bin/activate
(venv) $ pip install python-social-auth
(venv) $ pip install -r requirements.txt  # その他必要なパッケージ


settings.py を修正する

公式ドキュメントに従って設定していく。
INSTALLED_APPS などに以下を追加する。

INSTALLED_APPS = (
    ...
    'social.apps.django_app.default',
)

AUTHENTICATION_BACKENDS = [
    'social.backends.twitter.TwitterOAuth',

    'django.contrib.auth.backends.ModelBackend',
]

TEMPLATES = [
    {
        ...
        'OPTIONS': {
            'context_processors': [
                ...
                'social.apps.django_app.context_processors.backends',
                'social.apps.django_app.context_processors.login_redirect',
            ],
            ...
        },
    },
]


# Get your Twitter key/secret from https://apps.twitter.com/
# and place it in .env file
SOCIAL_AUTH_TWITTER_KEY = os.environ.get('SOCIAL_AUTH_TWITTER_KEY')
SOCIAL_AUTH_TWITTER_SECRET = os.environ.get('SOCIAL_AUTH_TWITTER_SECRET')

最後の SOCIAL_AUTH_... は先ほど取得した Consumer Key と Consumer Secret をセットする。
アプリケーションコードを環境変数を分離するため、ここでは .env に書いた。

.env
SOCIAL_AUTH_TWITTER_KEY=*************
SOCIAL_AUTH_TWITTER_SECRET=****************


urls.py を修正する

urls.py に以下のように1行追加する。

urlpatterns = [
    url('', include('social.apps.django_app.urls', namespace='social')),
    ...
]


ログイン URL を表示する

今回は django.views.generic.base.TemplateView を使う。

(settings.py の INSTALLED_APPapp を追加しておく)

urls.py
from django.conf.urls import include, url
from django.views.generic import TemplateView


urlpatterns = [
    url('', include('social.apps.django_app.urls', namespace='social')),
    url(r'^$', TemplateView.as_view(template_name='app/index.html')),  # 追加
app/templates/app/base.html
{% load staticfiles %}
<!DOCTYPE html>
<html lang="{{ LANGUAGE_CODE|default:"en-us" }}">
<head>
  <meta charset="UTF-8">
  <title>{% block title %}{% endblock %}</title>
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
  {% block extrahead %}{% endblock %}
</head>
<body>
  <div class="container">
    {% block content %}
      {{ content }}
    {% endblock %}
  </div>
</body>
</html>
app/templates/app/index.html
{% extends "app/base.html" %}

{% block title %}ホーム{% endblock title %}

{% block content %}
<div>
  <h1>ホーム</h1>

  <p>
  <ul>
    {% if user and not user.is_anonymous %}
    <!-- TODO: ログアウト URL を表示する -->
    {% else %}
    <li>
      <a href="{% url 'social:begin' 'twitter' %}?next={{ request.path }}">Login with Twitter</a>
    </li>
    {% endif %}
  </ul>
  </p>
</div>
{% endblock content %}

{% url 'social:begin' 'twitter' %} という書き方でログイン用 URL が取得できる。
また、URL に next パラメータをつけると認証後のリダイレクト先(コールバック URL)を指定できる。

Django のユーザー認証機能におけるリダイレクト URL はデフォルトで /accounts/profile になっているらしい。
参考:https://docs.djangoproject.com/en/1.9/topics/auth/default/#all-authentication-views

この時点ですでに Twitter でログインし、ログイン後元のサイトにリダイレクトする機能は実装されたことになる。
また、認証後 admin サイトで確認すると Django のユーザーが登録されている。

もし以下のようなエラーが出た場合、Consumer Key や Secret が正しく設定できていない可能性あり。

ValueError at /login/twitter/

Only unicode objects are escapable. Got None of type .

f:id:dackdive:20160209013152p:plain


ログアウト URL を表示する

urls.py に1行追加し、さらに html の TODO 部分をこのようにする。

urls.py
urlpatterns = [
    ...
    url('', include('django.contrib.auth.urls', namespace='auth')),
]
app/templates/app/index.html (抜粋)
    {% if user and not user.is_anonymous %}
    <li>
      <a>Hello {{ user.get_full_name|default:user.username }}!</a>
    </li>
    <li>
      <a href="{% url 'auth:logout' %}?next={{ request.path }}">Logout</a>
    </li>
    {% else %}

{% url 'auth:logout' %} でログアウト用 URL が取得できる。

なお、テンプレート内で user オブジェクトを参照しているが、これは django.contrib.auth.context_processors.auth という context processor を使っていると自動的にコンテキストに含まれるので自分で View を用意して明示的に指定しなくても良い。


アプリケーションを動かす

.env を使っているため

$ heroku local web

でローカルサーバーを起動する。


TODO

認証の時にキャンセルすると AuthCanceled エラーが raise されるのだが、それを捕捉してエラーメッセージを出すなり適切な処理を実装する必要がある。
これについては以下の Stack Overflow を参考にミドルウェアを実装してみたが、この書き方だとその他のエラーが発生したときも HttpResponse を返してしまうので正しいとは言えない気がする。

django - python-social-auth AuthCanceled exception - Stack Overflow


リファレンス