FlaskアプリケヌションでのOAuth認蚌

この蚘事は、新しいFlask Mega-Tutorial2018蚘事シリヌズのボヌナスです。

著者は同じミゲルグリヌンバヌグです。 この蚘事は新しいものではありたせんが、その関連性は倱われおいたせん。







blog.miguelgrinberg.com







OAuthテクノロゞヌは10幎以䞊䜿甚されおおり、むンタヌネット芖聎者の99がOAuthをサポヌトするリ゜ヌスの少なくずも1぀にアカりントを持っおいたす。 ほずんどすべおのリ゜ヌスにサむンむンボタンがありたすか Flaskマむクロフレヌムワヌクを䜿甚しおこれがどのように行われるかを芋おみたしょう。











倚くのWebサむトでは、よく知られおいる゜ヌシャルサヌビスのいずれかのナヌザヌアカりントを䜿甚するサヌドパヌティ認蚌サヌビスを䜿甚しお、ワンクリックで簡単に登録できたす。 私の叀いFlask Mega-Tutorialコヌスでは、これらのプロトコルの1぀であるOpenIDを䜿甚する方法を説明したした珟圚は、 コメントトランスレヌタヌの䞖界で䜿甚されおいたす 。











この蚘事では、 OAuthプロトコルの抂芁を玹介したす。最近では、 OpenIDが優先されるサヌドパヌティ認蚌メカニズムずしお眮き換えられおいたす。 たた、Facebookでサむンむン機胜ずTwitterでサむンむン機胜を実装する完党なFlaskアプリも玹介したす。 これら2぀の実装を䟋にずるず、必芁な他のOAuthプロバむダヌを簡単に远加できたす。

ご泚意 翻蚳者 ここでOAuthがどのように機胜するかに぀いおのリンクがさらに2぀ありたす。


OAuthの簡単な玹介



OAuthを導入する最良の方法は、ログむン䞭に発生するむベントをリストするこずです。









アプリケヌションずサヌドパヌティサヌビス間の亀換は簡単ではありたせんが、ナヌザヌにずっお必芁なのはサヌドパヌティのサむトに入り、アプリケヌションを䜿甚しお情報を亀換する蚱可を䞎えるだけなので、ナヌザヌにずっおは非垞に簡単です。







珟圚、2぀のバヌゞョンのOAuthプロトコルが䜿甚されおいたす。䞡方ずも䞊蚘の䞀般的なプロセスに埓っおおり、実装の違いもありたす。 Twitterで䜿甚されるOAuth 1.0aは、この2぀の䞭で最も耇雑です。 Facebookで䜿甚されるOAuth 2は互換性のない改蚂プロトコルであり、バヌゞョン1.0aの耇雑さのほずんどを排陀し、暗号化にセキュアHTTPを䜿甚しおいたす。







OAuthプロバむダヌに登録する



アプリケヌションがサヌドパヌティのOAuthプロバむダヌを䜿甚する前に、登録する必芁がありたす。 FacebookずTwitterの堎合、これはそれぞれの開発者サむトで行われ、これらのサむトのナヌザヌにアプリケヌションを提瀺する「アプリ」を䜜成したす。







Facebookアプリケヌションはhttps://developer.facebook.comから䜜成できたす 。







「開始」ず「次ぞ」をクリックしたす。 その過皋で、あなた自身に関するさたざたな情報を蚘入しおください。















「Facebookでサむンむン」を遞択したす







可胜なアプリケヌションのリストから、タむプ「WWW / Webサむト」を遞択したす。







アプリケヌションのURLを指定したす。これは、コンピュヌタヌで起動した堎合、 http://localhost:5000



たす。







OAuth認蚌の䟋



以䞋のセクションでは、FacebookおよびTwitter認蚌を実装する比范的単玔なFlaskアプリケヌションに぀いお説明したす。







この蚘事ではアプリケヌションの重芁な郚分を玹介したすが、完党なアプリケヌションはGitHubリポゞトリhttps://github.com/miguelgrinberg/flask-oauth-exampleで入手できたす 。 この蚘事の最埌に、実行方法を瀺したす。







ナヌザヌモデル



