[2] [DFN[[RUBYB[失効エンドポイント]@en[revocation endpoint]]]]は、
[[アクセストークン]]や[[更新トークン]]を[DFN[[RUBYB[失効]@en[revocation]]]]させる
([DFN[revoke]] する) ための[[エンドポイント]]です。

* 仕様書

[REFS[
- [1] '''[CITE@en[RFC 7009 - OAuth 2.0 Token Revocation]] ([TIME[2014-12-21 18:10:21 +09:00]] 版) <http://tools.ietf.org/html/rfc7009>'''
- [10] [CITE@en[RFC 6749 - The OAuth 2.0 Authorization Framework]] ([TIME[2014-12-15 14:15:35 +09:00]] 版) <http://tools.ietf.org/html/rfc6749#section-3.1>
- [30] [CITE@en[RFC 6749 - The OAuth 2.0 Authorization Framework]] ([TIME[2014-12-15 14:15:35 +09:00]] 版) <http://tools.ietf.org/html/rfc6749#section-5.2>
]REFS]

* 意味

[3] [DFN[[RUBYB[失効要求]@en[revocation request]]]]は、
[[クライアント]]が自身の保持する[[アクセストークン]]や[[更新トークン]]を無効化することを[[認可鯖]]に求めるものです。 [SRC[>>1]]

[41] [[認可サーバー]]は、
指定された[[トークン]]を無効にすると共に、
関係する他の[[トークン]]や[[セッション]]データ等の関係するデータを消去できます [SRC[>>1]]。

[6] [[実装]]は、
[[更新トークン]]の[[失効][失効 (OAuth)]]に対応しなければ[MUST[なりません]] [SRC[>>1]]。

[7] [[実装]]は、
[[アクセストークン]]の[[失効][失効 (OAuth)]]に対応する[SHOULD[べき]]です [SRC[>>1]]。

;; [8] しかしながら、
[[実装]]が[[失効][失効 (OAuth)]]に対応すること自体は必須とはなっていません。
[[OAuth]] 本体に対応していても、[[失効][失効 (OAuth)]]には対応していない可能性があります。
また[[更新トークン]]を使わない[[実装]]は[[更新トークン]]の[[失効][失効 (OAuth)]]にも対応していない
(できない) かもしれません。

;; [39] [[更新トークン]]の[[失効][失効 (OAuth)]]により[[アクセストークン]]が[[失効][失効 (OAuth)]]されることは保証されませんし、
されたかどうか直接知ることもできません。
確実に[[失効][失効 (OAuth)]]したければ両方を明示的に[[失効][失効 (OAuth)]]する必要があります。

[EG[
[4] [[OAuth]] はしばしば [[Webアプリケーション]]の[[ログイン]]の仕組みとして使われます。
そのような[[Webアプリケーション]] ([[クライアント]]) は、
[[末端利用者]] ([[資源所有者]]) が[[ログアウト]]したり[[アンインストール]]したりした時に、
[[トークン]]を[[失効][失効 (OAuth)]]させることができます。 [SRC[>>1]]
]EG]

[5] [[トークン]]を[[失効][失効 (OAuth)]]させることで、
[[ログアウト]]等の後[[末端利用者]]が気づかないまま[[トークン]]が残ってしまうことを防げますし、
乱用されることも防げます。 [SRC[>>1]]

;; [42] [[クライアント]]が [[revoke]] するのは任意であり、[[末端利用者]]は [[revoke]]
したかどうか直接知ることができないので、[[クライアント]]が悪意を持っている場合の対策にはならず、
せいぜい流出した時の被害を抑えるくらいの役目しか果たしませんが...

[43] [[認可鯖]]が[[末端利用者]]に対して[[認可承諾]]の一覧を提示している場合には、
そこからも消去されるでしょう。 [SRC[>>1]] 使わなくなった[[クライアント]]が一覧に残り続けるよりは良さそうです。

[24] [[クライアント]]は、 [[revokeエンドポイント]]が [CODE(HTTP)[[[200]]]]
を返した後その[[トークン]]を使おうと試みてはなりません [SRC[>>1]]。

