Graph API で Access Token を取得するフィルタを作成する（Flask)

## はじめに
PythonでFacebookの友人情報を使用したアプリを作成するにあたり、Graph APIの使い方を調べたのでまとめておきます。Flaskを使用したアプリケーションを前提としています。


## 認証コードを取得する
`https://graph.facebook.com/oauth/authorize?client_id=****&redirect_uri=****&scope=****` にリダイレクトすると、アプリが許可されていない場合には、アプリ許可用のページが表示されます。Flaskでは、このURLを指定して、`return redirect( https://**** )` とすればよいです。

| パラメータ | 設定内容 |
| :-: | :-: |
| client_id | Facebook Developersで設定したアプリケーションID |
| redirect_uri | 認証後にリダイレクトするURL (URIエンコードが必要) |
| scope | 要求するパーミッション |

許可されている場合には、`redirect_uri` に設定したリダイレクト先に遷移します。このときパラメータとして返却される `code`（認証コード）は、次のアクセストークン取得に使用します。また、何らかのエラーが起きた場合には、下記のように `error` が設定されたJSON文字列が返却されます。

```py:error_sample
{
    "error": {
        "message": "redirect_uri isn't an absolute URI. Check RFC 3986.",
        "type": "OAuthException",
        "code": 191,
        "fbtrace_id": "XXXXXXXXXXX"
    }
}
```


## アクセストークンを取得する
上で取得した認証コードを使って `https://graph.facebook.com/oauth/access_token?client_id=****&client_secret=****&redirect_uri=****&code=****` へアクセスし、アクセストークンを取得します。

| パラメータ | 設定内容 |
| :-: | :-: |
| client_id | Facebook Developersで設定したアプリケーションID |
| client_secret | Facebook Developersで設定したアプリのシークレットキー |
| redirect_uri | 認証後にリダイレクトするURL |
| code | 上で取得した認証コード (URIエンコードが必要) |

取得したデータは `access_token=****&expires=****` の形になっているので、`access_token=` 直後の値を取り出します。Pythonでは、`urllib2.urlopen` なんかを使用して結果を取得するといいと思います。抽出の仕方は適当に。

```py:main.py
from werkzeug.urls import url_quote_plus
import urllib2

...

app_id = "xxxxxxxx"
app_secret = "xxxxxxxx"
url_redirect = url_quote_plus( "https://hogehoge.com" )
code = "xxxxxxxx"

url_token = ( "https://graph.facebook.com/oauth/access_token"
              "?client_id={0}&client_secret={1}"
              "&redirect_uri={2}&code={3}" )

try:
    resp = urllib2.urlopen( url_token.format( app_id, app_secret, url_redirect, code ) )
    data = resp.read().split('&')
    access_token = data[0].strip('access_token=')

finally:
    resp.close()

...
```

これでアクセストークンの取得ができました。

## Flaskのフィルタにする
特定のページにアクセスしたときには、自動的にアクセストークンを取得するよう、Flaskのフィルタにしてしまいます。（下記サンプルでは、エラーが返却されたときの処理は省略しています。）
ちなみに、Pythonではこのようなフィルタをデコレータ？というらしい？この辺りは私自身まだよく分かっていません。

```py:main.py
from werkzeug.urls import url_quote_plus
from functools import wraps
from flask import Flask, request, redirect, session
import urllib2

...

domain = "https://hogehoge.com"
app_id = "xxxxxxxx"
app_secret = "xxxxxxxx"

# 認証コードの取得用URLを生成する --------------------------------------------------
def create_url_for_auth_code( path_orig ):
    url_orig = url_quote_plus( domain + path_orig )
    return ( "https://graph.facebook.com/oauth/authorize"
             "?client_id={0}&redirect_uri={1}&scope={2}" )

# アクセストークンを取得する --------------------------------------------------
def get_access_token( path_orig, code ):
    url_orig = url_quote_plus( domain + path_orig )
    url_token = ( "https://graph.facebook.com/oauth/access_token"
                  "?client_id={0}&client_secret={1}"
                  "&redirect_uri={2}&code={3}" )

    try:
        resp = urllib2.urlopen(url_token.format(app_id, app_secret, url_orig, code))
        data = resp.read().split('&')
        access_token = data[0].strip('access_token=')
    finally:
        resp.close()

    return access_token

# フィルタ本体 --------------------------------------------------
def oauth_filter(f):

    @wraps(f)
    def wrapper(*args, **kwargs):
        # セッションにアクセストークンがあれば、そのままリターン
        if session.has_key('access_token'):
            return f(*args, **kwargs)

        # セッションにアクセストークンがなければ、oauth認証して取得
        code = request.args.get('code')

        # リクエストパラメータの状態によって場合分け
        if code is None:
            # 認証コードが設定されていない場合、コードを取得する
            return redirect( create_url_for_auth_code( request.path ) )

        else:
            # 認証コードが設定されている場合、アクセストークンを取得する
            session['access_token'] = get_access_token( request.path, code )
            return redirect( domain + request.path )

        # 条件に一致しない場合には、そのまま抜ける
        return f(*args, **kwargs)

    return wrapper

...
```

## 各ルーティングへ適用する
上のフィルタを適用します。といっても、各ルーティングを指定しているメソッドに、`@oauth_filter` を追加するだけです。

```py:main.py
from flask import Flask, render_template, redirect, session
app = Flask(__name__)

...

# トップページには適用しない
@app.route('/')
def index():
    return render_template("top.html")

# マイページには適用する
@app.route('/mypage')
@oauth_filter # 適用！！
def mypage():
    return render_template("mypage.html")

# ログアウト時、セッションからアクセストークンを削除
@app.route('/logout')
def logout():
    if session.has_key( 'access_token' ):
        del session['access_token']
    return redirect( '/' )

...
```

## おわりに
以上で、ひとまず動くはずです。
本情報は、下記のGraph APIを使用したPHPサンプルと、Pythonログインデコレータの解説をもとに作成しました。

## 参考
* [アクセストークンを取得する (User Login Server-side Flow)](http://socialmedia.project0884.com/facebook/get_token_server.php)
* [Flaskのログインデコレータ](http://blog.setunai.net/20120227/flask%E3%81%AE%E3%83%AD%E3%82%B0%E3%82%A4%E3%83%B3%E3%83%87%E3%82%B3%E3%83%AC%E3%83%BC%E3%82%BF/)