OAuth2.0の仕組みとクライアントの作成
はじめに
今回はOAuth2.0の認可方式を取るGitHub APIを使って、クライアントの作成を行いたいと思います。
動機
自分がよく使うからにつきます。
Githubで、その1日のコミット数をSlackに投げたり、フル活躍しています。
せっかく使っているのなら、記事として、言葉として昇華させたいと思ったので本記事を執筆しようと思いました。
目次
1. OAuth2.0とは
1.1 ロール
このシステムでは、4つのロールがいます。
ロール | 役割 |
---|---|
リソースオーナー | だいたいはユーザーである。 |
リソースサーバー | アカウントをを持っているサーバーのことであり、アプリケーションをことを指すことが多い。 |
クライアント | リソースを要求していて、ユーザーの代わりに取ってくるものである。一般的にはアプリケーションである。 |
認可サーバー(注意1) | 認可をするためだけのサーバー。リソースと認可処理を分けたいなどのニーズからこのようなサーバーを持っていることが多い。 |
注意1: リソースサーバーが認可も同様に行う場合があって、そのような場合は兼ねているのでリソースサーバーとまとめる。
1.2 ターム
システムを知る、開発していくっと言う中で出くわすいくつかの専門用語を解説します。
ターム | 説明 |
---|---|
認可コード | 短時間で失効してしまう、アクセストークンを取得のために使用するコード、トークンのこと |
アクセストークン | 認可コードの有効性を検証されるともらえるリソースサーバにアクセスするためのトークン |
1.3 ユースケース
一体、どんなときにこのようなシステムをわざわざ使うの?って思う人もいるかもしれません。
しかし、実際には世の中にありふれています。
StackOverFlow
CircleCI
などデベロッパー向けのもあれば、たとえば
TweetDeck
のように私たちの生活にはかなり浸透しているものもそうです。
2. 認可のフロー
OAuth2.0, Authorization Code Flowのスクリーンショットを参考に解説してきます。
たとえばTwitterやFacebookなどSNSをまとめて表示してくれるサービスをアプリXYZとしましょう。
もちろんアプリXYZはTwitterやFacebookそのものではないので投稿をはじめとするコンテンツを取得しなければなりません。
そのような中で、「連携」と一般的に言われているような機能があるとします。
たとえばtwelogだと以下のようなページです。
このときに「サービスABCと連携をする」押したとしましょう。
ここで、サービスABCは、あなたがすでにアカウントを登録しているサービスのことです。 アプリXYZは、サービスABCの認可サーバーに、認可エンドポイントと認可リクエストを送信します。
すると例えば以下のようなサービスABCの認可画面に飛ばされます。
サービスABCのユーザー名やパスワードを答えると、その情報は認可決定エンドポイントに送信されます。
認可に成功すると、非常に短い間だけ有効な認可コードして、アプリXYZに返します。
そのコードをもってして、トークンエンドポイントに送信すると、アクセストークンを発行される。
そのアクセストークンを使って、リソースサーバのAPIを叩くことができる。APIを受信したリソースサーバーは、トークンの有効性を検証して、有効な場合のみリソースを返す。
以上のような流れになっています。
3. シェルスクリプトで作成してみる
あとあとの命名法にも関連してくるので、アプリケーション名はSGIT
とします。
今回は以下のような要件のクライアントを作成しようと思っています。
- Githubの自分のプライベートリポジトリの情報を表示する
もちろんGithubのプライベートリポジトリはユーザの許可なしに見ることはできません。
なので、今回はチャレンジしようと思います。
また、先ほどのロールと対応づけると以下のようになります。
ロール | 今回作るアプリ |
---|---|
リソースオーナー | ユーザー(自分) |
リソースサーバー | GitHubAPIサーバー |
クライアント | 今回のCLIツール |
認可サーバー | GitHubAPIサーバー |
最大の注意点なのですが、私はfish shell
を使っています。
3.0 スクリプトファイルを作成
以下のコマンドでスクリプトファイルを作成します。
公式チュートリアルは以下の通りです。
まず、ファイル名を決めます。
set -x FILE_NAME "hoge"
そしてファイルを作成したのちに、権限を与えます。
echo "#!/usr/local/bin/fish" > $FILE_NAME chmod +x $FILE_NAME
何も起きませんが、試しに実行してみてください。
./hoge
何もエラーがでなければ次に進んでください。
3.1 ユーザーに認証するかを確認する(オプショナル)
図にもあったように、最終的にはアクセストークンを使ってCLIツールがリソースを取得することになります。 つまり、アクセストークンを無くすことがあれば、再度取得する必要があるのです。
すでに連携している場合は「連携していますが」のようなフィードバックを、または「初期化を望みますか」といったようなことを返すのがユーザーエクスピリアンス(UX)のためには必要と思われます。
この機能はUXのためにあるので実装するかは任意ですので、飛ばしてもらってもかまいません。
今回はアクセストークンは環境変数として保存します。
他のアプリケーションがパソコン内にうじゃうじゃいることを考えると、以下のように命名します。
SGIN_ACCESS_TOKEN
つまり、この値がセットされていなかったら実行して、セットされていなかったら、今のところ実行されないようにします。
-n
が空文字であるかをチェックするので
if test -n $SGIN_ACCESS_TOKEN echo "Will create token" else echo "Already has one" end
のようにします。
実行すると以下のように返ってきます。
./shell/sgin Will create token
また、直接空文字であることも確認します。
echo $SGIN_ACCESS_TOKEN
3.2 認可エンドポイントにアクセスして、リダイレクトを行う
まずgithubの場合、以下の認可エンドポイントにアクセスしなければなりません。
https://api.github.com/authorizations
オプション--user
をつけてユーザー名を入力します。例えば--user=KeisukeYamashita
などです。
3.2.1 必須パラメータ
note
: なんのためのトークンを発行するつもりなのかを明記します。
3.2.2 オプショナルパラメーター
使いそうな主なパラメータを紹介します。
scopes
: どの部分まで権限があるかを確認する
全て知りたければ以下のリンクを参照してください。
3.2.3 例
以下のようなリクエストを送ると
{ "scopes": [ "public_repo" ], "note": "admin script" }
以下のように返ってきます。もちろんトークンなど実際のレスポンスではありません。
{ "id": 1, "url": "https://api.github.com/authorizations/1", "scopes": [ "public_repo" ], "token": "abcdefgh123456789", "token_last_eight": "123456789", "hashed_token": "25f94a2a5c7fbaf499c665bc73d67c1c87e496da8985131633ee0a9pdgwjt2e8", "app": { "url": "http://my-github-app.com", "name": "my github app", "client_id": "abcde12345fghij67890" }, "note": "optional note", "note_url": "http://optional/note/url", "updated_at": "2011-09-06T20:39:23Z", "created_at": "2011-09-06T17:26:27Z", "fingerprint": "" }
どのようなscope
があるのかは以下の一覧で確認することができます。
本題に戻って実際に送ってみます。
実際にはBasic認証をしているようなので-uオプションで指定します。つまり、--user=USERNAME:PASSWORD
が使えるというわけです。
すると二段階認証をしているので、以下のようなメッセージが返ってきました。
{ "message": "Must specify two-factor authentication OTP code.", "documentation_url": "https://developer.github.com/v3/auth#working-with-two-factor-authentication" }
ドキュメントにいってみて何をするべきか確認します。
翻訳すると
For users with two-factor authentication enabled, Basic Authentication requires an extra step.
もし二段階認証をしている場合は、追加のステップが必要です。
When you attempt to authenticate with Basic Authentication, the server will respond with a 401 and an X-GitHub-OTP: required; :2fa-type header. This indicates that a two-factor authentication code is needed。
もしBasic認証による認証を試行すると、サーバーは401を返して、そのレスポンスには
X-GitHub-OTP: required; :2fa-type
のようなヘッダーが付いている。これは二段階認証コードが必要なことを意味しています。...
あとはまとめると
- 2段階認証コードを
X-GitHub-OTP
ヘッダーに合わせて送る必要がある - Githubさんは二段階認証コードがすぐ失効するものなので、Authorizations APIを使ってアクセストークンを作ってそのトークンを使うことを推奨する
とのことです。
まず最初のものを行ってみると
curl --silent -u KeisukeYamashita https://api.github.com/authorizations -d '{"scopes":["public_repo"],"note":"Google Issues to GH"}' -H "X-GitHub-OTP: 936402"
HASH関数は一方向関数なのでhashed_token
を消す意味はそこまでないのですが、消しちゃいました。
{ "id": 216091729, "url": "https://api.github.com/authorizations/216091729", "app": { "name": "Google Issues to GH", "url": "https://developer.github.com/v3/oauth_authorizations/", "client_id": "00000000000000000000" }, "token": "TOKEN", "hashed_token": "HASED_TOKEN", "token_last_eight": "5f7466a4", "note": "Google Issues to GH", "note_url": null, "created_at": "2018-09-02T20:39:56Z", "updated_at": "2018-09-02T20:39:56Z", "scopes": [ "public_repo" ], "fingerprint": null }
と返ってきました。
GUIでも確認することができます。
やっとアクセストークンを取得することができました。
3.4 アクセストークンを使ってAPIを叩く
さきほど取得したアクセストークンを使ってAPIを叩きます。
curl https://api.github.com/user/repos -H 'Accept: application/vnd.github.v3+json' -H "Authorization: token $TOKEN"
すると取得できます。ここでは、レスポンスは表示されません。
まとめ
二段階認証をしてからは、端末が変わるごとに二段階認証コードを入れないといけなくなったので不便。
しかし、一度アクセストークンを取得できると、やりたい放題。