OpenID Connectのセッションに関する3つの仕様について

おはようございます、ritouです。

OpenID Summit Tokyo 2015までもうちょいですね。
プログラムを見てみましょう。
OpenID Summit Tokyo 2015
仕様の話もありそうですね。
今回はRFCXXXXとして発行された系ではなく、OpenID Foundationで策定が進んでいるOpenID Connect関連の仕様を紹介します。
ドラフト仕様も、いろいろあります。
Specifications | OpenID

このうち、今回は次の3個の仕様をとりあげます。

  • Session Management – (Optional) Defines how to manage OpenID Connect sessions, including postMessage-based logout functionality
  • HTTP-Based Logout – (Optional) Defines an HTTP-based logout mechanism that does not use an OP iframe on RP pages
  • Back-Channel Logout – (Optional) Defines a logout mechanism that uses back-channel communication between the OP and RPs being logged out

この説明だけ見ると、みんなlogout言ってます。
これらがどう違うのかを確認していきましょう。

OpenID Connect Session Management 1.0 - draft 25

Draft: OpenID Connect Session Management 1.0 - draft 28

ざっくり書くと、

  • RPはAuthN Response(OAuthでいうところのAuthZ Response)でセッションに紐づく、session_stateという値を受け取る
  • RPはiframe + postMessageをつかって、OPにsession_stateを送り、状態変更があったかどうかを問い合わせる
  • RP始動なログアウトのときは、OPのログアウト用URL(end_session_endpoint)にリダイレクトする

という感じです。

そういえばずっと前に記事を書いた気がします。

ritou.hatenablog.com

この時点から、そんなに変わってないです。

OpenID Connect HTTP-Based Logout 1.0 - draft 03

Spec obsoleted by openid-connect-frontchannel-1_0

(Session Managementに対して)RP上にiframeを必要としないHTTPベースのログアウトメカニズムを定義するとあります。

  • RPはログアウト用エンドポイントとセッションIDが必須かどうかをOPに登録、OPからiframeでそのエンドポイントを呼び出されたらログインセッションをクリアする
  • OPはログイン状態のRPを"visited site"Cookieのようなもので管理しておき、ログアウト時にiframeでそれぞれのログアウトURIを呼び出すことでRPに通知する
  • Session Management同様、RP始動なログアウトのときは、OPのログアウト用URLにリダイレクトする

ということで、Session Managementと並行して実装することもできます。
iframe対応をしなくてもOPの状態変更を知ることができるのは便利そうですね。

OpenID Connect Back-Channel Logout 1.0 - draft 01

Draft: OpenID Connect Back-Channel Logout 1.0 - draft 04

HTTP-Based Logoutとは異なり、OP-RP(複数の場合もあり)間のバックチャンネルコミュニケーションを用いてログアウトさせます。
Session Managementなんかはiframeで問い合わせるページをユーザが開いていなければチェックができないなどUser-Agentがアクティブである必要がありますが、バックチャンネルだと気にしなくていいです。
その代わり、CookieとかHTML Strageとかでさくっと実装できる感じではないので、サーバサイドでセッションを切るような作りにしなければとか、よくあるFirewall/NATの話とかがIntroductionに書いてあります。

定義されているものは

  • OPはログイン状態のRPを覚えておく必要がある(セッションに対して、ユーザーとRP一覧を紐づけておく)
  • ID Tokenに似ているLogout Tokenと呼ばれるものがバックチャンネルで送られる。Logout Tokenのsub/sidあたりを
  • 上記2つの仕様と同様、RP始動なログアウトのときは、OPのログアウト用URLにリダイレクトして、OPに行った後はそのあとバックチャンネルの処理になる

という感じです。詳細は長くなるので省略します。

気になるこまけぇこと

  • Session StateでAuthN Responseで受け取るユーザのログイン状態はsession_state
  • 他の2つのLogout関連の仕様では、セッション識別子はsidとしてID Tokenに含まれる

用途としてはたしかに別なので、こうなる気持ちはわかります。
が、一般的にユーザがログアウトしたらセッションIDを破棄すべきという話もあるので、session_stateとsidは統一できそうな気がします。
渡し方については...これはちょっと考えないといけませんね...

まとめ

  • OpenID Connectのセッション関連の仕様は3つある
  • Session Management : iframeを用いたRP->OPのログイン状態の問い合わせ方とRP始動ログアウト時のOPへのリダイレクト
  • HTTP-Based Logout : RPはOPからiframeで対象エンドポイントが呼び出されたらログアウト処理
  • Back-Channel Logout : RPはバックチャンネルでlogout tokenを受け取ったらログアウト処理

なんとなく違いがおわかりいただけたでしょうか。
このあたりは、時間を見つけてデモ環境を作りたいと思います。

ではまた!

RFC7662として発行されたOAuth Token Introspectionとは

こんばんは、ritouです。
今回紹介する仕様は、RFC7662 OAuth Token Introspectionです。

ざっくり言うと

Token Introspectionとかいうと、Token置換攻撃対策としてのClient-AuthZ Server間のAccess Token検証を想像する方もいるかもしれませんが、この仕様はそれとはあまり関係ありません。

この仕様はOAuthのProtected Resourceのための仕様です。
RFC 6749 - The OAuth 2.0 Authorization FrameworkのRoleのところには、resource serverがprotected resourceをホストしていて、 access tokenを用いたリクエストを受ける、とあります。
簡単に言うと、APIサーバのことですね。