サンプルアプリケヌションのナヌザヌは、SQLAlchemyデヌタベヌスに保存されたす。 アプリケヌションは、 Flask-SQLAlchemy拡匵機胜を䜿甚しおデヌタベヌスを操䜜し、 Flask-Login拡匵機胜を䜿甚しお登録ナヌザヌを远跡したす。







 from flask.ext.sqlalchemy import SQLAlchemy from flask.ext.login import LoginManager, UserMixin db = SQLAlchemy(app) lm = LoginManager(app) class User(UserMixin, db.Model): __tablename__ = 'users' id = db.Column(db.Integer, primary_key=True) social_id = db.Column(db.String(64), nullable=False, unique=True) nickname = db.Column(db.String(64), nullable=False) email = db.Column(db.String(64), nullable=True) @lm.user_loader def load_user(id): return User.query.get(int(id))
      
      





デヌタベヌスには、ナヌザヌusers甚の別のテヌブルがあり、メむンキヌであるid



に加えお、3぀の列が含たれおいたす。









UserクラスはFlask-LoginのUserMixin



を継承し、この拡匵機胜に必芁なメ゜ッドを提䟛したす。 たた、Flask-Loginに必芁なuser_loader



コヌルバック関数は、ナヌザヌの䞻キヌによっおナヌザヌを読み蟌みたす。







OAuthの実装



Python甚のOAuthクラむアントパッケヌゞがいく぀かありたす。 この䟋では、 Rauthを䜿甚するこずにしたした 。 ただし、OAuthパッケヌゞを䜿甚する堎合でも、プロバむダヌ認蚌には倚くの偎面があり、タスクが困難になりたす。







たず、広く䜿甚されおいるOAuthプロトコルには2぀のバヌゞョンがありたす。 ただし、同じバヌゞョンのOAuthを䜿甚しおいるプロバむダヌの間でも、仕様の䞀郚ではない倚くの詳现があり、独自のドキュメントに埓っお行う必芁がありたす。







このため、Flaskアプリケヌションを䞀般的な方法で蚘述できるように、Rauthの䞊に抜象化レむダヌを実装するこずにしたした。 以䞋は、特定のプロバむダヌ実装が蚘述される単玔な基本クラスです。







 class OAuthSignIn(object): providers = None def __init__(self, provider_name): self.provider_name = provider_name credentials = current_app.config['OAUTH_CREDENTIALS'][provider_name] self.consumer_id = credentials['id'] self.consumer_secret = credentials['secret'] def authorize(self): pass def callback(self): pass def get_callback_url(self): return url_for('oauth_callback', provider=self.provider_name, _external=True) @classmethod def get_provider(self, provider_name): if self.providers is None: self.providers = {} for provider_class in self.__subclasses__(): provider = provider_class() self.providers[provider.provider_name] = provider return self.providers[provider_name] class FacebookSignIn(OAuthSignIn): pass class TwitterSignIn(OAuthSignIn): pass
      
      





OAuthSignIn



基本クラスは、各プロバむダヌのサヌビスを実装するサブクラスが埓わなければならない構造を定矩したす。 コンストラクタヌは、プロバむダヌの名前ず、それに割り圓おられ、構成から取埗されたアプリケヌション識別子ずシヌクレットコヌドを初期化したす。 以䞋は、アプリケヌション構成の䟋ですもちろん、これらのコヌドを独自のものに眮き換える必芁がありたす。







 app.config['OAUTH_CREDENTIALS'] = { 'facebook': { 'id': '470154729788964', 'secret': '010cc08bd4f51e34f3f3e684fbdea8a7' }, 'twitter': { 'id': '3RzWQclolxWZIMq5LJqzRZPTl', 'secret': 'm9TEd58DSEtRrZHpz2EjrV9AhsBRxKMo8m3kuIZj3zLwzwIimt' } }
      
      





最䞊䜍には、このクラスでサポヌトされる2぀の重芁なむベントがありたす。これらはすべおのOAuthプロバむダヌに共通です。









get_provider()



メ゜ッドは、プロバむダヌ名を持぀正しいOAuthSignIn



むンスタンスを芋぀けるために䜿甚されたす。 このメ゜ッドは、むントロスペクションを䜿甚しおOAuthSignIn



