読者です 読者をやめる 読者になる 読者になる

( ꒪⌓꒪) ゆるよろ日記

( ゚∀゚)o彡°オパーイ!オパーイ! ( ;゚皿゚)ノシΣ フィンギィィーーッ!!!

OAuthプロトコルの中身をざっくり解説してみるよ

「おーおーっすっ!」


てなこって、TwitterAPIBASIC認証も6月末に終了してOAuth/xAuthに移行するというこの時期に、あらためてOAuthについて勉強してみたんですのよ?


OAuth認証を利用するライブラリは各言語で出そろってきてるのでそれを使えばいんじゃまいか? というと話が終わるので、じゃあそのライブラリの中身はなにやってんのよってことを、OAuthするScalaのライブラリ作りながら調べたことをまとめてみました。


間違っているところもあると思うのでツッコミ歓迎です><

OAuthってそもそもなんなの?

ものすごくざっくりというと「API利用側が、ユーザ認証をAPI提供サービス側にやってもらうための仕様」って感じでしょうか?


BASIC認証の場合、API利用側が認証に必要なアカウントやパスワードを預かる必要があるわけです。悪意のあるAPI利用側が「なんとかメーカー」とかいうついったーのアカウントを利用するサービスを、BASIC認証を用いて実装した場合、その「なんとかメーカー」を使うユーザーのIDとパスワードを入力させて、サーバー上のデータベースに保存しておいてアカウントをハックするとか出来ちゃうわけです(インターネットこわい)。


つまり、BASIC認証はIDやパスワードが一時的にせよAPI利用側に預けられてしまうという問題です。


これを回避するために、アカウントの認証処理自体をついったーなどAPI提供側に代わりにやってもらい、API利用側は認証された結果だけをもらうという形にしたのがOAuthです。


図にするとこんな感じです。Service Provider(Provider)は、ついったーや4sqなどのAPI提供側。OAuth Consumer(Consumer)は、OAuthでProviderに認証してもらってAPIを呼び出す側(例えばふぁぼったー)。Userは、実際のアカウントを持っている人です(ようは、あなたです!)。


f:id:yuroyoro:20100506175544p:image


OAuthでは、アカウント認証でIDやパスワードを入力する箇所はついったーなどProviderのページに限定されますので、悪意のあるConsumerがIDパスワードを盗むことができなくなります。

認証のざっくりした手順

OAuth認証の詳しいフローはこことかこことかを参考にしてもらいたいのですが、ざっくり説明するとこんな感じです。


まずは、事前準備としてConsumerの登録が必要です。

  1. Consumer登録
    1. 通常は、事前にProviderConsumer登録をしておきます。
    2. 登録すると、Consumer KeyとConsumer Secretが発行されます。
    3. Consumer KeyとConsumer Secretは、以降のOAuth認証で使用します


ここからが、実際のOAuth認証の流れです。

  1. ユーザは、ConsumerにOAuth認証を行うように指示します。
    1. 通常は、Consumerのページにあるログインボタンなどをクリックします。
  2. Consumerは、Providerからリクエストトークンを取得します
    1. ConsumerからProviderへHttp通信でリクエストトークンを要求します。
    2. リクエストトークンの要求で、事前に発行されているConsumer Keyと、リクエストパラメータをConsumer Secretで署名した値をパラメータとして付与します。
    3. ProviderはHttpのレスポンスとしてリクエストトークンを返します。この段階では、まだ認証は完了していません。
  3. 認証用URLへのリダイレクト・ユーザ承認
    1. Consumerは、発行されたリクエストトークンをURLに付与して、Providerの認証用URLへリダイレクトを行います。
    2. リダイレクト先で、Providerがユーザに対して、Consumerが要求しているOAuth認証によるAPI利用を許可するか選択します。
    3. 承認した場合は、Providerは(通常は)Consumer登録時に設定したコールバックURLへリダイクレトします。
  4. アクセストークンの取得
    1. コールバックURLへのリダイレクトで、Consumerリクエストトークンをもとにアクセストークン取得をHttp通信でProviderへ要求します
    2. アクセストークン取得要求は、Consumer Keyとリクエストトークンなどをパラメータに付与して呼び出します。(通常はAuthorizationヘッダに設定)
    3. アクセストークン取得要求のパラメータも、Consumer Secretで署名した値を付与します。
    4. Providerは、レスポンスとしてアクセストークンを返します。
  5. OAuthでのAPI呼び出し
    1. 実際のAPI呼び出しは、取得したアクセストークンをパラメータに付与して呼び出します。
    2. アクセストークンとConsumer Key、およびパラメータをConsumer Secretで署名した値を、Authorizationヘッダに設定に設定してAPIを呼び出すことで、OAuth認証を利用したAPI呼び出しができます。