AuthZ ServerとResource Serverが分かれることはよくある話です。
小規模なところは、裏で同じDBを見に行くなどしてAccess Tokenの検証をすればよいわけですが、そこそこの規模のサービスだとか、ID統合など諸々の理由でそういうつくりができないこともあるでしょう。
また、最近はなんたらAPI Gatewayみたいなやつが最初にリクエストを受け、Resource Serverに来る前にAccess Tokenの検証ができるみたいな構成も聞きます。
この仕様はそういうのではなく、"Resource Serverが受け取ったAccess Tokenの状態をAuthZ Serverに問い合わせる"ような状況において、Access Tokenの送り方(問い合わせ方)を定義したものです。

ここからは、RFCの構成を意識しながら説明していきます。

課題

Introductionに書いてあります。

  • OAuth 2.0の仕様で、Access Tokenは、"The string is usually opaque to the client."となっている
  • Protected Resourceの処理には、Tokenが有効かどうかや紐づいたユーザー情報、認可に関する情報などが必要である
  • しかし、OAuth2.0ではProtected Resourceがそれらを知る方法が具体的に定義されていないので、JWTとか独自の内部処理とかでの対応が必要となる(ので、例えばこんな提案とかが出回るわけです→OAuth 2.0のAccess TokenへのJSON Web Token(JSON Web Signature)の適用 - r-weblife)

この仕様が定めること

  • Protected ResourceがClientから受け取ったTokenのメタデータをAuthZ Serverに要求する方法
  • メタデータはTokenの有効性や許可されたアクセス範囲、認可情報などを含む
  • Token文字列自体がメタデータを含んでいるかどうかにかかわらず、この方法を利用できる

Protected Resourceが扱うのはAccess Tokenですが、仕様上はAccess TokenだけではなくRefresh Tokenでもいいみたいです。

Introspection Endpoint

AuthZ ServerはProtected Resourceからの要求を受けるエンドポイントを用意する必要があります。

AuthZ ServerがProtected Resourceにこのエンドポイントをどうやって教えるかなどは仕様の対象外です。
まずはリクエストの説明からです。

Introspection Request

Protected Resourceは"application/x-www-form-urlencoded"形式で次のパラメータを持つHTTP POSTなリクエストを送ります。

token scanning attackを防ぐため、このエンドポイントにアクセスするためのProtected Resource - AuthZ Server間の認可が必須です。
この認可については仕様対象外なので何でもいいんですが、OAuth 2.0の仕様に準拠させた例が載っています。
説明すると逆に混乱しそうですが、"AuthZ Serverにとって、Protected ResourceはIntrospection endpointにアクセスしてくるClientである"という感じです。

# client authenticationと組み合わせた例
POST /introspect HTTP/1.1
Host: server.example.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Basic czZCaGRSa3F0MzpnWDFmQmF0M2JW

token=mF_9.B5f-4.1JqM&token_type_hint=access_token

# bearer tokenを用いる例
POST /introspect HTTP/1.1
Host: server.example.com
Accept: application/json
Content-Type: application/x-www-form-urlencoded
Authorization: Bearer 23410913-abewfq.123483

token=2YotnFZFEjr1zCsicMWpAA

次はレスポンスの説明です。

Introspection Response

レスポンスはJSON("application/json")形式です。

HTTP/1.1 200 OK
Content-Type: application/json

{
 "active": true,
 "client_id": "l238j323ds-23ij4",
 "username": "jdoe",
 "scope": "read write dolphin",
 "sub": "Z5O3upPC88QrAjx00dis",
 "aud": "https://protected.example.net/resource",
 "iss": "https://server.example.com/",
 "exp": 1419356238,
 "iat": 1419350238,
 "extension_field": "twenty-seven"
}

必須なのは、activeという値(boolean)で、Tokenが有効かどうかが入ります。
それ以外で気になるのがあったら仕様を読みましょう。
JWTのClaimについてはRFC 7519 - JSON Web Token (JWT)を読むといいでしょう。

Security Considerationsも流し読みしておきます。

Security Considerations

  • AuthZ ServerのTokenチェックをしっかりやろう
  • 前述したような攻撃を防ぐために、Protected Resourceの認証、Introspection Endpointへアクセスできるかどうかの確認をしっかりやろう
  • TLS/SSLのサーバーチェックとかしっかりやろう
  • AuthZ Serverのサーバーサイドのログからの漏えいを防ぐため、GETじゃなくPOST
  • キャッシュすると無効化の同期が...
  • Token文字列が構造化されてたらどうこう...

みたいなことが書いてあります。
実際こういうしくみを作ろうと思うと、負荷のあたりは悩ましげな感じですねー。

Privacy Considerations

レスポンスにセンシティブなプライバシー情報を含む場合があるのでほげほげ。

まとめ

  • Protected Resource(を持ってるResource Server)からAuthZ ServerへのToken情報の要求方法が定義されている
  • HTTP POSTのリクエストでTokenを送るときに、Protected Resource - AuthZ Server間の認証/認可が必要
  • レスポンスに"Tokenが有効かどうか"は必須

個人的には...小規模だとなかなか使わないし、すごい大規模になると負荷とかでもっと工夫しそうなので、異なる企業間をまたいだ疎結合なシステムのときにAPIでつなぐのかなーとか想像しちゃうところです。
ではまた!

RFC7636として発行されたOAuth PKCEとは

おひさしぶりです、ritouです。

今日は家で風邪治してましたが、TLに流れてきた次世代なんちゃらの話題に乗っかって、ざっくりとした仕様紹介です。

一言でいうと