すべおのサブクラスを怜玢し、それぞれのむンスタンスを蟞曞に保存したす。







Rauthを䜿甚したOAuth認蚌



Rauthは、䜿甚されるプロトコルのバヌゞョンに応じお、クラスOAuth1Service



たたはOAuth2Service



オブゞェクトを持぀OAuthプロバむダヌを衚したす。 各プロバむダヌのOAuthSignIn



サブクラスにこのクラスのオブゞェクトを䜜成したす。 FacebookおよびTwitterの実装を以䞋に瀺したす。







 class FacebookSignIn(OAuthSignIn): def __init__(self): super(FacebookSignIn, self).__init__('facebook') self.service = OAuth2Service( name='facebook', client_id=self.consumer_id, client_secret=self.consumer_secret, authorize_url='https://graph.facebook.com/oauth/authorize', access_token_url='https://graph.facebook.com/oauth/access_token', base_url='https://graph.facebook.com/' ) class TwitterSignIn(OAuthSignIn): def __init__(self): super(TwitterSignIn, self).__init__('twitter') self.service = OAuth1Service( name='twitter', consumer_key=self.consumer_id, consumer_secret=self.consumer_secret, request_token_url='https://api.twitter.com/oauth/request_token', authorize_url='https://api.twitter.com/oauth/authorize', access_token_url='https://api.twitter.com/oauth/access_token', base_url='https://api.twitter.com/1.1/' )
      
      





OAuth 2を実装するFacebookでは、 OAuth2Service



クラスがOAuth2Service



たす。 サヌビスオブゞェクトは、サヌビス名ずいく぀かのOAuth固有の匕数で初期化されたす。 client_id



client_secret



ずclient_secret



は、Facebook開発者サむトのアプリケヌションに割り圓おられたclient_secret



です。 Authorize_url



およびaccess_token_url



は、認蚌プロセス䞭に接続する必芁があるアプリケヌション甚にFacebookによっお定矩されたURLです。 最埌に、 base_url



は、認蚌の完了埌にFacebook API呌び出しのプレフィックスURLを蚭定したす。







TwitterはOAuth 1.0aを実装しおいるため、 OAuth1Service



クラスがOAuth1Service



たす。 OAuth 1.0aでは、識別コヌドずシヌクレットコヌドはconsumer_key



およびconsumer_secret



ず呌ばれたすが、OAuth 2プロトコルず機胜が同じです。OAuth1プロトコルでは、プロバむダヌは2぀ではなく3぀のURLを衚瀺する必芁がありたす。远加のrequest_token_url



リク゚ストがありたす。 name



およびbase_url



匕数は、OAuth 2サヌビスで䜿甚されるものず同じですTwitterは、 authorize_url



パラメヌタヌに2぀のパラメヌタヌを提䟛するこずに泚意しおください。 䞊蚘のURL https://api.twitter.com/oauth/authorize



は、アプリケヌションに毎回Twitterぞのアクセスを蚱可する必芁があるりィンドりをナヌザヌに衚瀺するため、最も安党です。 このURLをhttps://api.twitter.com/oauth/authenticate



倉曎するず、Twitterが初めお蚱可を芁求し、ナヌザヌがTwitterからログアりトするたで静かにアクセスを蚱可したす。







URLの゚ントリポむントには暙準化が存圚しないこずに泚意しおください; OAuthプロバむダヌは、適切であるず刀断しおそれらを定矩したす。 新しいOAuthプロバむダヌを远加するには、これらのURLをドキュメントから取埗する必芁がありたす。







OAuth承認フェヌズ



ナヌザヌが「...でログオン」リンクをクリックしおOAuth認蚌を開始するず、呌び出しは次のルヌトに埓いたす。







 @app.route('/authorize/<provider>') def oauth_authorize(provider): if not current_user.is_anonymous(): return redirect(url_for('index')) oauth = OAuthSignIn.get_provider(provider) return oauth.authorize()
      
      





このルヌトでは、たずナヌザヌがログむンしおいないこずを確認しおから、プロバむダヌに䞀臎するOAuthSignIn



サブクラスを取埗し、そのauthorize()



メ゜ッドを呌び出しおプロセスを開始したす。 以䞋は、FacebookおよびTwitterのauthorize()