[26] なお、[[トークン]]はこの仕組み以外でも無効になることがあります。
[[クライアント]]は、いつでも[[トークン]]が無効にされる可能性を考慮しておかなければなりません
[SRC[>>1]]。例えば[[資源所有者]]が [[revoke]] するかもしれませんし、
[[認可鯖]]が自身の判断で無効化するかもしれません。

* クライアントの要求

[9] [[クライアント]]は、 [CODE(HTTP)@en[[[POST]]]] [[要求]]を使います [SRC[>>1]]。
[[JSONP]] を使う場合には [CODE(HTTP)@en[[[GET]]]] [[要求]]でも構いません [SRC[>>1]]。

;; [46] [[Google]] は [CODE(HTTP)@en[[[GET]]]] を使っています [SRC[>>45]]。
[[JSONP]] では無いようです。 [[RFC]] 以前からある独自の [[API]] かもしれません。
[REFS[
- [45] [CITE@ja[Using OAuth 2.0 for Web Server Applications - Google Accounts Authentication and Authorization — Google Developers]] ([TIME[2014-12-05 11:52:25 +09:00]] 版) <https://developers.google.com/accounts/docs/OAuth2WebServer#tokenrevoke>
]REFS]

[13] [[クライアント]]が [[revokeエンドポイント]]の [[URL]]
を得る方法は、 [[OAuth]] 仕様の範囲外です。[[鯖]]のドキュメントから調べても構いませんし、
自動的な[[発見]]の仕組みを使っても構いません。いずれにせよ信頼できる情報源に拠る必要があります。
[SRC[>>1]]

[14] [[revokeエンドポイント]]の [[URL]] は、 [[HTTPS]] でなければ[['''なりません''']]。
[[認可鯖]]は [[TLS]] を使わなければ[['''なりません''']]。
[[クライアント]]は [[HTTPS]] の [[URL]] であることを[RUBYB[検証]@en[verify]]しなければ[['''なりません''']]。
[[クライアント]]は偽の[[revokeエンドポイント]]を検出するため、[[証明書]]の検証などにより[[認証]]しなければ[['''なりません''']]。
[SRC[>>1]]

;; [63] もし偽の[[失効エンドポイント]]のつもりで偽の[[エンドポイント]]にトークンを送ってしまうと、
有効な[[アクセストークン]]や[[更新トークン]]を悪意ある者に送ってしまうことになり、
危険です。

[15] [[サーバー]]は、[[失効エンドポイント]]が[[素のHTTP]] でも利用できるなら、
そちらでの[[失効][失効 (OAuth)]]にも対応する[SHOULD[べき]]です。
しかしこれを[[失効エンドポイント]]の [[URL]] として出版しては[MUST[なりません]]。 [SRC[>>1]]

;; [16] こうすることにより、誤って[[素のHTTP]] 
で送信してしまったトークンも[[失効][失効 (OAuth)]]できます [SRC[>>1]]。

[11] [[revokeエンドポイント]]の [[URL]] は、
[CODE(MIME)@en[[[application/x-www-form-urlencoded]]]]
形式の [[query]] を含んでいても構いません [SRC[>>1, >>10]]。

[12] [[URL]] に[[引数]]を追加する時は、元の [[query]] を残さなければ[['''なりません''']]
[SRC[>>1, >>10]]。

[17] [[クライアント]]は [CODE(HTTP)@en[[[POST]]]] [[要求]]の [[payload body]] または
[[JSONP]] [CODE(HTTP)@en[[[GET]]]] [[要求]]の [[URL query]] に
[CODE(MIME)@en[[[application/x-www-form-urlencoded]]]] 
形式で次の[[引数]]を指定します [SRC[>>1]]。
[FIG(list members)[
[FIGCAPTION[
[[payload body]] または [[URL query]] ([CODE(MIME)@en[[[application/x-www-form-urlencoded]]]])
]FIGCAPTION]
:[18] [CODE(URI)@en[[[token]]]]:[[revoke]] したい[[トークン]]を指定しなければ[['''なりません''']] [SRC[>>1]]。
:[19] [CODE(URI)@en[[[token_type_hint]]]]:[CODE(URI)@en[[[token]]]] の種別のヒントです。
[[クライアント]]はこれを指定しても構いません。 [SRC[>>1]]
:[33] [CODE(URI)@en[[[callback]]]]:[[JSONP]] の場合に、 [[JavaScript]] [[関数]]の[[修飾名]]を指定します [SRC[>>1]]。
]FIG]