この規格はOAuth 2.0 [RFC6749]のPublic Client の Code Interception Attack 脆弱性に対応するもので、ephemeral keyを生成して、これを使ったProof of Possession of Key をします。

OAuth PKCEがRFC7636として発行されました。 | @_Nat Zone

なんですが、RFCの構成を意識して書いていきます。

問題

RFCでは、Introductionに書いてあります。

OAuth 2.0 [RFC6749] public clients are susceptible to the authorization code interception attack.

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

Public Clientってのは、client_secretを安全に管理できないclientですね。
そういうclientはimplicitでは?って思われる人もいるかもしれませんが、落ち着きましょう。
Public ClientがAuthZ Codeを使う場合、次のようなフローになります。

  1. Clientは外部ブラウザを立ち上げるなりして、AuthZ Reqを送る
  2. AuthZ Serverに送られ、ログインとか何とかした後にAuthZ CodeがブラウザからClientに返される
  3. Clientは、AuthZ CodeとClient CredentialをAuthZ ServerのToken Endpointに送って、Access Tokenを取得する

これでPublic Clientの場合にどこがどうなっててどう危ないか、後ろから見ていくとわかるものです。

  • Access Tokenを取得するためには、AuthZ CodeとClient Credentialが必要!
  • Public Clientなので、Client Credentialを知ることができる。つまり、AuthZ CodeがあればAccess Tokenを取得できる!
  • もし、カスタムURIスキームとか使ってAuthZ Codeの受け渡しのときに攻撃者にAuthZ Codeが渡ったら...AccessTokenを取得される問題!

ということですね。

解決方法

こちらもIntroductionに書いてあります。

To mitigate this attack, this extension utilizes a dynamically
created cryptographically random key called "code verifier". A
unique code verifier is created for every authorization request, and
its transformed value, called "code challenge", is sent to the
authorization server to obtain the authorization code. The
authorization code obtained is then sent to the token endpoint with
the "code verifier", and the server compares it with the previously
received request code so that it can perform the proof of possession
of the "code verifier" by the client. This works as the mitigation
since the attacker would not know this one-time key, since it is sent
over TLS and cannot be intercepted.

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

ここでは翻訳したいわけじゃないので...

  • Clientはcode verifierっていう、動的に生成されるcryptographically random key(←妥協)を生成
  • code verifierは毎回AuthZ Req毎に生成される
  • Clientは(code verifierを変換した)code challengeというパラメータをAuthZ Reqに含み、AuthZ ServerのAuthZ Endpointに送る
  • AuthZ ServerはAuthZ Codeとcode challengeを紐づけつつ、Clientのredirect_uriにAuthZ Codeを送る
  • Clientは、AuthZ Codeとcode verifierを一緒にAuthZ ServerのToken Endpointに送る
  • AuthZ ServerはAuthZ Codeと紐づけておいたcode challengeとcode verifierの値を検証し、Access Tokenを返す
  • 攻撃者はcode verifier知らないのでAuthZ Codeを奪うだけではAccess Token取得できない

こんな感じです。

ここまでで大事なことは書いたので、後はざっと流しましょう。

用語

  • code verifier : 動的に生成されたcryptographically random stringであり、Token EndpointへのAccess Token Reqに含まれる
  • code challenge : code verifierから生成されたchallengeの値で、AuthZ EndpointへのAuthZ Reqに含まれる
  • code challenge method : code verifierからcode challengeを作る時の方法
  • Base64url Encoding : 省略

code verifierの生成

最小43文字、最大128文字の...

code_verifier = high-entropy cryptographic random STRING using the
unreserved characters [A-Z] / [a-z] / [0-9] / "-" / "." / "_" / "~"
from Section 2.3 of [RFC3986], with a minimum length of 43 characters
and a maximum length of 128 characters.

ABNF for "code_verifier" is as follows.

code-verifier = 43*128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

code challengeの生成

code challengeの生成方法について、plainとS256が記載されています。

plain
code_challenge = code_verifier
S256
code_challenge = BASE64URL-ENCODE(SHA256(ASCII(code_verifier)))

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

ABNF for "code_challenge" is as follows.

code-challenge = 43*128unreserved
unreserved = ALPHA / DIGIT / "-" / "." / "_" / "~"
ALPHA = %x41-5A / %x61-7A
DIGIT = %x30-39

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

ClientはS256できるならS256使う。どうしようもなかったらplain。

AuthZ Reqへの追加パラメータ

code challengeとmethodを指定します。

code_challenge
REQUIRED. Code challenge.

code_challenge_method
OPTIONAL, defaults to "plain" if not present in the request. Code
verifier transformation method is "S256" or "plain".

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

AuthZ Resについて

AuthZ ServerはAuthZ Codeとcode_challenge, code_challenge_methodは返してはいけません。
method=plainだったらcode verifierそのものですからね。

Access Token Reqへの追加パラメータ

code_verifier
REQUIRED. Code verifier

RFC 7636 - Proof Key for Code Exchange by OAuth Public Clients

AuthZ Serverの検証について

  • code_challenge_methodの値に従って、code_verifierそのままもしくはs256した値とcode_challengeの値を比較する

Security Considerations

は、読んでおきましょう(疲れた)

まとめ

  • Public ClientからAuthZ Code使ってAccess Tokenを取得する場合のお話
  • Clientは毎回動的にcode verifierを生成する
  • これ使うと、redirect_uriの扱いが不安なPublic Clientでも、安全にAccess Tokenの取得が行えそう

文章だけでまだわからんので絵が欲しい って場合は、この記事をTweetしたついでに一言いただけると対応できるかもしれません。
ではまた!