ざっくり図にするとこんな感じでしょうか?


f:id:yuroyoro:20100506190425p:image


まとめます。

  • APIを利用する側は、Consumerとして事前登録してConsumer KeyとConsumer Secretを取得しておく
  • OAuthでAPI呼び出しするにはアクセストークンが必要
  • アクセストークンは、Provider側でユーザーがリクエストトークンを承認することで発行される。
  • リクエストトークンは、ConsumerProviderにHTTP通信で発行を依頼する
  • リクエストトークンProviderから取得したら、Providerの認証ページへリダイクレトしてユーザに承認してもらう
  • ユーザが承認したら、ConsumerのコールバックURLへリダイクレトされるので、ここでアクセストークンの発行をConsumerからProviderへ依頼する
  • Consumerは、もらったアクセストークンをAuthorizationヘッダにつけてAPIを呼び出すことでOAuth認証下でのAPIコールができる

OAuthの認証からAPI呼び出しまで

じゃあ、実際のOAuth認証からAPI呼び出しまでの間に、Consumer/Provider/Userの3者間でどのようなやりとりが行われているのか解説しますね。

0.Consumer登録

まずは、ProviderにOAuthを利用するConsumerであることを登録します。Consumerを登録する際に設定するコールバックURLが、「2.認証用URLへのリダイレクト・ユーザ承認」でユーザが承認後にProviderからリダイレクトされるURLになります。


TwitterのConsumer登録ページ
foursquereのConsumer登録ページ


Consumer登録を行うと、Consumer KeyとConsumer Secretという二つの値が発行されます。


Consumer Keyは、「1.リクエストトークンの取得」や「3.アクセストークンの取得」や「4.API呼び出し」の呼び出しなど、OAuthに関する全てのProviderへのリクエストで必要とされるパラメータで、いわばConsumerのIDになります。


Consumer Secretは、OAuth通信で送信するパラメータが改竄されていないか確認するための署名を生成するために必要な、いわば秘密鍵です。
署名に関しては、「9.リクエストの署名」で解説します。

1.リクエストトークンの取得

OAuth認証は、Consumerからリクエストトークンの発行をProviderへ要求するところから始まります。


f:id:yuroyoro:20100506190427p:image


リクエストトークンの発行要求は、リクエストトークン発行用のProvider側のURLへ通常HTTP POSTを送信することで行います。
ここで、OAuthパラメータとして、以下のような値を設定します。

oauth_Consumer_key Consumer登録時に発行されたConsumer Key
oauth_timestamp リクエスト作成時のタイムスタンプ値
oauth_nonce リクエスト毎に一意な値。通常はナノ秒を設定
oauth_signature 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64エンコードする。詳しくは「9.リクエストの署名」を参照。
oauth_signature_method oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある
oauth_version 必須ではないが、設定する場合は1.0である必要がある


これらのパラメータを、HttpヘッダのAuthorizationヘッダにつけてPOSTします。具体的なリクエストヘッダの中身はこんな感じです。

POST /oauth/request_token HTTP/1.1
Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999


なお、AuthorizationヘッダではなくPOSTのBodyに設定してもOKな場合もあります。


さて、このPOSTのレスポンスとして、Provider側からレスポンスボディにリクエストトークンの値が設定されて返されます。

