Monday, January 2, 2017

OAuth2 and oauth2.el

1 はじめに

いろいろ調べても、なんだか複雑な OAuth2。 RFC、Google、Github などでちょっとづつ説明や言葉使い、サポートしている物が違うので、基本を理解していないと頭の中でまとまりませでした。また、OAuth2はいろいろな種類のアプリに対応しているので、OAuth2を理解していないと自分のアプリ用にどれを使えば良いのか判断すらできませんでした。

毎回使う時になると、以前にどうやって使っていたのか忘れているので、きっといつも全然理解せずに使っていたのだろうと思います。今回は RFC を読んで、少しは理解したような気がするので、将来の自分の為に書いてみようと思います。

2 Native アプリで利用する場合の基本

OAuth2 を Native アプリで利用する場合、基本は2つしかない (と言いきってみる)

  • Authorization Code を貰う
  • Authorization Code を使って Access Token を貰う

Access Token さえ手に入れば、API に投げつけて使える。これだけ。たったこれだけなのに、なんで世の中こんなにややこしく書いてんだ?!?という気分です。

ちなみに、 Github の Personal access token は、その名の通り 個人用の Access Token です。なので、 Authrization Code を貰ってから Access Token を貰うという手順をふまなくても、いきなり API に投げてやることができます。とても便利なんだけど、漏れたら悪用されちゃうので、危険です。

3 まずは概念的に説明してみる

3.1 Authorization Code を貰う

Authorization Code (Authz Code とも書く1) を貰うには

  • 貰う場所 (Authz Endpoint)
  • ID (Client ID)
  • 使いたい場所 (Scope)

が必要。

貰う場所

Authz code を貰う場所 (Authz Endpoint) を URL で指定します。

という具合です。

ID

Client の ID を指定します。これは事前に Authz Server に Client を登録して、IDを発行してもらわなければなりません。これは OAuth2 のルールです。

App を登録する Authz Server はサービスによって異なります。

という感じです。

使いたい場所

OAuth2 がステキなのは、どの部分の利用だけを許すかという Scope を決めれる部分です。

が参考になります。

3.2 Access Token を貰う

Authz Code を貰ったら、次は Access Token を貰います。 Access Token を貰う為に必要な情報は

  • 貰う場所 (Token Endpoint)
  • ID (client id)
  • Secret (client secret)
  • Grant Type (今回は authz code)
  • Authz Code (上で貰ったやつ)

です。

貰う場所
これは、良いですよね。 Authz Code でも貰う場所の指定がありました。どこで貰うのか分らないと、どうにもならないです。 今回は Access Token を貰うので、 Token Endpoint を指定します
Client ID
Authz Code を貰う時と同じです
Client Secret
これは、今回始めて出てきました。 Client Secret は Client のパスワードみたいなもので、 Client ID と一緒に使うことで Client 自体の認証を行います。 Authz Server に Client を登録する時に ID と一緒に貰えます
Grant Type
今回は Authz Code を使いたいので authorization_code と指定します。
Authz Code
最後に先程 Authz Endpoint から貰った Authz Code を渡します。

これで、Access Token と呼ばれる文字列を Token Endpoint から貰えます。

4 COLUMN: なぜ世の中のドキュメントは、ややこしいのか?

きっとややこしいのは、 Authz Code 以外にも implicit, password credencial, client credencial とか、色んな方法があるからじゃないだろうか? しかも、まだ理解できていない最初のうちに、どれかの選択を迫られる。今回は Native App に限定したので Authz code に特化して書くことができたが、他の種類のアプリを作っている人にはまるで参考にならない文書になってしまっている。

それともう一つ、Google の oauth2 のページには、手順が 3つで書いてある。これ、ぜんぜん嘘ではない。

  1. Obtain OAuth 2.0 credentials from the Google API Console.
  2. Obtain an access token from the Google Authorization Server.
  3. Send the access token to an API.

1番で事前の処理、2番で Access Token を貰うまでの方法、そして 3で実際の API 発行となっているので、本当に間違っていない。だけど、2番の部分が

  • 2.1 authz code を貰う
  • 2.2 access token を貰う