Auth0のドキュメントに書いてあったMQTTをWeb APIっぽく使うための工夫

どーも、ritouです。

一言でいうと、MQTTでもTokenを使えそうというだけの話

MQTTに関して、様々な言語のライブラリや、他の機能と連携するプラグインなどもよく見かけます。
詳細な解説記事からさくっと使って「簡単でした!」みたいな記事までたくさん出回っておりますが、その中で、MQTTのAuthN&AuthZに着目した内容に触れているドキュメントを見つけました。
https://auth0.com/docs/scenarios/mqtt

Auth0ってのはIdentity as a Service、クラウドな認証基盤なサービスなんて紹介されたりしてるサービスです。
で、上記のドキュメントは”Authenticating & Authorizing Devices using MQTT with Auth0” というタイトルのとおり、ざっくり言うと

  • CONNECT時のusername/passwordとAuth0を用いて接続元デバイスの検証を行う = Authentication
  • PUB/SUB時にAuth0を用いて接続元デバイスとTopicの組み合わせの検証を行う = Authorization

という感じで、2種類の方法が紹介されています。

  • (1) MQTT Clientは普通の使い方、MQTT BrokerがAuth0に問い合わせる

  • (2) MQTT ClientはAuth0からTokenを取得し、"username/password"のところに"'jwt'/(取得したToken文字列)"を指定し、BrokerはTokenの検証を行う

(2)は個人的に大好物であるところのOAuthなWeb APIっぽい使い方に見えます。
username/passwordのところに違う値をぶっこむというやや強引なやり方ですが、Tokenの検証などについては前に書いたやつと同じ感じの設計でいけそうです。
OAuth 2.0のAccess TokenへのJSON Web Token(JSON Web Signature)の適用 - r-weblife

Moscaはこのあたりを試すのに便利

そもそもMQTT Brokerをしっかり実装しようとすると大変そうだし、さらにTokenを使うように拡張なんて...と思われるのはないでしょうか。
Auth0は、Node製のMQTT BrokerであるMoscaを利用しています。
Moscaでは、AuthN/AuthZに関連する機能を拡張しやすいつくりになっています。

With Mosca you can authorize a client defining three methods.

  • #authenticate
  • #authorizePublish
  • #authorizeSubscribe

Those methods can be used to restric the accessible topics for a specific clients.

https://github.com/mcollina/mosca/wiki/Authentication-&-Authorization

使い方はAuth0Moscaのサンプルを見るとわかりやすいです。

var auth0 = new Auth0Mosca(...);

//Setup the Mosca server
var server = new mosca.Server(...);

//Wire up authentication & authorization to mosca
server.authenticate = auth0.authenticateWithJWT();
server.authorizePublish = auth0.authorizePublish();
server.authorizeSubscribe = auth0.authorizeSubscribe();

Productionで使うとなると「接続/Pub数の制限とか、もっといろんな独自処理を挟みたい vs パフォーマンスがー問題」とか、いろいろ出てくるんでしょうけども、ちょっと試すには便利そうです。

まとめ

  • Auth0が出しているMQTT利用シナリオにあった、Tokenを用いる方法がWebAPIっぽいなーと思って紹介した
  • そのあたりの拡張が簡単にできるMoscaは、ちょっと遊ぶには便利そうな印象

そういえば、BaaSにもサーバーサイドスクリプトってのがあります。
MQTT as a Serviceを提供するようなところでもこのあたりの拡張ができて自サービスで発行したOAuthのAccess Tokenなどが使えると、MQTT Broker自体のパフォーマンスを下げずに既存のサービスとの親和性も高められてさらに便利かなーと思いました。

ではまた!

OAuth 2.0のAccess TokenへのJSON Web Token(JSON Web Signature)の適用

こんばんは、ritouです。

久々の投稿な気がしますが、今回はOAuth 2.0のリソースアクセス時の設計の話です。
ずーっと前から書こうと思いつつ書いてなかったので、ここに書いておきます。
出てくる用語や仕様は、下記の翻訳リンクを参照してください。

想定する環境

わりとよくある環境を想定しています。


  • OAuth 2.0で認可サーバーとリソースサーバーがある
  • 認可サーバーがAccess Tokenを発行
  • リソースサーバーがAPIリクエストに含まれるAccess Tokenを検証する

よくある実装とその悩みどころを、JSON Web Token(JSON Web Signature)により軽減できるかもという話です。

よくある実装 : Access Tokenに一見ランダムな文字列を利用、DBなりで紐付け

パフォーマンスとか無視して素直に実装しようとするとこんな感じになるのではないでしょうか?

認可サーバー
  • 認可情報を(DBなどに)保存 : user_id, client_id, scope, 有効期限など
  • Access Tokenはランダムっぽい文字列を利用して、それらと紐付けられる
リソースサーバー
  • Access Tokenの文字列を利用してDB接続や認可サーバーへのHTTPリクエスト、その他いろいろな手段でAccess Tokenに紐づく情報を問い合わせる : Access Tokenが有効であることを確認

認可サーバーにHTTPなどで問い合わせる感じだと、こんな図になるでしょう。

この場合、基本的に受け取ったAccess Tokenに対して全て問い合わせが走るので、どうしても負荷がきつくなりがちでしょう。
無駄な問い合わせを省略するためには、Access Tokenにタイムスタンプ含めるとか、文字列のフォーマットをチェックするとかはできるかもしれませんが、規模が大きくなると辛いのかなと思ったりします。

提案 : JWT(JWS)形式のAccess Tokenに情報を詰め込む

