janjan's blog

OAuth2.0の概要を理解する

Published: 2020/7/19
TOC

最近業務で利用していた AWS Cognito+Facebook での認証周りの挙動について調べることがあったのですが、調べるにあたりそもそも OAuth2.0 や OIDC を知らないと理解できないのでは?という気持ちになったので、これらのサービスやそれにまつわる認証部分について利用者側として最低限の知識を自分が調べた範囲でまとめます。今回は OAuth2.0 について、次回は OIDC についてまとめていこうと思います。

OAuth2.0 とは

OAuth2.0 は、RFC 6749で定義されており、以下のように要約されます。

The OAuth 2.0 authorization framework enables a third-party application to obtain limited access to an HTTP service, either on behalf of a resource owner by orchestrating an approval interaction between the resource owner and the HTTP service, or by allowing the third-party application to obtain access on its own behalf.

RFC 6749 Abstract より引用

日本語訳を以下の引用からみると、

OAuth 2.0 は, サードパーティーアプリケーションによる HTTP サービスへの限定的なアクセスを可能にする認可フレームワークである. サードパーティーアプリケーションによるアクセス権の取得には, リソースオーナーと HTTP サービスの間で同意のためのインタラクションを伴う場合もあるが, サードパーティーアプリケーション自身が自らの権限においてアクセスを許可する場合もある. 本仕様書は RFC 5849 に記載されている OAuth 1.0 プロトコルを廃止し, その代替となるものである.

OpenID Japan ページ内 Abstract より引用

となります。

簡単に言うとサードパーティーへの限定的なアクセスを可能にする認可フレームワークです。

従来のクラサバ型の認証モデルでは、クライアントからサードパーティアプリケーションにアクセスする際は、クライアントにサードパーティアプリケーションのクレデンシャルを入力してもらい、それを利用してリソースにアクセスしていました。

しかし、この認証モデルではセキュリティリスクの低下、サーバーがパスワード形式の認証方法をサポートしなければならないなどの問題があります。

例えば、自分が何かしらのチャットサービスを使っているとします。そのチャットサービスで、Google アカウントの連絡先に登録されている人をそのチャットサービスに登録する機能があります。 今利用しているチャットサービスは Google アカウントの情報を知らないので、どうにかして Google アカウントにアクセスする必要があります。このとき、チャットサービスがログイン ID とパスワードを入力してくれれば Google アカウントからアカウント情報をとってくる。となると、クライアントに Google のユーザー ID とパスワードが保存された状態となります。この状態でチャットサービスが情報漏洩をしてしまった場合や、チャットサービス自体が悪意のあるサービスだった場合、情報が第 3 者に漏洩してしまいます。

OAuth2.0 はこのような状況の時に、第 3 者へユーザー ID・パスワードを渡さずに、制限されたリソースにアクセスできる仕組みを提供します。ただし、OAuth2.0 では認証に関する部分は仕様として定められていないので、認証フレームワークではないことに注意してください。

認証フレームワークではないと記載しているのは、RFC 6749 3.1. Authorization Endpointに、以下の一文があるからです。

The way in which the authorization server authenticates the resource owner (e.g., username and password login, session cookies) is beyond the scope of this specification.

認可サーバーでのリソースオーナーの認証方法(ユーザー ID/パスワード、セッション、クッキー)は、仕様書のスコープ外です。 と記載されているからです。

OAuth2.0 のフロー概略

それでは、大まかな OAuth2.0 の概要を理解した上で、チャットサービスを例に Google アカウントのアクセス権を得るまでの流れと登場人物についての説明していきたいと思います。

OAuth2.0 に出てくる登場人物は以下の通りです。

  • リソースオーナー: 保護されたリソース(Google アカウント)へのアクセスを許可するエンティティー。ここでいう、自分のこと
  • リソースサーバー: アクセストークンを用いて保護されたリソースを提供することができる(レスポンスする)サーバー。ここでいう、Google アカウント(エンドポイント)のこと
  • クライアント: リソースオーナーの承諾を受けて、リソースサーバーにアクセスすることができるアプリケーション。ここでいう、チャットサービスのこと
  • 認可サーバー: リソースオーナーの認証と認可が成功したときに、アクセストークンをクライアントに発行するサーバー。ここでは登場していないが Google アカウント認証サーバーだと仮定します

次に基本フローについては説明していきます。下図を参照してください。

OAuth2.0 基本フロー

