Kekeの日記

エンジニア、読書なんでも

OAuth2.0の仕組みとクライアントの作成

はじめに

今回はOAuth2.0の認可方式を取るGitHub APIを使って、クライアントの作成を行いたいと思います。

動機

自分がよく使うからにつきます。

Githubで、その1日のコミット数をSlackに投げたり、フル活躍しています。

せっかく使っているのなら、記事として、言葉として昇華させたいと思ったので本記事を執筆しようと思いました。

目次

1. OAuth2.0とは

1.1 ロール

このシステムでは、4つのロールがいます。

ロール 役割
リソースオーナー だいたいはユーザーである。
リソースサーバー アカウントをを持っているサーバーのことであり、アプリケーションをことを指すことが多い。
クライアント リソースを要求していて、ユーザーの代わりに取ってくるものである。一般的にはアプリケーションである。
認可サーバー(注意1) 認可をするためだけのサーバー。リソースと認可処理を分けたいなどのニーズからこのようなサーバーを持っていることが多い。

注意1: リソースサーバーが認可も同様に行う場合があって、そのような場合は兼ねているのでリソースサーバーとまとめる

1.2 ターム

システムを知る、開発していくっと言う中で出くわすいくつかの専門用語を解説します。

ターム 説明
認可コード 短時間で失効してしまう、アクセストークンを取得のために使用するコード、トークンのこと
アクセストークン 認可コードの有効性を検証されるともらえるリソースサーバにアクセスするためのトークン

1.3 ユースケース

一体、どんなときにこのようなシステムをわざわざ使うの?って思う人もいるかもしれません。

しかし、実際には世の中にありふれています。

StackOverFlow

ja.stackoverflow.com

CircleCI

circleci.com

などデベロッパー向けのもあれば、たとえば

Instagram

www.instagram.com

TweetDeck

tweetdeck.twitter.com

のように私たちの生活にはかなり浸透しているものもそうです。

2. 認可のフロー

OAuth2.0, Authorization Code Flowのスクリーンショットを参考に解説してきます。

https://camo.qiitausercontent.com/cbceb0f0e391aeeb9220c484838d0c13e730c75d/68747470733a2f2f71696974612d696d6167652d73746f72652e73332e616d617a6f6e6177732e636f6d2f302f3130363034342f64393131396632312d373336642d643565642d393634642d3330363861663066636465392e706e67

たとえばTwitterやFacebookなどSNSをまとめて表示してくれるサービスをアプリXYZとしましょう。

もちろんアプリXYZはTwitterやFacebookそのものではないので投稿をはじめとするコンテンツを取得しなければなりません。

そのような中で、「連携」と一般的に言われているような機能があるとします。

たとえばtwelogだと以下のようなページです。

https://syncer.jp/twitter-how-to-setting-connect-application/medias/twitter-how-to-setting-connect-application-info-01.png

このときに「サービスABCと連携をする」押したとしましょう。

ここで、サービスABCは、あなたがすでにアカウントを登録しているサービスのことです。 アプリXYZは、サービスABCの認可サーバーに、認可エンドポイントと認可リクエストを送信します。

すると例えば以下のようなサービスABCの認可画面に飛ばされます。

http://imgc.appbank.net/c/wp-content/uploads/2014/04/Howto-Twitter-App-Renkei-1.jpg

サービスABCのユーザー名やパスワードを答えると、その情報は認可決定エンドポイントに送信されます。

認可に成功すると、非常に短い間だけ有効な認可コードして、アプリXYZに返します。

そのコードをもってして、トークンエンドポイントに送信すると、アクセストークンを発行される。

そのアクセストークンを使って、リソースサーバのAPIを叩くことができる。APIを受信したリソースサーバーは、トークンの有効性を検証して、有効な場合のみリソースを返す。

以上のような流れになっています。

3. シェルスクリプトで作成してみる

あとあとの命名法にも関連してくるので、アプリケーション名はSGITとします。

今回は以下のような要件のクライアントを作成しようと思っています。

  • Githubの自分のプライベートリポジトリの情報を表示する

もちろんGithubのプライベートリポジトリはユーザの許可なしに見ることはできません。

なので、今回はチャレンジしようと思います。

また、先ほどのロールと対応づけると以下のようになります。

| ロール | 今回作るアプリ | |:----:|:----:| |リソースオーナー| ユーザー(自分 ) | | リソースサーバー | GitHubAPIサーバー| | クライアント | 今回のCLIツール | | 認可サーバー | GitHubAPIサーバー|

最大の注意点なのですが、私はfish shellを使っています。

3.0 スクリプトファイルを作成

以下のコマンドでスクリプトファイルを作成します。

公式チュートリアルは以下の通りです。

developer.github.com

まず、ファイル名を決めます。

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: どの部分まで権限があるかを確認する

全て知りたければ以下のリンクを参照してください。

developer.github.com

3.2.3 例

以下のようなリクエストを送ると

{
  "scopes": [
    "public_repo"
  ],
  "note": "admin script"
}

以下のように返ってきます。

{
  "id": 1,
  "url": "https://api.github.com/authorizations/1",
  "scopes": [
    "public_repo"
  ],
  "token": "abcdefgh12345678",
  "token_last_eight": "12345678",
  "hashed_token": "25f94a2a5c7fbaf499c665bc73d67c1c87e496da8985131633ee0a95819db2e8",
  "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があるのかは以下の一覧で確認することができます。

developer.github.com

本題に戻って実際に送ってみます。 実際には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"
}

ドキュメントにいってみて何をするべきか確認します。

f:id:bobchan1915:20180903051904p:plain

翻訳すると

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でも確認することができます。

f:id:bobchan1915:20180903054249p:plain

やっとアクセストークンを取得することができました。

3.4 アクセストークンを使ってAPIを叩く

さきほど取得したアクセストークンを使ってAPIを叩きます。

curl https://api.github.com/user/repos  -H 'Accept: application/vnd.github.v3+json' -H "Authorization: token $TOKEN"

すると取得できます。ここでは、レスポンスは表示されません。

まとめ

二段階認証をしてからは、端末が変わるごとに二段階認証コードを入れないといけなくなったので不便。

しかし、一度アクセストークンを取得できると、やりたい放題。

参考文献

qiita.com

qiita.com