[20] [[クライアント]]は、[[クライアント認証]] (または認証できない場合には
[CODE(URI)@en[[[client_id]]]] [[引数]]) も含めます [SRC[>>1]]。


* 認可鯖の処理

[32] [[認可鯖]]は、 [[利用者エージェントベースのアプリケーション]]で使われるかもしれない場合には、
[[CORS]] に対応して構いません [SRC[>>1]]。

;; [44] 特別な [[CSRF]] 対策は必要無さそうです。

[21] [[認可鯖]]は、まず ([[機密]]の[[クライアント]]なら)
[[クライアントcredentials]]を[RUBYB[検証]@en[validate]]します [SRC[>>1]]。

;; [[クライアント認証]]参照。[[RFC]] には明記されていませんが、
整合性を考えると[[機密]]でなくても[[クライアントcredentials]]が発行されている場合には検証するべきと思われます。

[22] [[認可鯖]]は、次に[[トークン]]が当該[[クライアント]]に発行されたものか[RUBYB[検証]@en[verify]]します。
失敗した場合は、エラーを返して終わります。 [SRC[>>1]]

[37] [[RFC]] には明記されていませんが、[[引数]]の重複や欠落などがあれば、
エラーを返して終わるべきと思われます。

[23] [[認可鯖]]は、その後[[トークン]]を非妥当化します。非妥当化は直ちに行われ、
以後[[トークン]]は使えなくなります。実際には伝播遅延があるかもしれませんが、
できるだけ短期間で反映されるようにするべきです。 [SRC[>>1]]

[29] [CODE(URI)@en[[[token_type_hint]]]] の指定は、あまり意味がありません。

;; [CODE(URI)@en[[[token_type_hint]]]] 参照。

[25] [[認可鯖]]は、 [[revoke]] に関する方針次第で、
関連するトークンや元になった[[認可承諾]]をも [[revoke]]
して構いません。 [[更新トークン]]の [[revoke]] においては、
[[認可鯖]]が[[アクセストークン]]の [[revoke]]
にも対応しているなら、同じ[[認可承諾]]に基づくすべての[[アクセストークン]]をも
[[revoke]] する[['''べき''']]です。
[[アクセストークン]]の [[revoke]] においては、
対応する[[更新トークン]]も [[revoke]] して構いません。 [SRC[>>1]]

[27] [[認可鯖]]は、 [[revoke]] に成功したい場合や、
非妥当な[[トークン]]が指定されていた場合は、 [CODE(HTTP)[[[200]]]]
[[応答]]を返します [SRC[>>1]]。

[88] [[認可鯖]]は、誤りの場合には、 [CODE(HTTP)[[[400]]]] [[応答]]で次の[[引数]]を
[[JSON]] ([CODE(MIME)@en[[[application/json]]]]) [[payload body]] の 
[[JSONオブジェクト]]の名前と値 ([[文字列]]なら [[JSON文字列]]、
[[数値]]なら [[JSON数値]]) に含めます [SRC[>>1, >>30]]。
[FIG(list members)[
[FIGCAPTION[
[[payload body]] ([[JSONオブジェクト]])
]FIGCAPTION]
:[84] [CODE(URI)@en[[[error]]]]:[[誤り符号]]を指定しなければ[['''なりません''']] [SRC[>>30]]。
:[85] [CODE(URI)@en[[[error_description]]]]:[[人間可読]]な[[誤り]]の説明を指定して構いません
[SRC[>>30]]。
:[86] [CODE(URI)@en[[[error_uri]]]]:[[人間可読]]な[[誤り]]の説明を含む[[Webページ]]の [[URL]]
を指定して構いません [SRC[>>30]]。
]FIG]

[40] [[認可鯖]]は、 [[DoS攻撃]]に注意しなければ[['''なりません''']] [SRC[>>1]]。