下図の中で注目したいところは、ステップ 2.ステップ 4. ~ ステップ 6.です。それぞれのステップでの役割は以下の通りです。

  • ステップ 2.: クライアントは認可エンドポイントへリクエストを投げます。すると、クライアントではなくユーザーエージェントを通じてユーザー(リソースオーナー)が認証情報の入力とスコープの許諾を行います。そのため、クライアントにクレデンシャルがわたらないようになっています。
  • ステップ 4.: 認可サーバーでユーザーの認証と認可を行い、認可コードを発行します
  • ステップ 5.: クライアントから認可サーバーのトークンエンドポイントへリクエストを投げます。このときステップ 4.で受け取った認可コードを一緒に渡します
  • ステップ 6.: 認可サーバーは認可コードの検証を行い問題がなければアクセストークンを返却します

では次に OAuth2.0 に記載されている 4 つのフローを見ていきたいと思います。

OAuth2.0 の認可フロー種別

OAuth2.0 では 4 つのフローが記載されています。また、 リフレッシュトークン は認証周りの機能を実装するときによく使われると思うので合わせて触れておきたいと思います。

認可コードフロー

認可コードフローは、認可コードを発行してもらい、その認可コードからアクセストークンとリフレッシュトークンの両方を取得するためのフローです。

(1) クライアントが認可サーバーの認可エンドポイントに認可リクエストを送ります。このときクライアントが送る認可リクエストは以下の通りです。

GET {endpoint url} HTTP/1.1
Host: {host}
Content-Type: application/x-www-form-urlencoded

response_type=code
client_id=xxxyyyzzz
redirect_uri=https://www.example.com/hoge/fuga
scope=hoge fuga
state=xyzxyz
namevalueattrdescription
response_type“code”REQUIRED認可コードフローの場合は codeで固定
client_idクライアント IDREQUIRED認可サーバーが発行したクライアント ID
redirect_uriリダイレクト URIOPTIONALリソースオーナーとのやりとりが完了した後に、認可サーバーはユーザーエージェントをクライアントへ誘導するために使用する URI。リダイレクト URI は、認可サーバーで事前に登録するか、クエリで指定する。
scopeスコープOPTIONAL保護されたリソースにのアクセスできる範囲を指定します。例えば、YouTube アカウントの表示でスコープを絞る場合は、 scope=https://www.googleapis.com/auth/youtube.readonly となります。scope はプロバイダーごとで異なるため、ドキュメントを読んで指定しましょう。
stateステートRECOMMENDED主に CSRF 対策のために使用されるクエリ。値はクライアントから認可サーバーへリクエストを送る際に、クライアントが発行するものであり、認可サーバーーはレダイレクトによりユーザーエージェントからクライアントに戻る時にこの値を含めます。

(2) 認可サーバーの認可エンドポイントは、受け付けたリクエストを元にリソースオーナーを認証し、認可を得ます。認証は、ユーザーエージェント(ブラウザ)を通じて行われます(画面でユーザー、パスワードなどの入力するイメージ)。

(3) (2)で認証・認可が行われると認証サーバーからクライアントにレスポンスを返します。レスポンスは以下の通りです。

HTTP/1.1 302 Found
Location: https://www.exmaple.com/hoge/fuga?

code=hogehogefugafuga
state=xyzxyz
namevalueattrdescription
code認可コードREQUIRED認可サーバーにより発行された認可コードです。認可コードは漏洩のリスクがあるため有効期限を設定する必要があります。RFC で推奨されている時間は 10 分です。また、認可コードが 2 回以上使用されたら、認可サーバーはリクエストを拒否する必要があります(トークン発行後はリフレッシュトークンで再発行ができるから、認可コードは 1 回しか使わないはず)。
stateステートREQUIRED(リクエストに含まれていた場合)認可エンドポイントのリクエストに含まれていた場合は必須です。返却する値は、リクエスト時の値となります。

(4) (3)で発行された認可コードを認可サーバーのトークンエンドポイントにリクエストします。このときクライアントが送るトークンリクエストは以下の通りです。

POST {endpoint url} HTTP/1.1
Host: {host}
Content-Type: application/x-www-form-urlencoded

grant_type=authorization_code
code=hogehogefugafuga
redirect_uri=https://www.example.com/hoge/fuga
client_id=xxxyyyzzz
namevalueattrdescription
grant_type“authorization_code”REQUIRED認可コードフローの場合は authorization_codeで固定
code認可コードREQUIRED認可サーバーが発行した認可コード ID
redirect_uriリダイレクト URIOPTIONALリソースオーナーとのやりとりが完了した後に、認可サーバーはユーザーエージェントをクライアントへ誘導するために使用する URI。リダイレクト URI は、認可サーバーで事前に登録するか、クエリで指定する。
client_idクライアント IDREQUIRED認可サーバーが発行したクライアント。ここではクライアントが認証されいない場合は必須となる。