って書いてあったら、もっと簡単に理解できたような気がする。

ただ、先にも書いた通り Authz Code を使わない方法もあるので、このレイヤーでは、そう書けない。これが最初に読んだ時には分かりづらかった原因ではないだろうか?

この文書では、Google 手順の 2番について主に記載している。1番については、後半の「事前に貰うもの」で、3番については API を発行するだけの簡単な話なので割愛している。

5 こんどは oauth2.el で説明してみる

5.1 抽象度の高い関数だと、分りづらかった

やっと Emacs の話。 Emacs では oauth2.el というパッケージが ELPA から入手可能です。このパッケージの Commentary には、こう書いてあります。

The main entry point is `oauth2-auth-and-store' which will return a token structure. This token structure can be then used with `oauth2-url-retrieve-synchronously' or `oauth2-url-retrieve' to retrieve any data that need OAuth authentication to be accessed.

つまり、

メインで使う関数は oauth2-auth-and-store で、 token structure を戻しま す。戻ってきた token structure を oauth2-url-retrieve-synchronously や oauth2-url-retrieve と一緒に使うことで、データーが取れますよ

と。

これが、上で説明した2つの手順と繋がらなくて、自分は苦労しました。 oauth2-auth-and-store は、上記 2つの手順を一気に実施してくれて、さらに pstore に token を保存してくれる関数だと最初は理解できなかった。つまり Google 手順の 2番をまるっとやってくれる関数なんです。今思えば Google の手順とはぴったり合っているじゃないですか。

oauth2-auth-and-store は全部やってくれて、とても便利な関数になっているんだけど、初めて oauth する時は、まったく意味がわからなかった。特に code を取得し、access token を取得するという 2つの手順が見えていないと、全然使えなかった。

oauth2-auth (auth-url token-url client-id client-secret &optional scope state redirect-uri)

関数も、↑ずらーっと引数が並んでいるので、なにをどこに指定すれば良いのか、最初はまるで分かりませんでした。 ここまで読んで OAuth2 を理解できていれば、全然簡単なのですが…。

auth-url
Authz Endpoint
token-url
Token Endpoint
client-id
Client ID
client-secret
Client Secret

と、ここまでが必須

scope
ほぼ必須じゃないのかな。サービスの中でアクセスできる部分を指定するもの。今回は blogger サービスへのアクセスを例にしているので …. を指定する
state
今回は、使っていない。client の実装で、どこからコールしたのか分るようにするための文字列らしい。redirect されてくるときに、そのまま返してくれるらしい
redirect-uri
今回は、使っていない。そのうち http://localhost:port に対応したい

一度 OAuth2 を理解してしまえば、とても簡単なのですが。自分には抽象度が高すぎだったのと RFC や Google のドキュメントとのずれによって、理解するのに苦しみました。

5.2 Authorization Code を貰う

oauth2.el で Authz Code を貰う関数は、 oauth2-request-authorization です。

oauth2-request-authorization (auth-url client-id &optional scope state redirect-uri)
auth-url
Authz Endpoint
client-id
Client ID
scope
スコープ

という感じに、変数の名前の通りで、先の説明と一対一に整合が取れます。この関数を実行すると、 "4/mMf2ADq_XXXXXXXXXXXXXXXXXXXXXX" といった文字列を貰えます。これが Authz Code です。

5.3 Access Token を貰う

oauth2.el で Access Token を貰う関数は oauth2-make-access-request です。

oauth2-make-access-request (url data)
url
Token Endpoint
data
POST で投げる時に body に入れる文字列

この関数を実行すると Content-Type: application/x-www-form-urlencoded で POST してくれて、alist で情報が戻ってくるようになっています。

data と書いてありますが、中身は URL で使うような文字列を指定します。

  • client_id= に、Authz Server から貰った Client ID を繋いで
  • client_secret= にも、 Authz Server から貰った Client Secret を指定します
  • gran_type=authorization_code は、上でも説明した通り code を使っているので、この通りしてします
  • code= には oauth2-request-authorization で、貰った文字列を追記