[28] [[クライアント]]は、 [CODE(HTTP)[[[200]]]] [[応答]]の [[payload body]] を無視します 
[SRC[>>1]]。

[31] [[クライアント]]は、 [CODE(HTTP)[[[503]]]] [[応答]]を受信したらトークンはまだ有効と判断しなければならず、
適当な間を置いて再試行して構いません。[[鯖]]は [CODE(HTTP)@en[[[Retry-After:]]]]
で利用できないと思われる期間を示すことができます。 [SRC[>>1]]

[34] [[クライアント]]は、 [[JSONP]] の場合不正な [[revokeエンドポイント]]が不正なコードを注入する危険性があることに注意するべきです [SRC[>>1]]。

;; [35] そんなレベルで[[認可鯖]]が信用できない状況では [[OAuth]] 関連の処理がすべて危険そうですが...

* 歴史

[49] [[OAuth 1.0]] 本体には [[revoke]] [[API]] はありませんでした。

[53] [[OAuth Session Extension]] は [[revoke]] [[API]] を規定していました [SRC[>>52]]。

[47] [[Google]] は [[OAuth 1.0]] [[アクセストークン]]について、
[[AuthSub]] の [[revokeエンドポイント]]を使って [[revoke]] できる [SRC[>>48]]
としていました。

[51] [[Twitter]] は [[OAuth 2.0]] 向けに独自仕様の [[revokeエンドポイント]]を実装しています
[SRC[>>50]]。

