前提
models.py
メールアドレスとパスワードでログインする方式。
from django.db import models
from django.conf import settings
from django.contrib.auth.models import (
BaseUserManager, AbstractBaseUser, PermissionsMixin
)
from django.utils.translation import gettext_lazy, gettext as _
LANGUAGES = settings.LANGUAGES
NUMBER_REGEX = settings.NUMBER_REGEX
class UserManager(BaseUserManager): # ☆3
def create_user(self, username, email, password=None):
if not email:
raise ValueError('Enter Email!')
user = self.model(
username=username,
email=email,
)
user.set_password(password)
user.is_active = True
user.save(using=self._db)
return user
def create_superuser(self, username, email, password=None):
user = self.model(
username=username,
email=email,
)
user.set_password(password)
user.is_staff = True
user.is_active = True
user.is_superuser = True
user.save(using=self._db)
return user
class User(AbstractBaseUser, PermissionsMixin): # ☆1
# 使いたいFieldを追加
username = models.CharField(max_length=20, verbose_name="username")
email = models.EmailField(max_length=255, unique=True, verbose_name="email")
phone = models.CharField(validators=[NUMBER_REGEX], max_length=15, verbose_name="phone", blank=True, null=True)
lang = models.CharField(max_length=20,verbose_name="language", choices=LANGUAGES, blank=False, null=False)
is_active = models.BooleanField(default=True)
is_staff = models.BooleanField(default=False)
objects = UserManager()
USERNAME_FIELD = 'email' # ☆2 このテーブルのレコードを一意に識別
REQUIRED_FIELDS = ['username'] # スーパーユーザ作成時に入力する
class Meta:
db_table = 'users'
def __str__(self):
return self.email
チャネルIDとチャンネルシークレットを取得しておく。これは他のサイトでも詳しく紹介されているため、割愛します。
.envに以下のように記述。
SOCIAL_AUTH_LINE_KEY=チャンネルID
SOCIAL_AUTH_LINE_SECRET=チャンネルシークレット
settings.pyで.envから取得。
#################################
#---------LINE LOGIN----------
#################################
SOCIAL_AUTH_LINE_KEY = os.environ.get('SOCIAL_AUTH_LINE_KEY')
SOCIAL_AUTH_LINE_SECRET = os.environ.get('SOCIAL_AUTH_LINE_SECRET')
LINE認証からログイン、新規登録までの流れ
LINEdevelopersのこれ見てもいまいちピンとこないので、今回やりたいことを非常に簡単にまとめると、
①ユーザーが「LINEでログインする」のボタンを押す
②そのURLがLINEの情報アクセス許可のためのURLでユーザーはそこでアクセス許可を出す。
③自分のサイトにリダイレクト
④リダイレクトのURLにパラメータがついているので、それでトークンを取得する。
⑤取得したトークンでユーザーの情報を取得する。
⑥ユーザーの情報をもとにログイン、新規登録を行う。
⑦ログイン完了画面に遷移
①と⑦はclassベースビューで行い、その他は関数ベースビューを使う(今回は)。
中ではこんなに処理があるけど、実際にユーザーがやるのはログインボタンを押して承認したら、ログイン完了画面が出てくるだけ。
ぶっちゃけこれでもイメージがしにくいけど、実際にやってみると「あーこんなもんか」となる。あとは、なんでこんなことするの?となる。
具体的な手順
1.ユーザー認証を済ませてリダイレクトURLからcodeを取得する
LINEが用意してくれているユーザー認証画面に遷移するurlと認証後にリダイレクトされるurlの2つを追加する。
urls.py
from django.urls import path,include
from . import views
app_name = "accounts"
urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
path("signup/", views.signup, name="signup"),
path("signout/", views.signout, name="signout"),
path("signin/", views.signin, name="signin"),
path("changeinfo/", views.changeinfo, name="changeinfo"),
path("profile/", views.profile, name="profile"),
#追加(認証前)
path("line_login/", views.line_login, name="line_login"),
#追加(認証後リダイレクト)
path("line_token/", views.line_token, name="line_token"),
]
認証後のurlをsettings.pyに記述し、これをLINEdevelopersのコールバックURLにも追加する。自分は本番環境と開発環境それぞれにリダイレクトurlを設定した。LINEに追加したコールバックURLとdjangoに追加したリダイレクトurlが一致しないとうまくいかないので注意。
settings.py
#本番環境なら
LINE_REDIRECT_URL = 'https://hogehoge.com/accounts/line_token'
#local環境なら
LINE_REDIRECT_URL = 'http://127.0.0.1:8000/accounts/line_token'
templatesのどこかにこのviewsを呼び出すためのurlを乗せておく
<p>LINEで <a href="{% url 'accounts:line_login' %}">ログイン</a></p>
views.py
from django.middleware.csrf import get_token
from django.conf import settings
SOCIAL_AUTH_LINE_KEY = settings.SOCIAL_AUTH_LINE_KEY
SOCIAL_AUTH_LINE_SECRET = settings.SOCIAL_AUTH_LINE_SECRET
LINE_REDIRECT_URL = settings.LINE_REDIRECT_URL
def line_login(request):
try:
token = get_token(request)
scope = "profile%20openid%20email"
line_login_url = f'https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={SOCIAL_AUTH_LINE_KEY}&redirect_uri={LINE_REDIRECT_URL}&state={token}&scope={scope}'
return redirect(line_login_url)
except:
return redirect("accounts:signin")
scopeではユーザー情報で取得したい項目を承認してもらうために設定する。必要に合わせて変更できる。
tokenはセキュリティの観点から必要らしい。
これでline_login_urlにリダイレクトすると、URLにcodeとstateをつけて返してくれる。
具体的にはhttps://hogehoge.com/accounts/line_token/?state=123456abc&code=098765zyxのようになっている。
このcodeが次に利用する許可コードになっており、これとシークレットキー(チャンネルシークレット)を利用すると今度はid_tokenを取得でき、そのid_tokenでユーザーの情報を取得できる。
セキュリティの観点から必要なことだろうけど、二度手間だなあとは思う。。。
2.id_token(アクセストークン)の取得
正直ここが一番難しい。リクエストヘッダをつけたり、必須項目をパラメータにつけるのはまだ良いけど、response = requests.post(url, headers=headers, data=data)で受け取るとuriが一致しないだの、postが変だの情報の少ないエラーが続く。
まさしくこの内容
redirect_uriはこの後のリダイレクト先じゃなくて、さっきリダイレクトしたやつとここで設定するuriが一致するかどうかの検証らしい。この辺の説明がドキュメントも薄いし、先人も少ないのでなかなかハマった。
ちなみにreponseはjson形式で返ってきて、その中にid_tokenが入っている感じ。
やり方さえ知ってたらこんな感じで10何行かでできるのに、ここまで手こずるのは正直LINE側の使用の問題ではないかと思う。この辺の説明はもうちょっと書いていて欲しかった。
views.py
def line_token(request):
try:
code = request.GET.get('code')
url = "https://api.line.me/oauth2/v2.1/token"
# リクエストヘッダ
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
# リクエストボディ
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": LINE_REDIRECT_URL,
"client_id": SOCIAL_AUTH_LINE_KEY,
"client_secret": SOCIAL_AUTH_LINE_SECRET
}
# POSTリクエストを送信
response = requests.post(url, headers=headers, data=data)
data = response.json()
access_token = data['id_token']
#続きは後述
3.アクセストークンを使ってユーザー情報を取得しログインする
ここまでこれたらあとは大丈夫。先ほどのアクセストークンをつけて同じようにjsonを受け取ると、中にemailやusernameが入っているので、それを利用してログインを行う。ここではセキュリティ、検証的なものは考慮していないので注意。
#続き
url = f'https://api.line.me/oauth2/v2.1/verify?id_token={access_token}&client_id={SOCIAL_AUTH_LINE_KEY}'
response = requests.post(url)
data = response.json()
username = data['name']
email = data['email']
password = data['sub']
try:
user = User.objects.get(email=email)
login(request, user)
except User.DoesNotExist:
user = User.objects.create_user(username=username, email=email, password=password)
login(request, user)
return redirect('accounts:profile')
except:
return redirect("accounts:signin")
全体像
views.pyの全体像を載せておきます。モジュールのimportなどは適宜行なってください。
SOCIAL_AUTH_LINE_KEY = settings.SOCIAL_AUTH_LINE_KEY
SOCIAL_AUTH_LINE_SECRET = settings.SOCIAL_AUTH_LINE_SECRET
LINE_REDIRECT_URL = settings.LINE_REDIRECT_URL
#ユーザー認証とアクセス権の許可
def line_login(request):
try:
token = get_token(request)
scope = "profile%20openid%20email"
line_login_url = f'https://access.line.me/oauth2/v2.1/authorize?response_type=code&client_id={SOCIAL_AUTH_LINE_KEY}&redirect_uri={LINE_REDIRECT_URL}&state={token}&scope={scope}'
return redirect(line_login_url)
except:
return redirect("accounts:signin")
#id_tokenを取得し、そこからユーザー情報を取得 → ログイン
def line_token(request):
try:
code = request.GET.get('code')
url = "https://api.line.me/oauth2/v2.1/token"
# リクエストヘッダ
headers = {
"Content-Type": "application/x-www-form-urlencoded"
}
# リクエストボディ
data = {
"grant_type": "authorization_code",
"code": code,
"redirect_uri": LINE_REDIRECT_URL,
"client_id": SOCIAL_AUTH_LINE_KEY,
"client_secret": SOCIAL_AUTH_LINE_SECRET
}
# POSTリクエストを送信
response = requests.post(url, headers=headers, data=data)
data = response.json()
access_token = data['id_token']
url = f'https://api.line.me/oauth2/v2.1/verify?id_token={access_token}&client_id={SOCIAL_AUTH_LINE_KEY}'
response = requests.post(url)
data = response.json()
username = data['name']
email = data['email']
password = data['sub']
#ログインor新規登録
try:
user = User.objects.get(email=email)
login(request, user)
except User.DoesNotExist:
user = User.objects.create_user(username=username, email=email, password=password)
login(request, user)
return redirect('accounts:profile')
except:
return redirect("accounts:signin")
urls.py
from django.urls import path,include
from . import views
app_name = "accounts"
urlpatterns = [
path('i18n/', include('django.conf.urls.i18n')),
path("signup/", views.signup, name="signup"),
path("signout/", views.signout, name="signout"),
path("signin/", views.signin, name="signin"),
path("changeinfo/", views.changeinfo, name="changeinfo"),
path("profile/", views.profile, name="profile"),
#追加
path("line_login/", views.line_login, name="line_login"),
path("line_token/", views.line_token, name="line_token"),
]
settings.py
#################################
#---------LINE LOGIN----------
#################################
SOCIAL_AUTH_LINE_KEY = os.environ.get('SOCIAL_AUTH_LINE_KEY')
SOCIAL_AUTH_LINE_SECRET = os.environ.get('SOCIAL_AUTH_LINE_SECRET')
LINE_REDIRECT_URL = 'http://127.0.0.1:8000/accounts/line_token'
以上です。お疲れ様でした。