という感じで、こちらも先の説明と一対一に紐付けることができます。

5.4 Token Structure

RFC でも token が出てきますが oauth2.el でも token structure なるものが出てきます。これ、なんのことはない、 Access Token や Token Endpoint などをまとめてくれている structure です。

defstruct oauth2-token しているので make-oauth2-token という コンストラクターが定義されていて、 oauth2-request-access の中で使われていたり oauth2-token-client-idoauth2-token-client-secret というアクセッサーも自動で定義されてます。

5.5 Refresh Token

Access Token を取得するまでに話を絞っていたので、 Refresh Token の話をしていませんでした。 Access Token を貰うときに、一緒に Refresh Token なるものも貰えます。これ、Access Token が expire した時にもう一度 Access Token を貰うための Token です。

Refresh Token を使って Access Token を貰うのは、最初に Access Token を 貰う Endpoint と同じ Token Endpoint で貰えます。 grant_typecode から refresh_token に変更します。

oauth2.el では自動で refresh してくれるようになっているので、気にしなくても問題ありません。

6 Authz Server から事前に貰うもの

Client ID も Client Secret も Authz Server から先に貰わないといけません。 先に Authz Server に Client を登録しないと ID すら発行してもらえないというのは OAuth2 の約束事です。Google 手順の一番目について簡単に説明しておきます。

Google の場合は Google API Console の Credentials から create credentials → OAuth client ID を選択します。 Application type を選択させられるので、作成しているアプリに合わせて適切なものを選ぶ必要があります。 Native アプリケーション や Emacs から使う場合には other を選べば良いです。名前を付けて、作成完了です。

Github の場合は Personal Setting の中の Developer settings の下に OAuth applications があります。ここで、Register a new application を押せば OK です。 Authorization callback URL は、ちゃんと設定してあげないと動作しなくなるので注意してください。他は、まぁ、適当でもなんとかなるはずです。

どちらもスクリーンショットを付けた方が分りやすくて良いと思いますが、サイトの見た目が変ると使えなくなるので、やめておきます。 OAuth2 について調べている時、古いスクリーンショットが数多く出てきて、まるで役に立たなかったので。 Client ID と Client Secret が必要で OAuth の Client を API Console で登録/作成する必要があると理解できていれば、 Google API Console の使い方は難しくないはずです。逆に Google API Console でなにをすれば良いのか理解していないと、スクリーンショットの通りにしか作業しかできず、ちょっと Google API Console の UI が変更になっただけで、このページ自体が無意味な物になってしまうと思います。

ところで、OAuth2 の client_id は OAuth1 では consumer key と呼ばれていました。まだ、たまに OAuth2 と言いながら consumer key と書かれている場所もあるようです。

7 おわりに

Clinet ID や Client Secret がないと Access Token が貰えないのはこの文章を読んでもらえれば分ると思います。

ところが、Google では Open Source のアプリには Client ID や Client Secret を入れてはいけないと記載されています。つまり、Open Source Application のユーザーは、みんな Client ID と Client Secret を自分で取得しないといけないということです。なぜでしょう?

Client ID と Client Secret は Client / Application を特定するために使う ID とパスワードみたいなものというのは先にも説明した通りです。そのため、Open Source のように簡単にソースを読めてしまう場合には、悪い人に ID と Secret を取られてしまう 可能性が高い からというのが Google 側の理由です。 もちろんバイナリー配布のアプリから ID や Secret を抜き出すことも多分可能です。しかし、ソースコードから抜き出すよりは敷居が高いと Google は判断しているようです。ID が悪用されて困るのは Client / Application の開発者なので、開発者自身を守るためにも ID や Secret をコード内に入れないで欲しいということでしょう。

この問題を解決するために OAuth 2.0 Dynamic Client Registration という RFC も進んでいるので、期待しています。

Github の personal access token は、使い方を間違えば危険だが、 API を使わせるという意味では Open Source Application User にも便利なような気がしますね。

8 ref

Footnotes:

1
authentification の authn と区別するんだよ