[REFS[
- [52] [CITE@en[Implementers' Draft: OAuth Session 1.0 Draft 1]] ([TIME[2008-08-22 09:10:13 +09:00]] 版) <http://oauth.googlecode.com/svn/spec/ext/session/1.0/drafts/1/spec.html#rfc.section.7>
- [48] [CITE@ja[OAuth 1.0 API Reference - Google Accounts Authentication and Authorization — Google Developers]] ([TIME[2014-12-05 11:52:26 +09:00]] 版) <https://developers.google.com/accounts/docs/OAuth_ref#RevokeToken>
- [50] [CITE[POST oauth2/invalidate_token | Twitter Developers]] ([TIME[2015-03-05 11:07:26 +09:00]] 版) <https://dev.twitter.com/oauth/reference/post/oauth2/invalidate/token>
]REFS]

* メモ

[36] [[RFC 7009]] は [[OAuth]] 関連仕様の中でも特に品質が低いようです。
他の [[RFC]] を参照している箇所は章番号だけでどの規定が引用されているのか自明ではなかったりしますし、
[[事実の文]]による状況の説明ばかりで[[助動詞]]が含まれる規定の文は少なく、
前世紀レベルです。 [[JSONP]] に関する部分に至っては規定がほとんどなく例示に頼っています。

[38] [[RFC]] は未だに [[JSONP]] を考慮しているようですが、出版された2013年の時点ではぎりぎりまだ必要があったかもしれません。
現在では最早何の意味もなく、危険なだけなので [[CORS]] を使うべきでしょう。

[54] [CITE@en[OAuth2 · reddit/reddit Wiki]]
([TIME[2015-03-05 16:30:32 +09:00]] 版)
<https://github.com/reddit/reddit/wiki/OAuth2#user-content-manually-revoking-a-token>

[FIG(quote)[
[FIGCAPTION[
[55] [CITE@en-US[OAuth 2.0 Authentication | Viadeo API]]
([TIME[2015-03-05 17:54:09 +09:00]] 版)
<http://dev.viadeo.com/documentation/authentication/oauth-authentication/>
]FIGCAPTION]

> In order to revoke a token (i.e. remove the bridge between your application and Viadeo) use  https://secure.viadeo.com/oauth-provider/revoke_access_token2 with access token as parameter
> https://secure.viadeo.com/oauth-provider/revoke_access_token2?
> client_id=<YOUR_API_KEY>&
> client_secret=<YOUR_API_SECRET>&
> redirect_uri=http://www.example.com/oauth_redirect&
> access_token=<ACCESS_TOKEN_TO_REVOKE>
>  
> In response, you will get the following json response:
> {"token_revoked"}

]FIG]


[FIG(quote)[
[FIGCAPTION[
[56] [CITE@en[OAuth | Heroku Dev Center]]
([TIME[2015-03-06 08:08:09 +09:00]] 版)
<https://devcenter.heroku.com/articles/oauth#revoking-authorization>
]FIGCAPTION]

> Revoking an authorization will block associated tokens from making further requests.
> DELETE /oauth/authorizations/{authorization-id}

]FIG]


[FIG(quote)[
[FIGCAPTION[
[57] [CITE@en[Dropbox - Core API - endpoint reference]]
([TIME[2015-03-06 09:23:18 +09:00]] 版)
<https://www.dropbox.com/developers/core/docs>
]FIGCAPTION]

> /disable_access_tokenPythonJavaRubyPHP
> DESCRIPTION
> Disables the access token used to authenticate the call. This method works for OAuth 1 and OAuth 2 tokens.
> URL STRUCTURE
> https://api.dropbox.com/1/disable_access_token
> METHOD
> POST
> RETURNS
> An empty JSON dictionary, which indicates success.

]FIG]


[FIG(quote)[
[FIGCAPTION[
[58] [CITE@en[UserVoice OAuth Reference - UserVoice Developer]]
([[UserVoice]] 著, [TIME[2015-01-24 06:02:32 +09:00]] 版)
<https://developer.uservoice.com/docs/api/oauth/#_api_v1_oauth_request_token_>
]FIGCAPTION]

> Revoke	GET or POST	/api/v1/oauth/revoke.json

]FIG]


[FIG(quote)[
[FIGCAPTION[
[59] [CITE@en[Authentication | feedly Cloud API]]
([TIME[2015-01-23 08:10:58 +09:00]] 版)
<https://developer.feedly.com/v3/auth/#revoking-a-refresh-token>
]FIGCAPTION]

> refresh_token
> string The refresh token returned in the previous code.
> client_id
> string The clientId obtained during application registration.
> client_secret
> string The client secret obtained during application registration.
> grant_type
> string This field must contain a value of revoke_token.

]FIG]


[FIG(quote)[
[FIGCAPTION[
[60] [CITE@en[Strava API Authentication]]
([TIME[2016-04-15 06:01:09 +09:00]] 版)
<https://strava.github.io/api/v3/oauth/>
]FIGCAPTION]

> Deauthorization
> Allows an application to revoke its access to an athlete’s data. This will invalidate all access tokens associated with the ‘athlete,application’ pair used to create the token. The application will be removed from the Athlete Settings page on Strava. All requests made using invalidated tokens will receive a 401 Unauthorized response.
> Parameters
> access_token:	string required
> Responds with the access token submitted with the request.

]FIG]


[FIG(quote)[
[FIGCAPTION[
[61] [CITE@en-US[OAuth 2.0 Authentication | Viadeo API]]
([TIME[2015-03-05 17:54:05 +09:00]] 版)
<http://dev.viadeo.com/documentation/authentication/oauth-authentication/>
]FIGCAPTION]

> In order to revoke a token (i.e. remove the bridge between your application and Viadeo) use  https://secure.viadeo.com/oauth-provider/revoke_access_token2 with access token as parameter
> https://secure.viadeo.com/oauth-provider/revoke_access_token2?
> client_id=<YOUR_API_KEY>&
> client_secret=<YOUR_API_SECRET>&
> redirect_uri=http://www.example.com/oauth_redirect&
> access_token=<ACCESS_TOKEN_TO_REVOKE>
>  
> In response, you will get the following json response:
> {"token_revoked"}

]FIG]



[FIG(quote)[
[FIGCAPTION[
[62] [CITE@en[Strava Developers]]
([TIME[2018-11-15 07:15:08 +09:00]])
<https://developers.strava.com/docs/authentication/#deauthorization>
]FIGCAPTION]

> Applications can revoke access to an athlete’s data. This will invalidate all refresh tokens and access tokens that the application has for the athlete, and the application will be removed from the athlete’s apps settings page. All requests made using invalidated tokens will receive a 401 Unauthorized response.
> The endpoint is POST https://www.strava.com/oauth/deauthorize.
> access_token required string, in query	Responds with the refresh tokens that were revoked.
> 

]FIG]