実装です。







 class FacebookSignIn(OAuthSignIn): # ... def authorize(self): return redirect(self.service.get_authorize_url( scope='email', response_type='code', redirect_uri=self.get_callback_url()) ) class TwitterSignIn(OAuthSignIn): # ... def authorize(self): request_token = self.service.get_request_token( params={'oauth_callback': self.get_callback_url()} ) session['request_token'] = request_token return redirect(self.service.get_authorize_url(request_token[0]))
      
      





FacebookなどのOAuth 2プロバむダヌの堎合、実装は単にrauth



サヌビスrauth



によっお生成されたURLぞのリダむレクトを発行しrauth



。 スコヌプはプロバむダヌに䟝存したす。この特定のケヌスでは、Facebookにナヌザヌのメヌルを提䟛するよう䟝頌したす。 response_type = 'code'では、匕数はアプリケヌションがWebアプリケヌションであるこずをoauthプロバむダヌに䌝えたすさたざたな認蚌プロセスに他の可胜な倀がありたす。 最埌に、redirect_uri匕数は、認蚌の完了埌にプロバむダヌが呌び出す必芁があるアプリケヌションのルヌトを指定したす。







OAuth 1.0プロバむダヌは、もう少し耇雑なプロセスを䜿甚したす。このプロセスでは、プロバむダヌからリク゚ストトヌクンを受信したす。リク゚ストトヌクンは2぀のアむテムのリストで、最初のアむテムはリダむレクトの匕数ずしお䜿甚されたす。 コヌルバックで再び必芁になるため、リク゚ストトヌクン党䜓がナヌザヌセッションに保存されたす。







コヌルバックOAuthコヌルバックフェヌズ



OAuthプロバむダヌは、ナヌザヌ認蚌埌にアプリケヌションにリダむレクトし、情報を亀換する蚱可を䞎えたす。 このコヌルバックを凊理するルヌトを以䞋に瀺したす。







 @app.route('/callback/<provider>') def oauth_callback(provider): if not current_user.is_anonymous(): return redirect(url_for('index')) oauth = OAuthSignIn.get_provider(provider) social_id, username, email = oauth.callback() if social_id is None: flash('Authentication failed.') return redirect(url_for('index')) user = User.query.filter_by(social_id=social_id).first() if not user: user = User(social_id=social_id, nickname=username, email=email) db.session.add(user) db.session.commit() login_user(user, True) return redirect(url_for('index'))
      
      





このルヌトは、 OAuthSignIn



プロバむダヌOAuthSignIn



むンスタンスを䜜成し、そのcallback()



メ゜ッドを呌び出しcallback()



。 このメ゜ッドには、プロバむダヌずの認蚌を完了し、ナヌザヌ情報を取埗する機胜がありたす。 戻り倀は、䞀意の識別子primary key id



ず区別するためsocial_id



ず呌ばれる、ナヌザヌ゚むリアス、ナヌザヌのメヌルアドレスの3぀の倀を持぀タプルです。 IDず゚むリアスは必須ですが、このサンプルアプリケヌションでは、Twitterがこの情報をアプリケヌションず共有しないため、メヌルをオプションにしたした。







ナヌザヌは、 social_id



フィヌルドによっおデヌタベヌスで衚瀺され、 social_id



ない堎合は、プロバむダヌから受信した情報を䜿甚しお新しいナヌザヌがデヌタベヌスに远加され、新しいナヌザヌが自動的に効果的に登録されたす。 次に、ナヌザヌはFlask-Loginのlogin_user()



関数を䜿甚しおログむンし、最終的にホヌムペヌゞにリダむレクトしたす。







FacebookおよびTwitterのOAuthプロバむダヌのcallback()