(5) 認可サーバーは、トークンリクエストを受け付けたら、アクセストークンやリフレッシュトークンを発行しレスポンスとして返却します。このときのトークンエンドポイントのレスポンスは以下の通りです。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token": "aklsdjfalksjfasifkjfa",
  "token_type": "Bearer",
  "expires_in":3600,
  "refresh_token":"kasdfadfas8fa0wieafsdfaj",
  "scope":"hoge fuga"
}

トークンエンドポイントのレスポンス詳細は以下の通りです。

namevalueattrdescription
access_tokenアクセストークンREQUIRED認可サーバーにより発行されたアクセストークンです。
token_typeトークンタイプREQUIREDトークンのタイプを示します。詳細は7.1. Access Token Typesを参照してください。
expires_in有効期限RECOMMENDEDアクセストークンの有効期間(秒)です。推奨なのでどのような値が返却されるかはプロバイダーのドキュメントを読むのが良さそうです。
refresh_tokenリフレッシュトークンOPTIONALアクセストークンの更新の際に使用するトークンです。一般的に有効期限が長いアクセストークンをリクエストする時に使用します。
scopeスコープOPTIONALクライアントから同一のスコープが指定された場合は任意です。 それ以外は必須となります。

インプリシットフロー

インプリシットフローは、認可リクエストをすると、その応答としてアクセストークンを取得するフローです。こちらのフローでは、リフレッシュトークンの発行はサポートされていません。またセキュリティ的に安全ではないフローですので、必要がない場合以外は使用を避けた方が良いでしょう。 ※OAuth best practice 2.1.2

(1) クライアントが認可サーバーの認可エンドポイントに認可リクエストを送ります。このときクライアントが送る認可リクエストは以下の通りです。

GET {endpoint url} HTTP/1.1
Host: {host}
Content-Type: application/x-www-form-urlencoded

response_type=token
client_id=xxxyyyzzz
redirect_uri=https://www.example.com/hoge/fuga
scope=hoge fuga
state=xyzxyz
namevalueattrdescription
response_type“token”REQUIREDインプリシットフローの場合は tokenで固定です
client_idクライアント IDREQUIRED認可コードフローと同じなので割愛します
redirect_uriリダイレクト URIOPTIONAL認可コードフローと同じなので割愛します
scopeスコープOPTIONAL認可コードフローと同じなので割愛します
stateステートRECOMMENDED認可コードフローと同じなので割愛します

(2) 認可サーバーの認可エンドポイントは、受け付けたリクエストを元にリソースオーナーを認証し、認可を得ます。認証は、ユーザーエージェント(ブラウザ)を通じて行われます(画面でユーザー、パスワードなどの入力するイメージ)。

(3) (2)で認証・認可が行われると認証サーバーからリダイレクト URI を利用してクライアントに戻します。このとき URI には発行されたアクセストークンが含まれます。レスポンスは以下の通りです。

HTTP/1.1 302 Found
Location: https://www.exmaple.com/hoge/fuga#access_token=asdlkfajlskdfasdfalk&token_type=Bearer&...

access_token=asdlkfajlskdfasdfalk
token_type=Bearer
expires_in=3600
scope="hoge fuga"
state=xyzxyz
namevalueattrdescription
access_tokenアクセストークンREQUIRED認可サーバーにより発行されたアクセストークンです。
token_typeトークンタイプREQUIREDトークンのタイプを示します。詳細は7.1. Access Token Typesを参照してください。
expires_in有効期限RECOMMENDEDアクセストークンの有効期間(秒)です。推奨なのでどのような値が返却されるかはプロバイダーのドキュメントを読むのが良さそうです。
scopeスコープOPTIONALクライアントから同一のスコープが指定された場合は任意です。 それ以外は必須となります。
stateステートRECOMMENDEDリクエストに含まれている場合は指定が必要です

(4) クライアントはユーザーエージェントを redirect_uri を元にリダイレクト(フラグメントは除く)します。

(5) リダイレクト先からの返却は Web ページが返却されます(このとき返却される Web ページには埋め込みのスクリプトがあります)。Web ページはユーザーエージェントが保つ完全な URI にアクセスし、アクセストークンを取り出します。

(6) ユーザーエージェントはクライアントにアクセストークンを渡します

リソースオーナーパスワードクレデンシャルフロー