JWTを用いて、リソースサーバーから認可サーバーへ問い合わせを減らせるのでは?という提案です。

認可サーバー
  • 認可情報を(DBなどに)保存 : user_id, client_id, scope, 有効期限など
  • "認可情報もしくはAccess Tokenに対して一意"なtoken_id的なものを払い出し、認可情報とは別で有効なtoken_idを管理しておく
  • 認可情報とtoken_idを含むJWT(署名つきなので正確にはJWS)をAccess Tokenの値とする
  • 署名には認可サーバーの秘密鍵を利用(alg=RS256とか)し、リソースサーバーには公開鍵を配布するなどして有効な公開鍵を確認できるようにしておく
リソースサーバー
  • Access Tokenの署名検証を行う : 認可サーバーが発行した文字列であることを確認
  • 有効期限を確認する : 有効期限内のAccess Tokenであることを確認
  • JWTに含まれるtoken_idが有効かどうかを認可サーバーに問い合わせる : 何らかの事情でAccess Tokenが無効化されていないことを確認

AccessTokenの有効期限を短くして最後のtoken_idのチェックをスキップみたいな話もあるが、今回は割愛します。

メリット/デメリット

メリット
  • リソースサーバーは無効なAccess Tokenを署名検証 + 有効期限チェックである程度判定できる
  • DBや認可サーバーも、token_idの管理だけできればいいので負荷が抑えられる
デメリット
  • リソースサーバー側で行う処理が増える。この手の処理はApacheモジュールとか、WAFのプラグインとかでやれるしくみを用意すると幸せになれそう
  • JWT使うとAccess Tokenの文字列は長くなる

Access TokenのPayloadサンプル

JWTのPayloadに含む最低限の値はこんな感じかと。token_idなんかはHeaderでも良いかも。
3文字ルールの中にtoken_idとかscopeとか入れるの気持ち悪かったら何でも良いですし、構造化されててもかまわないと思います。

{
 "sub": "(リソースオーナーのユーザー識別子)",
 "token_id": "(認可情報 or Access Tokenに対して一意な識別子)",
 "aud": "(client_id)",
 "exp": (有効期限のタイムスタンプ),
 "scope": "(このAccess Tokenに関連付けられたScope)"
}

まとめ

  • OAuth 2.0のリソースアクセスの部分で、ベタに実装すると負荷とかきつくなりそうな部分をJWTを用いて改善できるかもという話を書いたつもり
  • 昔からWebサービスのSession Cookieなどでこ独自encode+signatureとか使っているところはあったと思うし、SessionをCookieに保存するしくみがあるが、それにも適用可能(最近話題のmicroservicesとかでOAuth 2.0のAccess Tokenをサービス内で使ってるところとかも)

参考になりそうな資料

ではまた。

YAPC::Asia Tokyo 2014にてソーシャルログインの話をしました

おはようございます ritou です。

去年 に引き続き、今年も話す機会をいただいたので行ってきました。
2回目っていうのと、私が師と仰ぐlyokato氏が一日目にMQTTからなんやらかんやらの話でオーディエンスにツッこむ隙を与えない非常に良い雰囲気を醸し出していたのを見ていたので、わりと落ち着いて望むことができました。

内容

OAuth/OpenID Connectを用いてID連携を実装するときに気を付けること - YAPC::Asia Tokyo 2014

  • ソーシャルログイン実装の際の細かい話
  • パスワード認証してるとこが導入する際に考えること
  • というか、考えたこと

あ、OpenID Connectの話はしなかったですね。
反応はあんまり聞いてないんでわかりませんが、延々とシーケンスとパラメータの説明よりは身近な感じになったのではと思ったりしてます。

スライド

OAuth/OpenID Connectを用いてID連携を実装するときに気を付けること #yapcasia - Speaker Deck

けっこう、はてブつくもんなんすね?
スライドはSpeaker Deckにあげましたが、Slideshareのソーシャルログインの挙動で一部気になってることがあるので後で調べてブログ書こうと思います。

その他

今年はいろいろ話を聞くことができましたが、みなさんおっしゃっているように内容も幅広い感じで、プログラミングが得意ではない自分でもエンジニアがんばろうと思える感じの内容でした。
挨拶できなかった方もいたので、今後も少しずついろんなところに顔を出そうと思います。

OpenIDファウンデーション・ジャパンで講師派遣がどうこういってたやつは、これです。
社内勉強会に講師を派遣します | 事務局ブログ | OpenID ファウンデーション・ジャパン

ではまたー。

Yahoo!ウォレット Fastpayのようなクレカ情報をやりとりするしくみに対する懸念

こんにちは、ritouです。

Y!Jが今後出そうとしてるFastPayってやつの開発環境が提供されてるみたいです。

決済をシンプルにしたいという思いは重要だと思います。しかし、クレジットカード情報を決済サービス - マーチャント間でやりとりするしくみについて、いくつか懸念があります。

懸念1 : マーチャントからのカード情報流出など

(ここでは一旦、「クレカ情報入力するのは便利は便利だよね」という前提で進めます。)

現在、クレジットカード情報の大規模流出事故が報じられています。ここから得られる教訓としては"信頼できるところ以外にカード情報を保持させてはならない"という感じでしょう。

マーチャントにカード情報を直接取り扱わせるしくみでは、どうしてもリスクが発生します。
もちろん、監査などさまざまな仕組みでそのリスクを減らすこともできるでしょうが、意図せぬ実装不備もどうしてもあるでしょう。また、利便性のために一度預かったカード情報を保存しておいて次回以降使う仕組みなんてのも実装しちゃう可能性があります。

