mixi Platformが導入したっていうOAuth 2.0のCSRF対策拡張を使ってみた

こんばんはこんばんは!!、ritouです。

木曜に公開されたこの記事を見て実際に試してみた/使ってみたってエントリ、たぶん誰も書いてくれないので自分で書きます。
OAuthのセキュリティ強化を目的とする拡張仕様を導入しました - mixi engineer blog

とりあえず上記の記事を読んで、最後の方のシーケンス図を覚えといてください。
では動作確認を始めましょう。

手順0 : サービスを登録してclient_id, client_secretの取得

もしかして、mixi Platformの挙動だけ確かめたい人にとってはここがハードル高いのか。まぁしょうがないです。
開発者登録している人はこちらからサービスを登録できますね。 https://sap.mixi.jp/connect_consumer.pl

例として、とりあえずこんな感じで登録してみます。

手順1:server_stateの取得

tokenエンドポイントにserver_stateを要求します。
(なんでtokenエンドポイントでserver_stateを返すのかっていうと答えに詰まりますが、余計な専用エンドポイント増やすのもあれかなと思っただけです。)

$ curl -i -d "grant_type=server_state&client_id=4f7511d31ca24d9d62c7" https://secure.mixi-platform.com/2/token
HTTP/1.1 200 OK
Date: Sat, 1x Nov 2013 xx:xx:xx GMT
Server: Apache
Cache-Control: no-store
X-MIXI-GRAPH-API-SPEC: 131072
Vary: Accept-Encoding
Content-Type: application/json
X-Content-Type-Options: nosniff
Connection: close
Transfer-Encoding: chunked

{"expires_in":1800,"server_state":"4RniCttOveIpCuZyYFF8Z-NlTqOY5cFvBnDLCsqwCkc"}

有効期限は30分です。

手順2:ユーザーのアクセス許可

こんなURLにアクセスします。
server_stateパラメータを含みます。

https://mixi.jp/connect_authorize.pl?
client_id=4f7511d31ca24d9d62c7&
response_type=code&scope=r_profile&
server_state=4RniCttOveIpCuZyYFF8Z-NlTqOY5cFvBnDLCsqwCkc

こんな感じの画面が出ますね。

アクセス許可すると、redirect_uriに戻ってきます。

http://localhost/callback?code=18866a25f0ec546f1702887e394c3905c85e97e4

これが認可応答です。

手順3:アクセストークン等を取得

codeパラメータを含む認可応答を受けたら、セッションに紐づけているserver_stateを一緒に送ってトークンを要求します。

$ curl -i -d "grant_type=authorization_code&client_id=4f7511d31ca24d9d62c7&client_secret=(秘密)&redirect_uri=http://localhost/callback&code=18866a25f0ec546f1702887e394c3905c85e97e4&server_state=4RniCttOveIpCuZyYFF8Z-NlTqOY5cFvBnDLCsqwCkc" https://secure.mixi-platform.com/2/token
HTTP/1.1 200 OK
Date: Sat, 1x Nov 2013 xx:xx:xx GMT
Server: Apache
Cache-Control: no-store
X-MIXI-GRAPH-API-SPEC: 131072
Vary: Accept-Encoding
Content-Type: application/json
X-Content-Type-Options: nosniff
Connection: close
Transfer-Encoding: chunked

{"refresh_token":"(秘密)","expires_in":900,"access_token":"(これ一番秘密)","token_type":"Bearer","scope":"r_profile"}

不正な組み合わせだとエラーが返されます。

  • codeにserver_stateが紐づいているが、指定されたserver_stateの値と異なる
  • codeにserver_stateが紐づいていないのにserver_stateの値が指定された
  • codeにserver_stateが紐づいているのにserver_stateの値が指定されていない

まぁ、これだけだったりします。
ここでPerlのライブラリの紹介です。

OAuth::Lite2だと簡単にできるよ

OAuth 2.0のServer/Clientの両方のための実装が詰まっているOAuth::Lite2っていうCPANモジュールがあります。
https://metacpan.org/release/OAuth-Lite2

上記の認可フローを利用するClientはOAuth::Lite2::Client::WebServerを使うと捗ります。
https://metacpan.org/pod/OAuth::Lite2::Client::WebServer
というのは、下記の処理が実装されているからです。

  • 認可要求URLの作成
  • アクセストークン要求
  • アクセストークン応答のパース

このOAuth::Lite2::Client::WebServer、最近get_server_stateっていうメソッドが追加されていて、それが上記のserver_state取得要求を実装しています。

なので、雰囲気としてはこんな感じで書くと動くはずです。

my $client = OAuth::Lite2::Client::WebServer->new(
    id               => q{my_client_id},
    secret           => q{my_client_secret},
    authorize_uri    => q{http://example.org/authorize},
    access_token_uri => q{http://example.org/token},
);
 
# 認可要求を送るとき
sub start_authorize {
    my $your_app = shift;

    # server_stateを取得
    my $server_state = $client->get_server_state()
    or return $your_app->error( $client->errstr );

    # 取得したserver_stateをセッションに紐づけておく
    $your_app->session->set_server_state($server_state);

    # extraに含めます
    my $redirect_url = $client->uri_to_redirect(
        redirect_uri => q{http://yourapp/callback},
        scope => q{photo},
        extra => {
            server_state => $server_state->server_state,
        },
    );
 
    $your_app->res->redirect( $redirect_url );
}
 
# 認可応答を受けてアクセストークンを要求するとき
sub callback {
    my $your_app = shift;
 
    my $code = $your_app->request->param("code");

    # セッションからserver_stateを取り出す
    my $server_state = $your_app->session->get_server_state();

    # access_tokenを取得するメソッドの引数に渡す
    my $access_token = $client->get_access_token(
        code         => $code,
        redirect_uri => q{http://yourapp/callback},
        server_state => $server_state->server_state,
    ) or return $your_app->error( $client->errstr );
 
    $your_app->store->save( access_token  => $access_token->access_token  );
    $your_app->store->save( expires_at    => time() + $access_token->expires_in    );
    $your_app->store->save( refresh_token => $access_token->refresh_token );
}

(これは$your_appとか書いてるのでコピペだけじゃ動きませんよ)
また今度時間のある時にデモClientとか作って紹介します。

まとめ

  • 使ってみた
  • 対応してるライブラリを紹介した

そういえば

今気づいたけど「カスタム URL スキーム上書き Authorization Code 横取り問題」の解決にもなってる?

2013-11-14

あるモバイルアプリからAuthorization Code Grantを使う認可フローを使う場合、

とかされて、正規のアプリ->アクセス許可->悪意のあるアプリとユーザーが遷移していくとAuthorization Codeからaccess_tokenとられてやられたわーなんていう可能性があります。
今回のserver_stateを使っていれば、Authorization Codeだけ取得してもaccess_tokenが取得できません。イイネ!

モバイルアプリだからclient_secret発行しない!っていってImplicit Grant提供しているところとかありますけれども、アクセストークン置換攻撃を気にするのであれば

  • server_stateの利用を必須にする
  • client_secretが空のAuthorization Code Grantを使う

とかにすると少しはマシになるのではとも思います。拡張仕様を考えるときにちょっとその辺を意識して、client_idだけでserver_state取得ができるようにしています。

ではまた!