リソースオーナーパスワードクレデンシャルフローは、リソースオーナーのクレデンシャル(ユーザー ID/パスワード)を利用してアクセストークンを取得するフローです。こちらのフローは、クライアントがクレデンシャルを保持するため、リソースオーナーがクライアントを信用している、もしくはこのフローでしたアクセストークンを取得できない場合にのみ利用を止めるべきです。

(1) リソースオーナーはクライアントにユーザー ID とパスワードを提供します。RFC では、クライアントリソースオーナーからクレデンシャルを取得する方法は定めていません。しかし、アクセストークン取得後はクレデンシャルを破棄しなければなりません。

(2) クライアントは認可サーバーに、リソースオーナーから取得したクレデンシャルをリクエストに含めて、アクセストークンをリクエストします。このときクライアントが送るリクエストは以下の通りです。

POST {endpoint url} HTTP/1.1
Host: {host}
Content-Type: application/x-www-form-urlencoded

grant_type=password
username=user01
password=password01
scope=hoge fuga
namevalueattrdescription
grant_type“password”REQUIREDリソースオーナーパスワードクレデンシャルフローの場合は passwordで固定です
usernameユーザー IDREQUIREDリソースオーナーのユーザー ID を指定 します
passwordパスワードREQUIREDリソースオーナーのパスワードを指定します
scopeスコープOPTIONALその他と同じなので割愛

(3) 認可サーバーは、リソースオーナーのクレデンシャルを検証し、問題がなければアクセストークンを返却します。リフレッシュトークンの発行は任意となっています。アクセストークンリクエストのレスポンスは以下の通りです。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token": "aklsdjfalksjfasifkjfa"
  "token_type": "Bearer",
  "expires_in":3600,
  "refresh_token":"kasdfadfas8fa0wieafsdfaj",
  "scope":"hoge fuga"
}

クライアントクレデンシャルフロー

クライアントクレデンシャルフローは、クライアントと認可サーバーとの間に信頼関係があり、クライアントクレデンシャルのみでアクセストークンを取得するフローです。このフローでは、リソースオーナーの認証は行わずに、クライアントの認証のみが行われます。

(1) クライアントはアクセストークンリクエストにクライアントクレデンシャルを含めてリクエストする。このとき送信されるリクエストは以下の通りです。

POST {endpoint url} HTTP/1.1
Host: {host}
Authorization: Basic alksdjfalskdjfalsjdl
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
scope=hoge fuga
namevalueattrdescription
grant_type“client_credentials”REQUIREDクライアントクレデンシャルフローの場合は client_credentialsで固定です
scopeスコープOPTIONALその他と同じなので割愛

(2) 認可サーバーは、クライアントクレデンシャルを元にクライアントを認証し、認証に問題がなければアクセストークンを返却します。このときリフレッシュトークンは含めべきではないとされています。

HTTP/1.1 200 OK
Content-Type: application/json;charset=UTF-8
Cache-Control: no-store
Pragma: no-cache

{
  "access_token": "aklsdjfalksjfasifkjfa",
  "token_type": "Bearer",
  "expires_in":3600,
  "scope":"hoge fuga"
}

リフレッシュトークン

リフレッシュトークンは、クライアントが保持しているアクセストークンを更新する時に使用します。リフレッシュトークンは一般的には、有効期限の長い追加のアクセストークンを取得するために使用します。クライアントタイプがコンフィデンシャルの場合は、認可サーバーでクライアントの認証を行う必要があります。

トークンエンドポイントへのリクエストは以下の通りです。

POST {endpoint url} HTTP/1.1
Host: {host}
Authorization: Basic asdfjasldfjas
Content-Type: application/x-www-form-urlencoded

grant_type=refresh_token
refresh_token=falsdjflajdfal's
scope=hoge fuga
namevalueattrdescription
grant_type“refresh_token”REQUIRED認可コードフローの場合は refresh_tokenで固定です
refresh_tokenリフレッシュトークンREQUIRED認可サーバーにより発行されたリフレッシュトークン
scopeスコープOPTIONALその他と同じなので割愛

まとめ

OAuth2.0 は、サードパーティアプリケーションのクレデンシャルをクライアントに渡さずに、制限されたリソースへのアクセスが可能になる認可フローであるということがわかったと思います(一部フローは違いますが)。また、OAuth2.0 は認証フレームワークではないという点も重要となっていますので、間違いのないように気をつけましょう。

次回は OAuth2.0 を拡張した Open ID Connect についてまとめようと思います。

ここまで読んでくださり、ありがとうございました。本記事に関する指摘、意見等々はIssuesに記載いただければと思います。