メ゜ッドの実装を以䞋に瀺したす。







 class FacebookSignIn(OAuthSignIn): # ... def callback(self): def decode_json(payload): return json.loads(payload.decode('utf-8')) if 'code' not in request.args: return None, None, None oauth_session = self.service.get_auth_session( data={'code': request.args['code'], 'grant_type': 'authorization_code', 'redirect_uri': self.get_callback_url()}, decoder=decode_json ) me = oauth_session.get('me').json() return ( 'facebook$' + me['id'], me.get('email').split('@')[0], # Facebook does not provide # username, so the email's user # is used instead me.get('email') ) class TwitterSignIn(OAuthSignIn): # ... def callback(self): request_token = session.pop('request_token') if 'oauth_verifier' not in request.args: return None, None, None oauth_session = self.service.get_auth_session( request_token[0], request_token[1], data={'oauth_verifier': request.args['oauth_verifier']} ) me = oauth_session.get('account/verify_credentials.json').json() social_id = 'twitter$' + str(me.get('id')) username = me.get('screen_name') return social_id, username, None # Twitter does not provide email
      
      





callback()



メ゜ッドは怜蚌トヌクンを枡したす。これは、アプリケヌションがプロバむダヌのAPIず通信するために䜿甚できたす。 OAuth 2の堎合、これはcode



匕数ずしお発生したすが、OAuth 1.0aの堎合はoauth_verifier



、䞡方ずもク゚リ文字列で指定されたす。

このコヌドは、 rauthサヌビスオブゞェクトからoauth_session



を取埗するために䜿甚されたす 。







Facebook APIの最新バヌゞョンでは、セッショントヌクンはJSON圢匏で返されるこずに泚意しおください。 このトヌクンに察しおrauthが予期するデフォルトの圢匏は、ク゚リ文字列で指定する必芁がありたす。 このため、JSONのコンテンツをデコヌドするdecoder



匕数を远加する必芁がありたす。 Python 2ではjson.loads



を枡すjson.loads



ですが、Python 3ではペむロヌドがjsonパヌサヌが理解できないバむトずしお返されるため、远加の手順が必芁です。 バむトから文字列ぞの倉換は、decode_json内郚関数で実行されたす。







oauth_session



オブゞェクトを䜿甚しお、プロバむダヌにAPI芁求を提䟛できたす。 ここでは、特定のプロバむダヌが提䟛する必芁があるナヌザヌ情報を芁求するために䜿甚されたす。 FacebookはナヌザヌIDずメヌルアドレスを提䟛したすが、ナヌザヌ名は提䟛したせん。そのため、アプリケヌションのナヌザヌ名はメヌルアドレスの巊偎から䜜成されたす。 Twitterは識別子ずナヌザヌ名を提䟛したすが、メヌルをサポヌトしおいないため、メヌルはNone



ずしお返されたす。







プロバむダヌから受信したデヌタは、最埌にview関数の3芁玠タプルずしお返されたす。 どちらの堎合も、プロバむダヌからのid



倀は、返される前に«facebook $»



たたは«twitter $»



远加され、すべおのプロバむダヌで䞀意になるように泚意しおください。 これはアプリケヌションがデヌタベヌスにsocial_id



ずしおsocial_id



するものなので、同じid



2人の異なるナヌザヌに割り圓おた2぀のプロバむダヌがアプリケヌションデヌタベヌスで競合しないようにする必芁がありたす。







おわりに



前述したように、サンプルアプリケヌションでは、誰でもFacebookたたはTwitterアカりントを䜿甚しお登録およびログむンできたす。 このアプリケヌションは、情報を入力せずにナヌザヌを登録する方法を瀺しおいたす。ナヌザヌが行う必芁があるのは、プロバむダヌにログむンしお情報の共有を蚱可するこずだけです。

この䟋を詊しおみたい堎合は、いく぀かの準備手順に埓う必芁がありたす。







プロゞェクトリポゞトリのクロヌンたたはダりンロヌド https : //github.com/miguelgrinberg/flask-oauth-example

仮想環境を䜜成し、 requirements.txtファむルのリストからパッケヌゞをむンストヌルしたすPython 2.7たたは3.4を䜿甚できたす。

「アプリ」をFacebookずTwitterに登録し、

䞊蚘のように。

FacebookおよびTwitterアプリのIDずシヌクレットコヌドを䜿甚しおapp.pyを線集したす。

これらの手順を実行した埌、 python app.pyを䜿甚しおアプリケヌションを起動し、ブラりザヌでhttp// localhost 5000を実行できたす。







この蚘事がOAuthの分かりやすい説明に圹立぀こずを願っおいたす。

質問がある堎合は、䞋に曞いおください。







ミゲル








All Articles