なるべく、マーチャントにカード情報そのものを触らせないしくみを提供することが大事だと思います。

提案 : 競合であるWebPayが提供しているCheckoutHelper機能みたいなやつ

FastPayの競合サービスであるWebPayでは、マーチャントがクレジットカード情報を直接扱わずにすむしくみが提供されています。

カード情報の入力フォームの処理と クライアントサイドでのカード情報のトークン化 を行える、WebPay.js向けのヘルパーを提供しています。

CheckoutHelperを使うと、数行のHTMLを記述するだけで、 カードに関する入力情報のチェックと、カード情報を直接取り扱わないセキュアな形で決済に利用出来るトークンを取得し、サーバサイドに送信することができます。

https://webpay.jp/docs/checkout

実際に挙動を細かく確認したわけではありませんが、一度WebPay側にカード情報がわたりトークンに変換されるような感じでしょう。上記のリスクを理解してカード情報を扱いたくないというマーチャントに向けて、バックエンドのやりとりを安全にできるしくみを提供している点は良いと思います。

懸念2 : カード情報を入力するUXが普及することによるフィッシングリスクの増加

これはもう説明不要かと思いますが、「クレジットカード情報をさくっと入力して決済」が当たり前になるとちょっと怖いなーと思います。マーチャント上のダイアログだけで、カード情報が本当にY!Jに流れるのかをユーザーはどうやって確認できるのでしょうか。
Y!Jのブランド力がフィッシング被害の後押しをしてしまうかもしれません。

提案 : OAuth + 決済APIをシンプルにするためにjs提供とかの方が良いのでは

やはり、PayPalや"Login and Pay with Amazon"みたいな"アカウント持っているだけで決済までさくっといける"しくみをもっと便利にする方向を考えるべきだと思います。

まとめ

2つの懸念があることを説明したつもりです。

  • 意図せぬ流出や保存、悪用の可能性を考えたら、カード情報をマーチャントに扱わせるのは良くないと思う
  • フィッシングのリスクなど、そもそもカード情報を入力させるUXも良くないと思う

不正利用などの場合、カード会社が補償してくれるところもありますが、リスクがあるしくみが普及して被害が増大してしまうと、今後は補償対象外となるケースも出てきちゃうのではないかというのもちょっと気になります。

中の人はよく考えるべきだと思います。

Cookie使ったセッション管理も認可だと思うわって話

こんばんは、ritouです。

OAuthが認可の仕組みだっていうのはだいぶ広まった感ありますね。
でも、なんかまだしっくりこないっていう人いると思います。

その理由の一つとして、Webアプリケーションでよく使われている”Cookieを使ってログインセッションを保持する”しくみ、あれが一般的に認証って言われてるせいじゃないかと思ったりします。

こいつ、OAuthと同じで、途中で認証もしてるけど、全体で見ると認可のしくみじゃねーか的な感じで書いて見ます。
いわゆる”Webアプリケーションの認証フロー”は次のような感じでしょう。

  1. ブラウザはログイン画面を表示
  2. ブラウザは入力されたメアド/パスワードをログイン用URLにを送信
  3. Webサーバーが組み合わせを検証
  4. 有効な場合、WebサーバーはSession Cookieを発行
  5. ブラウザはSession Cookieつきで会員情報を表示するURLにアクセス

次のように読み替えてみます。

  • ログイン用URL -> トークンエンドポイント
  • Session Cookie -> アクセストーク
  • 会員情報を表示するURLにアクセス -> リソースアクセス

するとこうなりますね。

  1. ブラウザはログイン画面を表示
  2. ブラウザは入力されたメアド/パスワードをトークンエンドポイントに送信
  3. Webサーバーが組み合わせを検証
  4. 有効な場合はアクセストークンを発行
  5. ブラウザはアクセストークンつきで、リソースアクセス

これはほぼ、OAuth 2.0でいうところのResource Owner Password Credentialsです。
私はこのフローの中でメアド/パスワードをWebサーバーが検証する部分がまぁ認証であり、それ以外はフル権限の認可処理ととらえています。
ブラウザってのは、メアドやパスワードを扱うことを許されており、サービスの全機能を利用できる権限を要求する、しかもclient_idで自分が誰かすら名乗る必要がない、特殊なアプリケーションであると考えれば良いでしょう。

ただし、Cookieにメアド/パスワードを保存してBasic認証張りにそれを毎回送ってチェックしてるとかならCookie認証なのかもしれません。

で、何が言いたいかと言うと、自分でもよくわかりません。
ではまた!

JICS 2014のセキュリティ・トラックを聞いて思ったこと

こんばんは、ritouです。
1/14,15にJAPAN IDENTITY & CLOUD SUMMITが開催されました。
すでにレポートや取材記事、まとめ、まとめのまとめなどありますのでイベントを知らない方は見ていただければと思います。
IdM実験室: Japan Identity & Cloud Summit 2014のまとめのまとめ

2日間のうち、自分の出番の時間以外はなんとか一通り聞いてたつもりでしたが、関係者から「セキュリティ・トラック、ritou寝てただろ」と言われましたので、ノートPCを閉じ、スマホをポケットにしまい、椅子に座ってやや下を向きつつ目を閉じて静かに息をしながらID連携推しの立場として思ってたことを残しておきます。

セキュリティ・トラックの内容はこのあたりでわかるかと思います。
#JICS2014 #Sec Track - クレイジーダブルポケットフランネルチェックシャツ - Togetter

パスワード認証の現状に対するID連携の使いどころ