oauth_token=XYZABCD&oauth_token_secret=ZZZZZ
oauth_token これがリクエストトークンの値
oauth_token_secret リクエストトークン毎に発行される値。アクセストークン発行時の署名はこの値を含めたキーにより生成する必要がある。詳しくは「9.リクエストの署名」を参照。


ここまででリクエストトークンの取得が終わりました。

2.認証用URLへのリダイレクト・ユーザ承認

リクエストトークンが取得できたら、Providerの認証用URLへリダイクレトします。


f:id:yuroyoro:20100506191220p:image


通常のWebサービスでは、ユーザのOAuth認証ボタンのクリック時にリクエストトークンを取得して、そのレスポンスとしてユーザのブラウザにProviderの認証用URLへリダイクレトを返す形になるでしょう。


Providerの認証用URLへは、リクエストトークンをURLパラメータに含めたURLでのリダイレクトになります。たとえば、"http://foursquare.com/oauth/authorize?oauth_token=XYZABCD"のような形です。


リダイレクト先のProviderは、このConsumerからのOAuth認証要求をユーザが承認するか確認するページを返します。foursquerの場合はこんな画面です。


f:id:yuroyoro:20100506175548p:image


この画面で承認を行うと、ProviderはあらかじめConsumer登録時に設定されているURLへリダイクレトを行います。このときにパラメータとしてリクエストトークンの値と、場合によってはoauth_verifierという値が付与されます。


こんな感じのURLになります。

http://fooservice.com/?oauth_token=XYZABC&oauth_verifier=NNNNNN
3.アクセストークンの取得

さて、リダイレクトを受けたConsumerは、承認されたリクエストトークンを元にアクセストークンProviderから取得します。このアクセストークンが無いと、APIの呼び出しができません。


f:id:yuroyoro:20100506190429p:image


アクセストークンも、リクエストトークンと同じようにProvider指定のURLへHTTP通信を行うことで取得できます。


OAuthパラメータとして、以下のような値をAuthorizationヘッダに設定します。

oauth_token ユーザが承認済みのリクエストトークン
oauth_verifier リクエストトークンが承認された時にもらえるoauth_verifierの値
oauth_consumer_key Consumer登録時に発行されたConsumer Key
oauth_timestamp リクエスト作成時のタイムスタンプ値
oauth_nonce リクエスト毎に一意な値。通常はナノ秒を設定
oauth_signature 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64エンコードする。詳しくは「9.リクエストの署名」を参照。
oauth_signature_method oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある
oauth_version 必須ではないが、設定する場合は1.0である必要がある


リクエストトークン発行時のパラメータにくわえて、oauth_tokenとoauth_verifierが追加されています。


具体的なHTTPリクエストヘッダはこんな感じです。

POST /oauth/access_token HTTP/1.1
Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN


Provider側のレスポンスも、同様にレスポンスボディにアクセストークンの値が設定されています。

oauth_token=EFGHIJ&oauth_token_secret=WWWWW
oauth_token これがアクセストークンの値
oauth_token_secret アクセストークン毎に発行される値。以降のAPI呼び出し時には、この値を含めたキーにより署名を生成する必要がある。詳しくは「9.リクエストの署名」を参照。


これでようやくOAuthでAPIを呼び出す準備ができました。

4.API呼び出し

OAuth認証下でのAPI呼び出しは、アクセストークンなどのOAuth関連のパラメータをAuthorizationヘッダに設定したうえで、通常のAPIのコールと同様に行います。

f:id:yuroyoro:20100506190430p:image



OAuth認証とBasic認証API呼び出しの違いは、Authorizationヘッダの違いと言ってもよいかと思います。


例えば、foursquerのcheckinsというAPI呼び出しのリクエストヘッダは以下のようになります。

GET /v1/checkins HTTP/1.1
Host: api.foursquare.com
Authorization: OAuth oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN


具体的に、Authorizationヘッダに設定する値は以下の通りです。

