PythonでGoogle Sign-Inを使用して認証の実装
はじめに
サービスを作るときに、一部のユーザーのみ提供したい場合があるでしょう。
社内で、ビジネスを加速させるために、データサイエンスツールを作っていた。社内限定なツールなので、アクセス制限が必要です
弊社は全員Gmail使用するため、ユーザーシステムを持たずに、Google Sign-In経由、手軽に認証システムを作りました。
本文は、Google Sign-Inを使用するユーザー認証のプロセスを紹介します。構成は以下です:
「Talk is cheap, show me the code.」を信じている人は、「Google Sign-Inを用いた認証のプロセス」の部分だけ見れば良い。
自分の理解が限られるなので、もし不適切なところがありましたら、ご指摘いただけると幸いです。
OAuthとGoogleログインの紹介
OAuthが広く使われている概念ですが、自体はややこしいなので、誤解されていることがあるでしょう。先に関連する概念を説明します。
基本概念
認証(Authentication、AuthN):通信の相手が誰(何)であるかを確認すること。
認可(Authorization、AuthZ):とある特定の条件に対して、リソースアクセスの権限を与えること。
なので、認可には、「誰」という概念はなく、「リソース」という概念が必要。リソースに対してアクセス可否を判断することです。 リソースの定義は略、WebページやAPIやなどのことです。
OAuth:権限の認可を行うためのプロトコルである。現時点の標準は2.0版なので、OAuth2と呼ばれている。
OpenID Connect:OAuth 2.0 プロトコルの上にシンプルなアイデンティティレイヤーを付与したものである。
簡単に言うと
OpenID Connect = OAuth2 + Identity Layer
Google Sign-In:Googleアカウントでサインイン、軽減する認証システムです。Googleユーザーと連結することもできます。
Google Sign-Inは、OAuth 2.0のプロトコルに従っています。
出典:https://developers.google.com/identity?hl=ja
OAuth2認可のプロセス
OAuth2に関する4つのロール
Resource Owner:リソースへのアクセス権限を与えられる人またはエンティティ。例えば、ユーザー。
Resource Server:リソースを格納するかつAccessTokenを使ったリソースリクエストに応えられるサーバー。例えば、Facebook・Google・GithubのAPIサーバー。
Client:Resource Ownerの代理として、リソースを要求するアプリケーション。例えば、Kaggle(Facebookでログインできます)。
Authorization Server:例えば、Facebook・Google・Githubの認証サーバー。
OAuth2認可プロセス
OAuth2における、認可の流れは以下です:
+--------+ +---------------+ | |--(A)- Authorization Request ->| Resource | | | | Owner | | |<-(B)-- Authorization Grant ---| | | | +---------------+ | | | | +---------------+ | |--(C)-- Authorization Grant -->| Authorization | | Client | | Server | | |<-(D)----- Access Token -------| | | | +---------------+ | | | | +---------------+ | |--(E)----- Access Token ------>| Resource | | | | Server | | |<-(F)--- Protected Resource ---| | +--------+ +---------------+
出典:https://tools.ietf.org/html/rfc6749
簡単に説明すると、
- ClientはResouce Ownerに特定リソースの権限を要求する、Resouce Ownerは権限付与を同意(Authorization Grantを発行)する
- ClientはAuthorization GrantをAuthorization Serverに見せて、AccessTokenを要求する。Authorization Serverは検証し、AccessTokenをClientに渡します。
- ClientはAccessTokenを使って、Resource Serverにリソースを要求する、Resource ServerはAccessTokenを検証し、リクエストの結果を返す。
具体出来に、どう検証するのか、GoogleにおけるOAuth2の認可のプロセスの部分に参照してください。
承認のフローがややこしいなので、詳細は
に参考してください。
Open Id Connect のプロセス
Open Id Connectの流れは以下です、説明が略。
+--------+ +--------+ | | | | | |---------(1) AuthN Request-------->| | | | | | | | +--------+ | | | | | | | | | | | End- |<--(2) AuthN & AuthZ-->| | | | | User | | | | RP | | | | OP | | | +--------+ | | | | | | | |<--------(3) AuthN Response--------| | | | | | | |---------(4) UserInfo Request----->| | | | | | | |<--------(5) UserInfo Response-----| | | | | | +--------+ +--------+
出典: https://openid.net/specs/openid-connect-core-1_0.html
なぜOAuth2が必要なのか
下記の仮想ユースケースから説明します。
ユーザーAは、Googleアカウントを持っています。そして、幾つのリソースが持っています:
- メールアドレス
- アイコン
- 年齢情報
- など
ユーザーが自分のユーザー名とパースワードでログインするから、リソースの訪問することができます。
ユーザーAは、あるアプリBで、活動するために、Googleアカウントの特定情報(例えばアイコン)が必要です。
アプリBが、ユーザーAのGoogleアカウントの特定のリソースをアクセスすることが必要となっています。
しかし、このリソースはプラベートなので、A以外は誰でも訪問出来ない。Aは、リソース訪問権限をBに付与したい場合は、どうすればいいでしょうか?
方法1:ユーザーAはアカウントとパスワードをBに渡し、Bはリソースをアクセスする。
リソースのアクセスができますが、以下のデメリットがあります:
- 安全ではありません:パースワードを渡すのは怖いでしょう
- 権限が大きい:Bが認証を取得すると、Aのすべてのリソースにアクセスできる
- 簡単にキャンセルできない:パースワードを変更する以外は認証を失効されることはできない
方法2:ユーザーAが一定期間内、特定リソースへアクセスする「トークン」をBに渡す、Bはこの「トークン」を持って、リソースを訪問します。
パスワードより、トークンは以下のメリットがあります:
- パスワードが漏らない
- 特定のサービスに限定できる
- 期間を過ぎたら失効出来る
Access Tokenの理解
お客さん、会社に見学することをイメージして。
お客さんの資格を審査し、お客さんシールと食券を渡します。社員のセキュリティカードと違うものです。
お客さんはシールを使って、会社の特定なエリアで見学できますし、食券を使って食事もできます、でもそれ以外のコンフィデンシャルのところの立ち入ることはできない。
見学終了あと、シールと食券の使用はできなくなて、再入場の場合、再度の審査とシールの再発行が必要です。
トークンは似てるなものです。
Google Sign-Inを用いた認証のプロセス
「Google Sign-In」での認証実際は、GoogleのOAuth2サービスを使って、ユーザーからGoogleアカウント情報をアクセスの認可をもらえて、ユーザーのGoogleアカウントの情報を使って、ユーザーをウェブサイトの訪問を許すかどうか判断することです。
GoogleにおけるOAuth2の認可のプロセス
OAuth2認証のプロセスに従って、GoogleSignInのプロセスを説明します。
権限を要求する
ユーザーがログインボタンを押すとき、ClientはGoogle Login画面をリダイレクトして、権限を要求する。ユーザーを同意すると、権限を付与されます。
権限付与のとき、(事前発行した)client_idや、同意するscopeをGoogleに渡します。
AccessTokenを要求する
ユーザーが同意する後、GoogleはClientを事前登録したCallbackのUriを叩いて、(Grant)codeを渡します。 Clientはそのcodeとclient_id, client_secretを使って、AuthAPIを叩き、AccessTokenを請求します。 Google側が検証して、AccessTokenをResponseで返します。
リソースを請求する
一定期間内、返したAccessTokenを使って、ユーザーのリソースをアクセス出来ます。
PythonでGoogle Sign-Inで認証の実装
事前準備
ウェブアプリはGoogleAuthを登録して、以下の情報をもらえます:
- client_id
- client_secret
そして、以下の情報を、Googleに登録します:
- domain
- Redirect Uri
準備の詳細は、このこのページに参考してください:
https://developers.google.com/identity/protocols/OpenIDConnect?hl=ja
FlaskでGoogleログインを実装する
最低限の機能を実現するコードを実装してみます。
フォルダストラクチャー:
-- requirements.txt -- index.py -- templates |-- login.html |-- index.html
index.py
ポイントはcallback関数。 Google Sign-Inは、callback関数のURIを叩いて、コードを返します。
from flask import Flask, render_template, request import requests import logging logging.basicConfig(level=logging.DEBUG) app = Flask(__name__) @app.route('/login') def login(): return render_template("login.html") @app.route('/oauth/redirect') def callback(): client_id = "your_client_id" client_secret = "your_client_secret" code = request.args["code"] url = "https://www.googleapis.com/oauth2/v4/token" headers = {'Content-Type': 'application/x-www-form-urlencoded'} form = dict(code=code,client_id=client_id, client_secret=client_secret,redirect_uri='http://localhost:5000/oauth/redirect',grant_type='authorization_code') # codeとclient_secretと合わせて、AccessTokenを請求 resp = requests.post(url, headers=headers, data=form) # access_tokenを取得 access_token = resp.json()['access_token'] # access_tokenを使ってリソースを獲得 resp = requests.get('https://www.googleapis.com/oauth2/v1/tokeninfo?access_token={access_token}'.format(access_token=access_token)) logging.info(resp.json()) email=resp.json()['email'] # validate the email, if on whitelist, etc. return render_template("index.html", email=email) if __name__ == '__main__': app.run()
templates/login.html
ポイントは渡すのパラメタで、client_id、redirect_uri、scopeのところ、適当に切り替えすることです。 scopeの設定について、Googleのドキュメントに参照してください:
https://developers.google.com/identity/protocols/OpenIDConnect?hl=ja#scope-param
<h1>hello</h1> <a href="https://accounts.google.com/o/oauth2/auth?client_id=your_client_id&redirect_uri=http://localhost:5000/oauth/redirect&response_type=code&scope=openid%20email">グーグルで認証</a>
templates/index.html
<h1>hello</h1> {{email}}
引用
Final: OpenID Connect Core 1.0 incorporating errata set 1
OAuth 2.0の代表的な利用パターンを仕様から理解しよう - Build Insider
OpenID Connectユースケース、OAuth 2.0の違い・共通点まとめ - Build Insider
Using OAuth 2.0 to Access Google APIs | Google Identity Platform
OpenID Connect | Google Identity Platform | Google Developers