セキュリティ・トラック、モデレーターの方の肩書きが「OpenIDファウンデーション・ジャパン プロデューサー」でしたので、ところどころに「やはりID連携!OpenID!!」ってぶっこんでくれると期待していましたが、あんまりそっちの方向に話が膨らむことはなかった印象です。まぁ、とても魅力的な内容でしたけども。

まずは、「記事の続きを見るのに登録必要なのうざいときある、ID連携で良いのでは?」っていうあたりです。
辻さんもブログで触れられていましたね。

パスワード管理の観点からすると管理しなければいけないアカウントが増え
これも簡単なパスワード、使い回しを誘発する要因となると考えられます。

そこで、言い方は悪いですが

「ユーザにとって価値の低いと考えられるアカウントだけを
ID連携でまとめてしまうというのもあってもいいのではないでしょうか?」

という発言をしました。
実現するかどうかは別にして以外と楽になるんじゃないかなって思います。

http://n.pentest.jp/?p=31142

こういうところって、無料会員なんだけどやたらと属性情報ほしがりますよね。ものによっては、企業名とか入れる場所もあったりしますが、別に代表に電話かかってきて確認するようなことはないですね。本当に勤務先などの情報を欲しがっているなどの場合はエンプラ・トラックで少し紹介された企業アカウントの属性情報を外に出す話につながるのですが、現状求められている属性情報はそこまでではなさそうです。

とりあえず、どんなユーザーが見ているかを知りたいのであれば、OpenID ConnectのUserInfoを補完的に使いつつ、足りない属性情報を入力させてしきいを下げる感じにするだけでだいぶ使いやすくなるでしょう。

OpenID Connectでは「難しいことも簡単に」みたいな感じでかなりセンシティブなデータを扱うユースケースでも利用できるように設計されていますが、OpenIDの初期のユースケースってブログコメントとかの比較的ライトなユースケースでした。

私たちが「もうパスワード管理するのやめよう、ID連携で信頼できるところに任せよう」と言っても、「サービスの認証機能を全てIdPに預けるのは、そのIdPのアカウントに何かあったときとかいろいろ不安。」という声はもちろんあります。
では逆に、「コンテンツに対するコメントの部分だけ」「無料会員のところだけ」とID連携するスコープを狭めるのはどうでしょうか。「個別の機能では利便性重視で外部アカウントに任せてもいいところもある」という考えもあるんじゃないかと。

今回は後者の話で、スコープもリスクも限定したID連携の啓蒙というのも重要かもなーと思いました。

IDを持たないという選択について

こっちは前者のほうですね。
ディスカッションでは、MS Passportの失敗の話とかFBやTWのようにAPIありきじゃないと連携する気にならないみたいな話も出ていました。
まぁ、ユーザーセントリックなんつってOpenID 2.0が騒がれたときも本音は囲い込みが目的だろ?って感じでしたね。

今はOAuth + Profile APIなどで属性情報を提供するIdPは増えており、属性情報がもらえてUXがそこそこ悪くなければID連携するよって感じのサービスも少しずつ増えています。
昨今のパスワード認証してるところからの漏洩やリストを用いた不正ログインの状況は、ID連携を後押しするものだと思います。
ただし、ID連携をしたとしてもたどり着く課題は最後のリカバリーのところですし、リカバリーこそそのサービスの最終的な認証手段といえます。

ということで、ID連携しましょうって言いつつ、「ID連携しているRPが使える、”万能でもなくていいけど各サービスの特性に合わせたアカウントリカバリー方法”」についてもよく考えないとなーと思いました。

おまけ:「パスワードを覚えられない方はこちら」について

そんな中、1つの方法としてサービス提供側がユーザ登録時に強固なパスワードを発行して、それを使ってユーザはログインし、自分の好きなものに変更することはせず、再度ログインするときには再発行/リセットするといった「なんちゃってワンタイム」(実際には再発行/リセットするまで同じパスワードなのでワンタイムとは呼べませんが)的な運用を行なうという選択肢もあってもいいのかも?と思いました。
もちろん、再発行/リセットを行うためのメールアカウントなどは死守する必要はありますが、記憶/記録しておかなければならないものが減りますし、サービス提供側が毎回強固でランダムなものを発行すれば
「複雑なパスワードを設定して、サービスごとに別々のものを設定して使い回しをしない。」
ということは満たされますし、リスト型攻撃などによる被害状況は改善されるのではないかと思います。
(追記:再発行とすると平文でパスワードがメールで届くことがイメージされるため、再発行から再発行/リセットとしました。)

http://n.pentest.jp/?p=31142

私が「パスワードなんて覚えずに、毎回リセットかましてログイン状態になるユーザーがいるらしい」って話を聞いたのは2007年ごろだった気がします。
そのとき、似たようなことを考えていて、

  • 普通に再設定したいユーザーはそのままさせればよい
  • はなから覚える気がないユーザーには毎回ワンタイムなログイン用URLをメールで送りごにょごにょしたあとログインさせ、パスワードは"設定なし"という扱いにする

という2つの選択肢を用意しても良いのでは?という感じでした。辻さんがおっしゃられているようなユースケースを切り出しちゃうイメージです。

「メールを受け取ってログインするしかない」認識であればユーザーはがんばってメールアドレスをメンテするだろうし、その頃ちょうど「PC用とケータイ用の両方のメアドが必要」なんていうサービスが増えてきたときだったので複数必須にしとけばなんとかなるかなーとか思ってました。