oauth_token アクセストークンの値。
oauth_consumer_key Consumer登録時に発行されたConsumer Key
oauth_timestamp リクエスト作成時のタイムスタンプ値
oauth_nonce リクエスト毎に一意な値。通常はナノ秒を設定
oauth_signature 送信するURLやパラメータなどが改竄されていないか確認するためにConsumer側で生成された値。通常はHMAC-SHA1でConsumer Secretを元にダイジェストを生成しBase64エンコードする。詳しくは「9.リクエストの署名」を参照。
oauth_signature_method oauth_signatureを生成する署名方式。通常はHMAC-SHA1だが、PLAINTEXTをサポートするProviderもある
oauth_version 必須ではないが、設定する場合は1.0である必要がある
9.リクエストの署名

最後に、これまでProviderへの通信時に必ず付与する必要があった、リクエストの署名の生成方法を解説します。


ぶっちゃけ、ここ見てもらうのが早いんですがまぁ軽く書いてみます。


署名方式は、通常は、HMAC-SHA1という方式です。


署名は「(A)Consumer Secret及びToken Secret」をキーとして、「(B)HTTPメソッド、URL、全てのパラメータを連結した文字列」を元に作成されるダイジェスト値です。


まず「(A)Consumer Secret及びToken Secret」の署名キーですが、以下の形式の文字列をキーとします。

"Consumer SecretをURLエンコードした値"&"Token Secretの値"


Consumer SecretはConsumer登録時にProviderから発行される値、Token Secretは、リクエストトークンアクセストークンが発行されたときにProviderのHTTPレスポンスボディに含まれる値です。


Consumer Secret=XXXXでTokenSecretがNNNNの場合は、キーは"XXXX&NNNN"です。


「1.リクエストトークンの取得」の時点ではToken Secretはまだ無いので、Consumer Secretのみで署名キーを作ります。"XXXX&"のようになります。


次に「(B)HTTPメソッド、URL、全てのパラメータを連結した文字列」についてです。

"(a)HTTPメソッド"&"(b)アクセスするURL"&"(c)全てのクエリパラメータをキーの昇順でソートしURLエンコードした値"

(a)HTTPメソッドは、GET/POSTなどです。(b)は実際にアクセスするURL(http://foursquare.com/oauth/request_token など)です。


(c)全てのクエリパラメータをキーの昇順でソートしURLエンコードした値については、OAuthパラメータを含む全てのパラメータ(APIコール時のパラメータも含む)を、キーの昇順にソートした上で"キー1=値1&キー2=値2..."のように&で結合します。


例:

param1=value1&param2=value2&oauth_consumer_key=XXXX&oauth_nonce=1111&oauth_signature=YYYY=&oauth_signature_method=HMAC-SHA1&oauth_timestamp=9999&oauth_token=XYZABC&oauth_verifier=NNNNNN


最後に、(a)と(b)と(c)をそれぞれURLエンコードして"&"で結合します。これでできた文字列が署名対象です。


例:

POST&http:%3A%2F%2Ffoursquare.com%2Foauth%2Frequest_token&param1=value1%3param2=value2%3oauth_consumer_key=XXXX%3oauth_nonce=1111%3oauth_signature=YYYY=%3oauth_signature_method=HMAC-SHA1%3oauth_timestamp=9999%3oauth_token=XYZABC%3oauth_verifier=NNNNNN


実際の署名は(A)署名キーをもとに(B)署名対象文字列からHMAC-SHA1アルゴリズムを利用して16進のダイジェスト値を生成し、その値をBase64エンコードします。さらに、URLエンコードした文字列が署名としてoauth_signatureの値になります。


実際に生成したものがこれです。

8bP1EEnRivY3cDkYHcqaLN7+wRM=

ScalaでOAuthライブラリ書いてみた

てきとーじっそうです。
yuroyoro-util/src/main/scala/com/yuroyoro/util/net/OAuth.scala at master · yuroyoro/yuroyoro-util · GitHub


Scalaでは、Dispatchというのがあります。
Dispatch — Dispatch