これいきなりY!とかのサービスでいきなり使えるようにってのは難しいと思いますけど、世の中にあふれている生パスワード送信系リマインダーややたら短いパスワード文字数、文字種制限のあるログイン方式に比べたら有用な話だと思うので、気が向いたら実装のデモでも作って次のidconなどで披露したいと思います。そのときは是非、セキュリティな人に検証をお願いしたく。



とりあえずこんなところです。
「本当は寝てたけど、ブログ記事とtogetter見なおして今ぱっと思いついた」とかじゃないです。
エンプラとSAMLの話も書こうかと思ったけど別で書くかもしれません。

ではまた。

OpenID ConnectのSelf-Issued OPデモ用Androidアプリを作ってみた

こんにちは、ritouです。
Self-Issued OPについて、去年からなんかごちゃごちゃ言ってきましたが、やはり動くものが見れないと話にならないと思っていたので、年越しあたりでデモ用のアプリを作ってました。

Self-Issued OPとはなんなのかについての復習はこちらから。
OpenID ConnectのSelf-Issued OPの話 - r-weblife

Self-Issued OPによるAuthorizationの流れ

いつもながらRP(Client)がAuthorization RequestをOPに送り、OPがAuthorization Responseを返すわけですが、その際のOP側の一連の流れは以下のとおりです。

  • 1. openid://というカスタムURIスキームで起動
  • 2. Authorization Requestの処理
  • 3. Consent Page(アクセスを許可するか確認する画面)の表示
  • 4. ID Tokenの生成
  • 5. Authorization Responseの生成

実装の話は長いので、先に画面の流れを紹介します。

スクリーンショット

アプリはこちらです。
https://play.google.com/store/apps/details?id=info.openidconnect.selfissued
立ち上げるだけじゃAuthorization Requestがなくて何も起こらないので、仕様とデモ用RPのURLを乗せてあります。ちなみにデモRPのコード(Perl)はgithubにありますがSelf-Issued用のものはまだpushしてません。

RPはWebアプリケーションです。
http://demo-client.openidconnect.info/selfissued/authorize
モバイルからの見た目がよくないのはごめんなさいごめんなさい。
Authorization Requestはopenid://で始まってるのでPCで見ながらクリックしても何も起こらないと思います。

クリックすると、アプリが立ち上がり、同意画面が表示されます。いわゆるConsent Pageです。
今回はscope=”openid”のみサポートしているので、(client_idに指定されたURLが)端末識別子の役割を果たす値を要求していますよ、許可しますか?って感じにしました。
キャンセルしたかったらアプリ落としてください。。。

Allowを押して進めると、OPはAuthorization Responseを作成します。一番最初のときだけ時間かかるかもしれません。
http://で始まるURLについてのIntentをブラウザで受け取ることで、RPはAuthorization Responseを取得できます。
RPはフラグメントからStateパラメータとID Tokenの値を取得して、値を検証します。
デモRPではずらずらと検証した結果を表示します。



何を検証しているかは、仕様のURLを見てください。
では、どう実装したのかを説明します。

実装

1. openid://というカスタムURIスキームで起動

AndroidManifest.xmlで ’’ とか指定すればいけます。

2. Authorization Requestの処理

クエリパラメータを検証します。

  • response_type : id_tokenのみ許容
  • client_id : Self-Issued OPの場合、client_idには戻り先のURIが指定されますが、前のエントリに書いたとおり、戻り先としてカスタムURIスキームを許容するのは若干の不安があるため、http(s)://なURLのみ許容としました。
  • scope : プロフィール情報設定させたりとかがめんどくさかったので、"openid"のみ許容としました。
  • state : チェックなし
  • nonce : チェックなし

RPの情報(ポリシー、TOS、ロゴなど)を指定することでユーザーに表示されるというregistrationパラメータも今回はサポートしていません。
エラーがあった場合はエラー画面を表示させています。
Self-Issued OPの場合、事前の静的な登録などを行わずに進んでいくので、オープンリダイレクタのリスクもある気がします。
なので、しれっと戻すのはよくないかもと思ってます。

3. Consent Pageの表示

scope=openidのみをサポートしているので端末の識別子を要求してますよぐらいにしています。

4. ID Tokenの生成

RSAの鍵ペアを生成します。このときちょっとだけ動作がモッサリします。
Priivate KeyはID Tokenの署名生成に利用します。
Public KeyのExponentとModulusの値はID Token内にsub_jwkパラメータとして含まれ、それぞれの値を連結してハッシュ値Base64 URLエンコードしたものがsubパラメータとなります。
client_id毎にPrivate/Public Exponent, Modulusの値を保存することで、RP単位で異なる識別子が返されるPPIDを実現しています。この保存については、KeyStoreなどを使うべきなのでしょうけども、デモってことでSharedPreferencesを使っています。

5. Authorization Responseの生成

ID TokenやStateパラメータを使ってAuthorization Responseを作成して、Intentを送ったらOPの処理は完了です。

動作させてみて改めての懸念点

一番アレだなーって思ったのはAuthorization Responseでブラウザに戻るときに別のタブで開かれちゃったときですかね。
ブラウザの設定でなんとかできればいいのかもですが・・・あとは前に書いたエントリのとおりです。

今後鍵の保管あたりをまじめにやったりとか、プロフィール情報を持って他のscopeをサポートするとか、registrationパラメータをサポートしてユーザーにClient情報を表示させるとか、カスタムURIスキームなClientをサポートするとかをやろうと思います。

そういえば来週、JICS(Japan Identity & Cloud Summit 2014)が開催されます。
https://jics.nii.ac.jp/
私もちょっとだけ何かします!

寒いのでこのへんで。ではまた!