ID連携の標準化仕様紹介とセキュアな実装のためのアプローチ ~ 2021

f:id:ritou:20210904174248p:plain

おはようございます ritou です。
久々に「解説付きスライド全公開」的なやつをやります。

f:id:ritou:20210904174413p:plain

先月、チーム内でID連携のための標準化仕様に関する勉強会(私が一方的に話す会)を行いました。
が、実際はだいぶグダグダになってしまい、これはその後色々付け足してるうちに別物になってしまった資料です。

f:id:ritou:20210904200923p:plain

内容としては、ID連携のための標準化仕様にどのようなものがあるかを知ってもらうための「入門編」のような立ち位置で作りました。

f:id:ritou:20210904200944p:plain

OpenID Connect(SAMLのような) ID連携のための標準化仕様を紹介しようと思うと、ついつい個別にシーケンスやリクエスト/レスポンスの説明を始めがちですが、初学者が気になるのはそんな細けぇことではないでしょう。 まずは「この仕様で何ができるようになるのだろう」「この仕様では何を実現したいんだろう」と言うところから理解していくのが良いのではないでしょうか。

そこで、今回はこれらの仕様を知るきっかけとして、

  • たくさんある仕様のうち、"何かをできるようにするための仕様" の概要
  • "セキュリティ強化のための仕様" で使われていてID以外のWebアプリケーション開発に活かせそうな考え方

を紹介していきます。

ID連携機能を提供するための仕様とユースケース

f:id:ritou:20210905005548p:plain

前半は「こんなことを実現するための標準化仕様がありますよ〜」って言うお話をします。

OpenID Connect

f:id:ritou:20210905005709p:plain

いきなりですが、OpenID Connectの話を(ちょっと)します。

f:id:ritou:20210905012239p:plain

まずは、よくある「●●でログイン」って言う機能の流れを紹介します。

ステップ1として、Relying Party(以下、RP)という「外部アカウントを利用してログインさせるサービス」のログイン画面にて、ユーザーが「●●でログイン」と言うボタンを選びます。

f:id:ritou:20210905012422p:plain

ステップ2として、RPがOpenID Provider(OP)にユーザーを送ります。 画面のリダイレクトやポップアップなどで実現されます。

f:id:ritou:20210905012448p:plain

ステップ3,4では、OPがユーザーに対して認証を要求したり、「RPに対してあなたの情報を提供することを許可しますか?」と言って確認を求めます。

f:id:ritou:20210905012514p:plain

ステップ5では、ユーザー情報と合わせて「いつ、誰に対してどんな情報を提供することに同意を得たか」と言った認証イベントの情報が提供されます。

f:id:ritou:20210905012539p:plain

ステップ6では、RPは受け取った認証イベントの情報を検証して紐づくユーザーをログイン状態にします。

f:id:ritou:20210905012747p:plain

このような「●●でログイン」とう機能を実現するための標準化仕様がOpenID Connect(OIDC)であり、OPがユーザーの同意の元でRPに認証イベントの情報とユーザーの属性情報を提供するための仕組み です。

f:id:ritou:20210905012826p:plain

OIDCのコアとなる仕様ではここまで説明した最低限の機能である

  • 認証イベント情報の受け渡し方
  • ユーザーの属性情報の提供方法

が定義されています。

それ以外にもいくつか仕様があるのですが一部は後から紹介します。

OAuth 2.0

f:id:ritou:20210905013037p:plain

次に紹介するのが、OAuth 2.0です。

f:id:ritou:20210905013120p:plain

あるサービスが別のサービスのユーザー情報にアクセスする「データ連携」の流れを紹介します。

ステップ1として、ユーザーはブログのようにClientと呼ばれる「外部サービスのデータを利用するサービス」で、別サービスで管理するリソースの利用を開始します。(例えばログイン状態の設定画面の「●●と連携する」ような機能から処理が始まることが多いでしょう。)

f:id:ritou:20210905013749p:plain

ステップ2として、ClientがAuthorization Server(以下、AS)にユーザーを送ります。

f:id:ritou:20210905013813p:plain

ステップ3,4では、ASがユーザーに対して認証を要求してResource Orner(RO)を特定し、「Clientがあなたの情報にアクセスすることを許可しますか?」とリソースアクセスの許可を求めます。

f:id:ritou:20210905013847p:plain

ステップ5では、ASからClientに対してAPIアクセスのためのトークン発行処理が行われることでリソースアクセスの権限を提供します。

f:id:ritou:20210905013943p:plain

ステップ6ではClientが取得したリソースアクセスの権限を利用してAPIアクセスを実行できる状態、つまり「連携済み」の状態となっています。

f:id:ritou:20210905014208p:plain

このようなデータ連携を実現させるための仕様がOAuthです。 一言で説明すると、認可サーバー(AS)がリソースオーナー(RS)の同意のもと、クライアントに対してリソースアクセスを提供するための仕組み です。

OIDC vs OAuth 2.0

f:id:ritou:20210905014241p:plain

と、OIDCとOAuth 2.0の両方を並べて説明したところで "なんか似てる" と思われた方もいるでしょう。 登場人物の呼び名が違うものの、一連の流れは同じような感じでした。

f:id:ritou:20210905014327p:plain

で、この辺りの話をするときに避けられないのがいわゆる "OAuth認証"と言うワードです。

「データ連携を提供する仕組みであるOAuthを用いても、リソースオーナーの情報にたどり着くことができたらそのユーザーでログインさせられるよね。」

この考えがOAuth認証と呼ばれるものの正体です。

Client -> AS -> Clientという一連の流れでリソースアクセスを許可したリソースオーナー本人を特定してログインさせることができれば問題はありませんが、少しでもその流れが保証されないような仕組みになっていると問題が起こり得ます。

ritou.hatenablog.com

それに対して、OIDCではRPは一連のフローで取得した認証イベントの情報を利用してユーザーをログインさせます。少しだけ、この認証イベント情報について触れましょう。

f:id:ritou:20210905014404p:plain

OIDCの仕様を見たことがある方はIDトークンと呼ばれているものをご存知でしょうか。

  • いつ (トークン発行日時)
  • 誰が (ユーザー)
  • 誰に (RP)
  • 誰が持つ (OP)
  • どんな属性情報を提供することを (scope)

という5W1Hのような情報、あとはセキュリティ対策のためにRP -> OP -> RPと引き回される値などを含んだものが認証イベント情報です。

後は継続的にユーザーの属性情報を利用したいサービスのために専用のAPI仕様が定義してあるのがOIDCです。

ちょっと昔話をすると、この属性情報APIへのアクセスも独自に仕様策定が進められていましたが、仕様策定当時に同様に策定が進められていた OAuth 2.0 と目的が一致していたため、OAuth 2.0をベースとしてこのようなIDレイヤーに関する仕様を加えて拡張したものとして仕様策定を進めた という関係性になっています。

OIDCで扱う属性情報についても少しだけ触れておきましょう。

f:id:ritou:20210905014432p:plain

OIDCでは汎用的なプロフィール情報、あとはメールアドレスや電話番号は確認済みフラグの定義もあります。

f:id:ritou:20210905014457p:plain

最新の属性情報を返す "UserInfo Endpoint" から返されるJSONの表現例を載せておきます。 住所も細かく分かれてるのとか連結したやつとかが定義されています。

よくこの手のフォーマット見て 「日本にはな、「カナ」「漢字」と言うのがあるんじゃ。どうすりゃいいのよ。」 みたいなことがありますが、実はカタカナや漢字の氏名もサポートされています。

5.2. Claims Languages and Scripts

Human-readable Claim Values and Claim Values that reference human-readable values MAY be represented in multiple languages and scripts. To specify the languages and scripts, BCP47 [RFC5646] language tags are added to member names, delimited by a # character. For example, family_name#ja-Kana-JP expresses the Family Name in Katakana in Japanese, which is commonly used to index and represent the phonetics of the Kanji representation of the same represented as family_name#ja-Hani-JP. As another example, both website and website#de Claim Values might be returned, referencing a Web site in an unspecified language and a Web site in German.

(日本語訳)

Human-readable な Claim Value およびそれらへの参照は, 複数言語および複数文字種で表現することができる (MAY). 言語および文字種の指定には BCP47 [RFC5646] 言語タグを # を区切り文字としてメンバー名に付与する. 例えば family_name#ja-Kana-JP は日本語のカタカナ表記の Family Name を示す. カタカナ表記の氏名は, 漢字表記 (family_name#ja-Hani-JP) の氏名の発音を示したりインデックス目的等で用いられることが多い. 他にも website が言語指定のない Web サイト, website#de がドイツ語の Web サイトを参照していることを示すといった例も挙げられる.

  • family_name#ja-Kana-JP : カナ 姓
  • given_name#ja-Kana-JP : カナ 名
  • family_name#ja-Hani-JP : 漢字 姓
  • given_name#ja-Hani-JP : 漢字 名

Yahoo! JAPAN の属性取得API(UserInfoAPI)のドキュメントにも書いてあります。

developer.yahoo.co.jp

個人的にはこの辺りがあるおかげで普段個別で持ってたデータも汎用的に扱えて便利そうだなーと思っています。

f:id:ritou:20210905014528p:plain

ここまでざっとOIDCの概要について説明しました。 実際は関連する仕様がいっぱいあるので、引き続き「これができるようになる仕様がある」みたいな話をしていきます。

Discovery & Dynamic Registraion

f:id:ritou:20210905014914p:plain

まずはOIDCのDIscoveryとDynamic Registrationという仕様の紹介です。

f:id:ritou:20210905014943p:plain

こちらはよくあるソーシャルログインの画面ですが、このボタンってどうやって設定するかわかりますか?一般的なのはサービスが対応するOPを厳選して開発者登録とかして最終的に設定完了したら並ぶみたいな流れでしょう。

f:id:ritou:20210905015001p:plain

Discoveryというのは、RPがエンドユーザーの情報からOPを特定する方法、OPがエンドポイントや仕様のサポート状況を外部に公開する方法が定義されています。

OP特定の方はメアドやURLとかからOPのURLを教えてくれという要求を行うために、 WebFinger と呼ばれる仕組みを利用します。

外部公開の方は、OPは OpenID Configuration と言って特定のパス /.well-known/openid-configuration にて静的なJSON形式のデータを返します。 こちらは GoogleYahoo! JAPAN などいろんなサービスが対応しています。

Dynamic Registrationの方は、動的にRPをOPに登録する方法が定義されています。

f:id:ritou:20210905015048p:plain

GoogleのOIDC関連ドキュメントから拾ってきた図です。JSON形式で各種エンドポイントからサポートする仕様、属性情報とかを提示できます。

f:id:ritou:20210905015106p:plain

Dynamic Registrationのリクエスト/レスポンスの例です。 今日は概要紹介なので詳細は省きます。

f:id:ritou:20210905015123p:plain

これらの仕様のユースケースとして、RP設定の効率化、自動化、半自動化みたいなものがあります。

これを実装しているサービスもいくつか聞いたことがあるのでもしかしたら体験された方もいらっしゃるかもしれません。 OIDCを利用するRPの立場になりうるサービスの設定画面で "Googleと連携する" とかを押すと、エンドポイントの設定がバシッと終わっちゃうみたいなイメージです。

ここでOPがDynamic Registrationに対応していたらRPの登録まで一気に終わっちゃうのでしょうけれども、大体はここは非対応で開発者がOPにredirect_uriや名前などを登録して取得したClientID/ClientSecretの値をRPに登録する場合が多いです。

という感じで、半自動化~自動化もできるようになりますよってところですね。 Discoveryの中でもOpenID Configurationの部分は対応しているOPが多いので、このユースケースは割と一般的になりつつあるのかなってところです。

f:id:ritou:20210905015143p:plain

もう一つのユースケースはさらに実装してるところ見かけないやつですが、 "ユーザーが普段利用しているOP" でログインできるようなものです。 これはOpenID Connectの前のOpenID Authenticationと呼ばれる仕様で採用されていたUX であり、メールアドレスの @以降の部分からOPを特定、今まで使ったことのないOPであれば OpenID Configurationの値を取得してDynamic Registrationするという流れです。

リソースアクセスをターゲットにしているOAuth 2.0の本来のユースケースだとどうしてもベンダーロックインな仕組みに思えますが、OpenIDは本来 "User-Centric Identity"つまり、ユーザーが自分の使いたいOPを選んでID連携機能を利用することを想定した仕組みです。

現状ではDynamic Registrationに対応しているOPがほとんどいないと思うのでこのユースケースはなかなか使われないと思いますが、こういうこともやろうと思えばできるよというお話でした。

Session Management

f:id:ritou:20210905015410p:plain

次はセッション管理に関する仕様を紹介します。

f:id:ritou:20210905020110p:plain

OIDCを用いたID連携において、基本的にはOP/RP間のセッション状態は同期されません。 もちろん、ID連携した瞬間というのはOP/RP共に同じユーザーが利用しているので共にログイン状態となりますが、その後OP/RPのいずれかでログアウトしたらその状態は崩れます。 また、OPと複数のRPが連携している場合、RPごとに同じことが起こり得ます。

f:id:ritou:20210905020131p:plain

そのような状況を改善するために、セッション管理に関する仕様があります。

  • Session Management : この仕様ではOP/RP間でセッション状態が同期しているかを確認する方法を定義しています。具体的には、iframe + postMessageを用いて状態変更がないかどうかを問い合わせます。
  • RP-Initiated Logout : RPからOPにログアウトしたい意思を伝える方法を定義しています。OPの専用エンドポイントにリダイレクトさせることで実現します。
  • Front Channel Logout : OPがフロントエンドのリクエストなどを用いてRPにログアウトする意思を伝える方法
  • Back-Channel Logout : OPがバックチャンネルのリクエストを用いてRPにログアウトする意思を伝える方法

後者2つは昔から "Webビーコン" でやるとか、バックエンドでやるのは確実だけどシステム作るのめんどいなーとか言われてたものを標準化した感じです。

必要に応じて、これらとOIDCを組み合わせて使っていくイメージです。

f:id:ritou:20210905020154p:plain

これらの仕様により、何ができるようになるかというと

  • 複数ドメインで構成される単一サービスをOP/複数RPの関係と捉え、OP上のユーザーのログイン状態に変更がないかをRPは随時問い合わせることができる
  • また、OPのログイン状態に変更があった場合、RP全てをログアウト状態にできる

というあたりです。

前者の例は複数の国に対応するサービスにおいて、トップレベルドメインが異なるURLを複数持つサービスがある場合とかですかね。 とはいえ、最近の3rd Party Cookieに関するブラウザ実装の方針によってはこれらの仕様のうち postMessage や iframe 周りの挙動が変わるかもしれないため、導入を検討する際は注意が必要です。

Identity Assurance

f:id:ritou:20210905020313p:plain

次は Identity Assurance という仕様を紹介します。

f:id:ritou:20210905020335p:plain

なんとかPay、なんとかgramといった決済アプリではもはや馴染みのある "犯収法" "本人確認" といったキーワードですが、この本人確認を行った情報の流通についてのお話です。

自分たちで実装、運用していくとなかなか手間のかかる本人確認 ですが、せっかくならこの確認済みの情報や確認済みフラグといった情報をユーザーの同意のもとで外部サービスに提供しようと思うとどうなるでしょうか?

f:id:ritou:20210905020414p:plain

Identity AssuranceはOPで検証済されたユーザーの属性情報をRPに提供するための仕様です。

"どのルールに従った情報が欲しい" という "要求", "誰がどのルールに従って検証したか" という検証プロせるの情報と値を組み合わせた"表現"、ID連携時のみに提供するかUserInfo Endpointから最新の情報を返すかといった"応答"の仕様が定義されています。

f:id:ritou:20210905020445p:plain

OIDCの属性情報は上で説明しましたが、属性情報の "値" だけでした。 Identity Assuranceでは "誰がどのルールに従って検証したか" という "verification" の情報と属性の値 "claims" によって表現されます。

ちなみにこの "trust_framework" の部分の定義に日本の

も含まれており、"これは犯収法対応のために検証済みの情報です" といった表現が拡張なしで利用できることを意味します。これは国内企業の方が仕様策定に関わることで実現されました。

f:id:ritou:20210905020510p:plain

ユースケースもそのままですね。

  • OIDC : 個別のユーザー認証 -> 認証イベントを流通
  • Identity Assurance : 個別の属性情報検証 -> 検証済み属性情報を流通

という感じで、OPが各種書類やマイナンバーカードで検証した情報の流通はこれから重要になる仕組みのための仕様です。

RISC & CAEP

f:id:ritou:20210905021544p:plain

次は RISC と CAEP という仕様です。

f:id:ritou:20210905021626p:plain

OIDCのフローで説明したID連携処理を行う際、ユーザーは認証処理やアクセス元など含めたリスクベース判定などで安全だと判断されることが一般的です。

しかし、その後例えばネイティブアプリが動作しているスマートフォンやPCのネットワーク環境が変わったりした場合のことを考えると、リソースアクセス時のリスク評価も必要となるでしょう。また、ID連携後にユーザーがOP上でBANや不正利用などでアカウント停止に陥った時、ログインのみに利用しているRPなどはそれを知ることなくサービスを提供し続ける可能性があります。

f:id:ritou:20210905021654p:plain

このような状況を考慮して、OpenIDファウンデーションのWGでは

  • CAEP : 継続的なアクセス評価
  • RISC : アカウントの状態変化を通知

という機能のための仕様策定が進められています。 同時に、前者はmicrosoft、後者はGoogleが実際にプロダクトに導入するなどリファレンス実装も進められています。

f:id:ritou:20210905022349p:plain

RISCの例として後者のRISCユースケースをあげると、ID連携を行ったOPからRPに

  • アカウントの状態変更、退会
  • セッションの無効化

などのイベントをWebhookでJWTを送ることで通知、RPが署名検証して対応する処理を実施のようなことが実現できます。

CIBA

f:id:ritou:20210905021745p:plain

ユースケースを伴う仕様紹介の最後は CIBA です。

f:id:ritou:20210905022430p:plain

最近個人的に注目している、Decoupled Authentication という概念があります。

いわゆる "手元のスマホで認証 + アルファする" っていうお話で、決済の場合は 3D Secure 2.2 からリスク判断結果に応じて手元の端末にPushが送られるフローが導入されています。

このような「手元のスマホでID連携」を実現するための仕組みが CIBA です。

f:id:ritou:20210905022453p:plain

CIBAでどのようにこの Decoupled Authentication を実現するかという点で大事なのが、環境の分離です。

これまで紹介したOIDCのフローではユーザーが操作する User-Agent つまりブラウザが "認証を要求する端末" であり "認証を行う端末" でした。

しかし、CIBAではこれを

  • 認証を要求する端末がユーザーの目の前にある必要はない
  • 認証を行う端末が手元にあり、そこにPush通知などで要求が行く

というように分離します。 認証を行う端末がスマホなどになることで、生体認証やローカル認証を利用するFIDOとの相性も良くなり、より利便性の高いUXの提供にも繋げられるかもしれません。

f:id:ritou:20210905022518p:plain

CIBAのユースケースで重要なのは、事前にユーザーをある程度識別する必要があるところです。 コンビニ決済の例では、客がポイントカードを出す際にそれに紐づくアカウントを店側で保存しておけば「この人に幾らの決済をさせたい」と言う要求ができます。

手元で通知を受け取って決済内容に同意したら、どこぞのQRコード決済のように店員に見せたり端末にスマホをかざして「なんとかぺ〜い」って聞こえるまでの微妙な時間を待つ必要もありません。

f:id:ritou:20210905022547p:plain

CIBAのユースケースとしては先ほどのコンビニに限らずPOS端末全般にNFC端末を置かずに導入できたり、コールセンターや銀行窓口での認証+アルファの確認に使ったりするユースケース、あとはサブスクライブ決済の定期的な確認要求といったところを想定しています。

ここまでのまとめ

f:id:ritou:20210905022612p:plain

前半部分ではID連携に関連する標準化仕様のうち「こんなことができるよ」ってのがはっきりしているものを紹介しました。

ID連携なんて言っても●●でログインだけなんでしょ?と思われている方もいるかと思いますが、実際はその周辺機能についても「この機能も併せて使うよね。なら標準化しよう。」と言った流れでどんどんと拡張されています。

標準化が行われるメリットとしては、ユースケース毎にリスクなどを整理された上で仕様策定が進められることが一番でしょう。 みなさんご存知の通り、ID連携に関わる脆弱性はサービス全体の将来を左右するものになり得ます。 これをやりたい!と思いついたことが既に標準化されていないかを確認し、要件が一致するものは積極的に利用していくことが重要です。

ID連携仕様から学ぶWebアプリケーション間のデータ送受信をセキュアにする方法

f:id:ritou:20210905023446p:plain

後半戦です。

ここからは、ID連携のための標準化仕様に出てくる "Webアプリケーション間のデータ送受信をセキュアにする方法" を紹介します。

標準化種類のタイプ

f:id:ritou:20210905024825p:plain

ID連携のための標準化仕様は

  • 基本仕様 : プロトコルというよりもベースとなるフレームワークや要件を満たす最もシンプルな仕様である程度拡張性を持たせたもの
  • 拡張機能 : 既存仕様と組み合わせたり、一部を置き換えることで安全性を高められる "武装" のような立ち位置にある仕様
  • 特定のユースケースではここに気をつけなれけばならない、と言ったプロファイルやベストカレントプラクティス、あとは脅威と対策の整理したやつとか

というような種類に分けられます。

今回はその中で、拡張仕様でよく使われるテクニックに注目します。

f:id:ritou:20210905024843p:plain

例えば OAuth 2.0 に関連するものは IETF にて仕様策定されてRFCとして発行されているのですが、

f:id:ritou:20210905024904p:plain

ちょちょっと検索して出てくる "RFC 6749 The OAuth 2.0 Authorization Framework" がベースとなるフレームワーク、"RFC 6750 The OAuth 2.0 Authorization Framework: Bearer Token Usage" っていうのが最もシンプルなアクセストークンの種類の定義となります。

それに対してモバイルアプリとかでトークンを安全にやりとりできないケースがあるとなった時にピンポイントで強化する "拡張仕様" が "RFC 7636 Proof Key for Code Exchange by OAuth Public Clients" です。

f:id:ritou:20210905024951p:plain

そして、モバイルアプリやSPAがOAuth 2.0を実装する時に気をつけないといけない細けぇお話みたいなのが BCP としてまとめられています。

その結果、全体で見た時に拡張仕様、とBCPがもりもりになってしまってカオスな状態になってしまい、「新しい話入れずに、一旦まるっと整理しようか」という立ち位置で仕様策定されているのが "OAuth 2.1" と呼ばれているものだったりします。

f:id:ritou:20210905025020p:plain

OpenID Connectの方でもOpenIDファウンデーションにてFAPIというWGが作られています。

f:id:ritou:20210905025528p:plain

元々金融サービス向けのプロファイル作成を目的として作られたのでFAPI("Financial-grade API")という名前になっていますが、ヘルスケア分野のセンシティブなデータのやり取りなどの基準としても使えるようにということで中身は「大事なデータを扱うユースケースにおけるセキュリティプロファイル」と言ったところになっています。

最初は銀行APIの "残高読み取りだけ" = Read Only, "預け入れなども行う" = Read & Write みたいな立ち位置だったのが、"Baseline" と "Advanced" という名前でプロファイルの仕様が分けられています。

f:id:ritou:20210905025702p:plain

ここからは、FAPIでも言及されている仕様で使われている、"Webアプリケーションをセキュアにするための実装のヒント" を紹介します。

と言うと、なんかとても難しい話になりそうですが、実際はそんなことはありません。

直接通信と間接通信

f:id:ritou:20210905025724p:plain

OIDCやOAuth 2.0の仕様を分解していくと、サービス2者間、もしくはユーザーが絡んだ3者間のデータのやり取りです。 それらはアプリケーション間の直接通信、ブラウザが絡んだ間接通信と呼ばれています。

直接通信には "誰のリソース(属性情報)" と言う概念がなくアクセス元が何者かだけを検証するための "クライアント認証" を可能とするリクエストと、"誰のリソース(属性情報)" と言う概念が入った "アクセストークンにより保護された" リクエストがあります。

間接通信では、ブラウザを利用した一般的な GET/POST のリクエストがよく使われています。

直接通信

f:id:ritou:20210905025824p:plain

まず、クライアント認証と呼ばれるところから見ていきます。

"Googleでログイン" みたいな機能を実装する際に、Googleから

  • クライアントID
  • クライアントシークレット

を発行してもらって、Webアプリの環境変数に設定して...みたいな作業を行ったことがあるでしょう。それぞれは識別子とパスワードみたいなものです。

この正しいシークレットを保持していることを確認する仕組みが共有暗号方式、RSAやECDSAなどの秘密鍵/公開鍵のペアを作成して公開鍵を相手に渡して...ってやるのが公開鍵暗号方式と呼ばれていますが、クライアント認証でもこれらに相当したいくつかの方式があります。

f:id:ritou:20210905025852p:plain

最もシンプルなのが、クライアントシークレットをリクエストにそのまま含んで受け取った側が正しいものかどうかを検証する方式です。

  • POSTのリクエストボディのパラメータに含む ("client_secret_post")
  • Authorization ヘッダにBasic認証のパスワード相当として含む ("client_secret_basic")

と言うものですが、プロキシその他なんらかの方法でリクエスト自体を取得されるとクライアントシークレットの値を確認でき、第3者になりすましをされる可能性があると言うリスクもあります。

それを少しセキュアにしようとすると、"そのままじゃなくて署名生成の鍵として使おう" と言う方式です。("client_secret_jwt") リクエストにクライアントシークレット自体が含まれないため、リクエスト自体を取得するだけでは第3者にクライアントシークレットの値を把握されることは困難になります。

f:id:ritou:20210905025917p:plain

さらに公開鍵暗号の仕組みを利用すると、リクエストを作成できるのが秘密鍵の所持者のみ、となります。 これの何が便利かと言うと、何かあった時に「あなた、このリクエストを確かに送りましたよね」と言う証拠として扱えることです。いわゆる否認防止(Non-repudiation)と呼ばれるものであり、センシティブなデータを扱う分野だと重要になってきます。

また、アプリケーションレイヤではなくTLSのレイヤでこれらを実装する方式もあります。Mutual TLSなんて呼ばれていますが、TLSのクライアント証明書を利用するものです。署名生成/検証処理をTLSに任せ、アプリケーションレイヤでは鍵の所持者が適切かと言うあたりを検証するイメージですが、最近はAzureのなんとかやAWSのなんとかで対応が進んでいたりします(説明が雑)ので今後はやってくるかもしれません。

f:id:ritou:20210905025947p:plain

アクセストークンを用いたリソースアクセスの保護については、2種類に分けられます。

  • "Bearer Token" と呼ばれる、アクセストークンを持っているものがその権限を持っていると言う仕組みです。現実世界では電車の切符などがそれに当たります。拾った切符でも有効だったら電車に乗っていけますね。
  • "Sender-Constrained" と呼ばれる、トークンを発行された対象であることを検証可能な仕組みです。海外の航空チケットの場合、(本人と顔写真などで紐づけられた)パスポートとの関連を確認することで搭乗が許可されます。

"Sender-Constrained" の方がセキュアであり、それを実現するための仕組みとしては先ほど紹介したMutual TLSを用いるものや署名付きのJWTを利用するものがあります。

f:id:ritou:20210905030008p:plain

直接通信のポイントをまとめます。 まず、そう簡単に暗号化、Encryptionまでは要求されません。これは割と大事なことです。 クライアントシークレットを利用する仕組みでも、値そのものを送るのではなく署名生成の鍵として利用する方がセキュアである。 そして、トークンを利用する場合も、"Sender-Constrained" にすることでリクエストをセキュアなものにできます。

間接通信

f:id:ritou:20210905030031p:plain

次に間接通信です。 URLをブラウザで開く、でおなじみのGETアクセスですが、OIDCやOAuth 2.0では3種類ぐらいの方式が利用されています。

クエリパラメータを使うのは最もシンプルな方法ですが、ユーザーが手元で値を改竄できたり、サーバーにそのままログが残ってしまうことが懸念としてあります。

OIDCやOAuth 2.0の各種トークンがログに落ちるとそれだけで "漏洩した" となってしまう可能性があるので、GETで重要な値をやりとりする場合はフラグメント部に指定する方式をとります。 "https://example.com/hoge#foo=bar" みたいなやり方ですね。こうするとサーバーに値は落ちないのですが、リダイレクトするときにブラウザがこの値を引き回すかどうか、引き回した時の脆弱性が一時期話題になったりしました。

そして、こちらでも署名付きのJWTにしたら改竄されても検知できるじゃんと言うお話が出てきます。

f:id:ritou:20210905030052p:plain

サーバーにログを残したくないやり方として、POSTでリクエストを送る方法もあります。 個人的には簡単に値をいじってリクエストを送るのがちょっと手間になるのでアレなのと、Microsoftの仕組みはPOSTのリクエストが好みなようです。

こちらも値をそのまま送るのと、署名付きJWTにして送る方式があります。 ちなみにドメインまたぎのPOSTリクエストは受け取った時にHTTP Cookieが送られてこないみたいな3rdParty Cookie, SameSite属性と言ったあたりの話が出てくるので注意が必要です。

f:id:ritou:20210905030146p:plain

OIDCの認証要求を複雑にしたり、認証と同時に決済の要求をしようとしたり...なんてことを考え始めると、どんどん送りたいパラメータが増えていきます。そしてそれをJWTなんかにしたら文字列のサイズが...となることがあるため、直接通信と間接通信の組み合わせもよく使われます。

ここで言うPush/Pullはデータの流れだけを意味していて

  • Push : 先にデータを直接通信で送っておいてそのキーを間接通信に含むやり方
  • Pull : キーとなる値を間接通信で受け取り、受け取った側が直接通信でとりにいくやり方

と言う2種類があります。

なんで両方あるかと言うと、OIDCでの直接通信が

  • RP -> OP 多用されている
  • OP -> RP あまり利用されない

みたいな特徴があるためです。

開発中にlocalhostで立ち上げたRPのWebアプリを考えてみてください。そこからOPに直接通信を送るのは簡単ですが、OPからのリクエストを受け取ることはできません。

一方、間接通信はブラウザがあれば可能です。 最近のゼロトラストの流れで「トラストの境界はネットワークからIDに変わった」なんて言葉を聞くことがあるかもしれませんが、以前はネットワーク制限で直接通信が不可能な場合に間接通信だけで実装可能な仕組みが使われきたんだ、とSAMLに詳しい先輩方に教わりました。OIDCでも同じことができます。

さらにこれをJWTにするのも当然ありです。

f:id:ritou:20210905030220p:plain

あとはちょっと応用として、ID連携においてはRP -> OP -> RPと言う往復、海外では "OAuth Dance" と呼ばれたりしている挙動が出てきます。 2つの間接通信が絡むため、CSRFや値の横取りなどで忙しい世界線です。

f:id:ritou:20210905030249p:plain

このような場合、RP側でセッションと紐づけた値をリクエストに含み、レスポンスを検証する必要が出てきます。 これもJWTと組み合わせることで改竄検知が可能になります。

f:id:ritou:20210905030309p:plain

連続した間接通信について、OIDCやOAuth 2.0ではこれらのパラメータで保護することが可能です。この辺りで検索すると私のブログが引っかかると思うのでよろしくです。

f:id:ritou:20210905030345p:plain

間接通信について紹介しましたが、こちらもそう簡単に暗号化までは求められません。 改竄されたくなかったら署名付きJWTを使う、露出させたくなかったら直接通信で送れと言う感じです。

f:id:ritou:20210905030406p:plain

まとめ

後半のまとめです。 OIDCのセキュリティプロファイルで指摘されている拡張機能で使われている、直接通信と間接通信をセキュアにする仕組みを紹介しました。

f:id:ritou:20210905030429p:plain

ID連携のための標準化仕様のうち、拡張仕様と呼ばれる仕様のいくつかは既存の仕組みのどこかをこの辺りのテクニックを用いて強化するものです。 今回紹介したあたりを意識しておくと、今後もし他の標準化仕様を読む機会ができても怖くなくなるかも...と思います。 もし次回があれば、実際のID連携の細かい処理単位で解説、ってのもしたいなと思ってます。

まとめ

f:id:ritou:20210905030429p:plain

ID連携のための沢山の標準化仕様のうち、今回は「何かをできるようにするための仕様」と「既存の仕様の一部を拡張してセキュアにするための仕様」に着目し、 ゆるふわな感じで 紹介しました。

前半について、ID連携というと「〇〇でログイン」の部分だけ、せいぜいそれとAPIアクセスぐらいを想像される方が多いかと思いますが、関連機能も標準化は進められています。 「あんなこといいな できたらいいな」なんて言う要望を叶えてくれるのはどこぞのポケットではなく標準化活動であると言えるでしょう。

後半について、この分野の仕様を調べていくとどんどん関連する仕様が出てきて頭が混乱しそうになることがあります。(みんなそうです。そしてTwitterで「OAuthなんもわからん」「OIDCなんもわからん」と書いて私の知り合いに本を薦められるのです。) しかし、基本的に通信をセキュアにするための実装パターンは限られており、それをベースとなるシーケンスのどこにどう導入するかというのが拡張仕様そのものになっていたりします。 言ってしまえばID連携なんてのはユーザー情報がメインのサービス間データ連携であり、「枯れた」と表現されることもある定石テクニックが仕様に採用されているわけなので、それを普段のアプリケーション開発に活かすことは難しいことではないでしょう。

以上です。

f:id:ritou:20210905030450p:plain

スライド

speakerdeck.com

超参考になる資料

www.amazon.co.jp

みんなで読みましょう!

ではまた!

NASCAR problemを軽減できるかもしれない簡単な実装とさらなる妄想

f:id:ritou:20210819183905p:plain

こんばんは、ritouです。

いつもお友達同士でキャッキャウフフさせていただいているTwitterにてこんな投稿をしたところ、珍しくたくさんの反応をいただけました。

簡単に説明すると、

  • ソーシャルログインとかメアド/パスワードとかでログインできるサービスがある
  • 一度ログアウトして再びログイン画面を訪れると、「お前は前回Facebookでログインしたよな」って教えてくれた
  • どれ使ってたっけ...で迷わない!いい感じ〜

ってな話です。

どう実装されているか

  • ログインセッションそのものを扱うCookieとは別のCookieに認証方法を保存
  • ログイン画面ではその値を見て表示出しわけ

と言うようになっています。

f:id:ritou:20210819180525p:plain

ちなみにここを "tw" に変更すると、お前前回Twitter使ってたよな!?になります。

値の保存方法はCookieでもlocalStorageでも良いでしょう。 持ってる情報としてもそんなにセンシティブな扱いではなさそうです。

Googleのような細かく情報を残したログアウト状態を実装したわけではなく、単純に方法のみを残しておくだけでシンプルに実装可能です。 スマホ時代にあまりログアウトしないけどな〜って言うコメントもありましたが、それはそうですね。必ずしもみんなに需要があるわけではないでしょう。 しかし、PC向けのサービス、複数の認証方式を採用しているサービスは、あまり考えずにこれを使ってみても良いのではないでしょうか。

さらなる妄想

ここからは妄想です。

引用RTであったのが、「新しい端末でもこれできるといいな」ってやつです。

それに対して「その辺りはブラウザに頼みましょうや」と答えています。 この発言の背景としては、次のような展開を想像してのことです。

例えば

  • lastSignInMethod.set(method), lastSignInMethod.get() みたいなブラウザのAPIが定義される
    • ログイン成功時に lastSignInMethod.set("fb") を呼ぶ
    • ログイン画面で lastSignInMethod.get() を呼ぶ

みたいな標準化が行われたとします。 すると、「このサービスで前回何でログインしたか」を統一的な方法でブラウザに覚えてもらうことができるようになりますね。

少し別の話をしましょう。 この前、WebOTPの拡張についての記事を書きました。

ritou.hatenablog.com

この記事で紹介されている仕組みでは、ChromeではPCとAndroidで同じGoogleアカウントでログインすることでAndroidChromeにて受信したWebOTPをPCのChromeの方に送られます。 また、パスワードの同期の仕組みがあれば別環境の同一ブラウザで過去に利用したサービスを訪れた時に保存しておいたパスワードを自動入力することも可能でしょう。

こう言う仕組みを今回の「前回のログイン方法」にも当てはめるとどうなるでしょう。

  • PCであるサービスにFacebookでログインする
  • Androidのブラウザでそのサービスを開いてログイン画面に行った時に、「前回Facebookでログインしましたよね」みたいな表示になる

っていうのが実現できそうではないですか?ここまでできるとわりといろんな人が幸せになれそうです。

以上、単純な機能だとしても、いわゆるWeb標準的な仕組みにしてブラウザベンダーが頑張ってくれたら夢が広がりますね〜っていうお話でした。 こういう細けぇお話ができる場が少ないな〜って思いましたが、自分が勉強会をしていないからでした。申し訳ございません。

ではまた!

セキュアなトークンへのJWT適用について

f:id:ritou:20210815061808p:plain

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

こういう記事を読みました。

lab.astamuse.co.jp

Webアプリケーションでセキュリティ対策のためにランダム文字列な文字列する場面が多々あります。例えば

  • CSRF対策のトーク
  • OAuthやOpenIDに使用するnonce, state
  • メールの到達確認用URLのトーク
  • パスワードをhashする際に使用するsalt

これらの値は単に衝突しなければOKというものではありません。十分なセキュリティ強度を確保するためには推測不可能なランダム値を使う必要があります。

私は持病を持っているため、ついつい「あー、これJWTなら...」と考えてしまいます。 今回は、こういうセキュアなトークンの方にJWTを使ったらどうなるかというお話をざっくり書きます。

ちなみに、乱数生成の話に異論を唱えるわけではありません。 今回の要件にある

  • 衝突しない
  • 推測不可能

ざっくり言ってしまえば前者はデータ設計、後者はセキュリティ面からの要件と言えるでしょう。

  • 重複はしないんだけど生成される文字列の順番が決まってる文字列生成関数
  • オートインクリメントな数値

とかを(プライマリー)キーにしてデータを管理しており、それを外部に露出して推測されて困るような場合、記事にあるような乱数文字列をふったりマッピングをもったりする必要が出てくるかもしれません。

そこで、JWTを使うとどうでしょうか?以前、似たような話を書きました。

ritou.hatenablog.com

この記事では "推測困難な文字列としてJWTが使えるんじゃないか?" という主張をしていますので興味ある方はどうぞ。 私の記事はいわゆるJWS(JSON Web Signature)を使うお話が多いのですが、キーの値をPayloadに含めることを考えます。

  • JWTのPayloadはデコードしたら読める。ので、 データのキー自体は確認できるし推測できても良い場合 には使える。
    • ログイン中のデータ操作などはACLちゃんと確認しよう
  • 署名がついているので、キーを変えて他人のリソースに...ってのは困難

という点から、

記事にあるような乱数文字列をふったりマッピングをもったりする必要が出てくるかもしれません。

これをしなくても実用に耐えうるものとなるでしょう。

もちろん、良いことばかりとは言いません。

  • 鍵の管理が必要 : パスワードの処理とかには向かないかもしれません
  • 署名生成/検証処理が必要 : たくさんの処理をするものであれば無視できない部分でしょう

という話は出てきます。 それでもJWTを適用するメリットを享受するために、"文字列自体にメタデータを保持できる" 点を挙げておきます。

例えばセキュアなトークンにこんなメタデータを追加していったらどうなるでしょう。

  1. 単純な文字列 "だけ" をキーにしている場合 : "1234567890qwerty"
  2. 用途を含む "prefix" などがついている場合 : "sid_1234567890qwerty"
  3. 生成日時、有効期限などがついている場合 : "sid_1234567890qwerty_1628974596"

こういう値を追加することによって、データストアを参照する "前" にある程度弾くなどの処理ができそうです。 いろんな用途でセキュアなトークンを利用したい場合、キーとなる値 + 様々なメタデータを含むことで無駄なデータストアの参照を減らしたり、別の用途に文字列を利用も検知、拒否できます。

JWTの場合、署名により改竄されていないことを検証できるので、あとは

  • Header : 用途をこのレベルで分けてしまい、署名検証前にチェックする
  • Payload : 発行日時や有効期限などを持ち、データアクセス前にチェックする

と言った部分を工夫することで、必要最低限のデータアクセスを追求できるのではないでしょうか。

いかがでしたでしょうか?

JWTは "データを保持する際のキーとなる乱数文字列" をメタデータとともに保持することで、キーの種類に関わらず改竄不可能な値として扱えて無駄を省いたハンドリングが可能なので、セキュアなトークンとして利用するのに便利な仕組みじゃないですか? というお話でした。

ではまた。

Chrome 93で実装されたCross Device WebOTPフローを試してみた

f:id:ritou:20210801015355p:plain

おはようございます ritou です。 8月ですよ。お仕事の進捗大丈夫ですか? 最近、Google方面からDecoupled AuthNの香りがしたので追ってみました。

何の話?

この話です。

developer.chrome.com

WebOTPとは?

Webアプリケーションがモバイル端末でSMS経由のOTP認証をしようと思った時、画面に「認証コードを送信しました」なんて出た後にSMSを確認できるアプリを起動して確認してまたブラウザに...とかをやったことがある人は多いでしょう。 WebOTPとは、モバイル端末上のブラウザであればアプリの切り替えをせずにワンタップでSMSで受け取ったOTPの値を読み込んで検証リクエストに繋げられる、しかもSMSを発信したWebアプリを特定できるフォーマットになっていることでフィッシングサイトからはこのフローを使えなくするような仕組みのことです。

web.dev

Cross Device WebOTPとは?

Desktop(PC) ブラウザとモバイルブラウザで同じユーザーでログインしていたら、Desktop側のブラウザでWebOTPのシームレスなフローを実現できる仕組みのことです。

細かい話は以下のドキュメントに書いてあります。

docs.google.com

どんな動きをするかと言うと、

  1. ユーザーがPCのChromeでSMS検証フローを開始
  2. WebアプリはWebOTPに対応したOTP入力フォームを表示しつつSMS送信
  3. SMSを受け取ったAndroid端末で動いているChromeがOTPを取得、ユーザーの許可を得た上でPCのブラウザに送信
  4. PCのChromeでOTPが自動入力されて処理を継続

と言う流れで、"手元のAndroidでOTPを受け取ってPCに送り、PCブラウザでSMS検証が完了できる" のが特徴です。

試した

デモのやり方が書いてあります。

準備

  • PCブラウザとしてChrome 93以降を用意する(私はChrome betaを用意しました。)
  • Google Play Service のバージョンが 20.30.12 より上のAndroid端末を用意し、こちらもChrome betaを入れる
  • Android端末の設定でChrome betaを規定のブラウザに設定する
  • どちらの環境でもGmailなどを開き、同一ユーザーでログインする
  • 念のため再起動する(ある人から聞いたおまじない)

デモサイトの使い方

デモページを開きます。

web-otp-demo.glitch.me

f:id:ritou:20210801022431p:plain

Send following SMS message to this phone from another one after pressing "Verify".

とあって

Your OTP is: 123456.

@web-otp-demo.glitch.me #123456

と書いてあるので、"Verify" を押したらターゲットとなるAndroid端末に向けて別の端末からSMSを送ったり、Twilioの無料枠を使ったりしてSMSを送信します。

f:id:ritou:20210801022847p:plain

あとは入力フォームのまま、PCブラウザでは待ってれば良いです。 AndroidにSMSが届くと、PCブラウザにOTPを送っても良いか?と聞く通知が来るので「送信」を押すと、PCブラウザの画面でOTPが入力されたようになって検証成功します。

成功したときのPC側の画面を録画したものを含むTweetを載せておきます。

いかがでしたか? 今までのOTPの認証フローがChromeのサポートにより Decoupled AuthN へと進化してしまっています。

何がすごいのか

個人的には

  • 互換性を保ちながら便利に使える人が増えること
  • OTPの認証フローを使っているサービスであれば誰でも使えること(そう、Googleユーザーならね)

と言う2点で、Googleさんすごいなって思います。

前者について、新しい認証フローみたいなのを考える場合「対応していない環境」をどうするかが課題になります。 WebAuthnの場合は関数の利用可否をもとに「導線を出さない」みたいな実装がされたりするでしょう。 それに対して今回のはこれまでのOTPのフローを拡張したものです。環境によって

  • SMSを受け取れる端末上のブラウザならば : OTP取得をスムーズに実現可能
  • PCブラウザでもSMSを受け取れるAndroid端末で同一のGoogleユーザーでログインしていたら : OTPを送信してスムーズに利用可能
  • どちらでもなかったら普通のOTPフロー

と言うように、WebOTPに対応しているだけでWebアプリ開発者が意識しないところで "勝手に" 便利にできるわけです。 これは良いですね。(ユーザーサポートと言う点ではパターンが複雑になる面倒さもあるかもしれません。)

後者については、これがGoogleにログインするため"だけ"のものではないということです。 WebOTPを導入している全てのサービスがこの恩恵を受けられる可能性があります。 ちょっと前にAppleがWebAuthnを拡張して便利にするぞ〜みたいな話を書きましたが、広い意味では似たような感じですね。

ritou.hatenablog.com

今後もプラットフォームやブラウザによる便利な実装が話題になったらチェックしてみたいと思います。 ではまた!

(動かね〜ってTweetしたら色々とサポートしていただいた えーじさん、ありがとうございました!)

フィッシング被害が多発しているというAmazonからのセキュリティ通知の仕様の危うさ

こんにちは、ritouです。

こんな記事を見かけました。

internet.watch.impress.co.jp

Amazonをかたるフィッシングが報告全体の35.8%を占め、特にSMSによるものが多かった。同協議会では、SMSではメールよりも本物と誤解したり、ついアクセスしたりしやすいため、注意が必要だとしている。

Amazonでなんでもポチれる時代にこれは切ない事態です。 今回はその Amazonから送られてくるセキュリティ関連の通知の仕様がよろしくないのでなんとかして欲しいというお話 をちょっとだけします。

セキュリティ通知

中の人じゃないので細かい仕様を全て把握できているわけではありませんが、例えばログインの設定をいじろうとしたときなどにこんな画面が表示されます。

f:id:ritou:20210709115406p:plain

SMSとメールで通知を送ったので確認しろという内容です。

その下の謎の申請ってのはもしかしたらアプリへの通知かもしれませんが、昔いろいろあって今はアプリを入れてないのでSMSとメールを見てみます。

そして送られてきた内容を見てみましょう。

f:id:ritou:20210709115516p:plain

左がSMSです。

  • 送信元の検証が困難すぎぃ
  • URLを送ってくるUXが存在することでフィッシングにかかりやすくなる
  • URLつけたら届かないとかいう都市伝説も昔からある気がする

と言った観点からもSMSでリンクを送るのはよろしくないでしょう。

こんなことやってるから "Amazonをかたるフィッシングが報告全体の35.8%を占め、特にSMSによるものが多かった。" なんて言われてしまうのです。

右のメールの方も、

このEメールがAmazonから送信されていることを確認するにはどうすればよいですか? このEメールのリンクは「https://www.amazon.co.jp」で始まります。以下のリンクをいつでもブラウザに貼り付けて表示できます。

とか書いてあってなんとなく厳しい感じが伝わってきます。

URLの検証が困難なので個人的には避けたいところですがメールでURL送信は長い長い歴史があるので難しいところですね。

提案するなら

  • リンクは送るな
  • 何かを送ってそれを入力させる、2FAでよくあるフローの方がまだ良さそう
  • メールで怪しくないアピールするならURLにアクセスして表示された後もドメイン確認しろって言え

あたりでしょうか。

今度、アプリの方も見てみます。

おまけ

メールのタイトル。

f:id:ritou:20210709115031p:plain

何を言ってるんだ、amazon.co.jpはお前だろ。

ではまた。

FIDOアライアンスのUXガイドラインには何が書いてあるか

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

前の投稿でも取り上げたWebAuthnとかFIDO認証って呼ばれてる認証方式のお話です。

ritou.hatenablog.com

今回は、FIDOアライアンスからUXのガイドラインが出ているので紹介します。

fidoalliance.org

一言で言うと

MacとかiOSとかAndroidとかWindowsの生体認証が使えるデバイス上でWebアプリを使ってるユーザーに対して、新規登録/ログインフローのなかでをどうやってデバイスを登録させてFIDO認証を使わせるか

っていうお話です。

今回のターゲットとしては一般的なユーザーが使いつつ、強固なセキュリティが求められる銀行のWebアプリケーションのようなところを想定しています。FIDOにより一番平和になって欲しい世界のお話です。

これまでtoCのサービスでも、FIDO認証を取り入れる際には「YubiKeyのようなセキュリティキーを用意させるよりも "デバイス自体" を登録してWindows/Mac OS/iOS/Androidそれぞれで提供されている生体認証をする方が使われるようになるだろう」というのは以前から言われていました。

しかし、実際にどのようにデバイスの登録を求めるかと言ったあたりは先行して導入するサービスがそれぞれ探り探りやっているような部分があったので、このUXガイドラインによりその辺りの迷いが解消されることに期待せざるを得ません。

構成

UXガイドラインは3つのコンセプトとして User journey, Process steps, Use Case の3つがあります。

  1. User journey : エンドユーザーがFIDOのAuthenticatorを登録し、そしてログインするまでの処理が対象です
  2. Process steps: “sign in(ログイン)” とか “detect device eligibility(デバイスの適合性の検出)” みたいな細かい処理をまとめて “awareness” とか “consideration” と言ういくつかのゴールを表現し、目的を達成するためにこうしたら良い!ってのが整理されています
  3. Use Case: 銀行のWebアプリを想定してます
  4. Biometric sign-in or FIDO sign-in: 生体認証もしくはデバイスアンロック手段を用いたFIDO認証を利用することを想定しています

(3つと言いつつ4つ書いてあるじゃないか)

ゴールとProcess Stepsの関係はこんな感じで図示されています。

f:id:ritou:20210701121005p:plain

  • Awareness : このサービスはFIDO認証に対応してるよと伝える
  • Consideration : ログイン中のユーザーをAuthenticatorの登録に誘導する
  • Registration : Authenticator を登録する
  • Sign-out (post registration) : Authenticatorを登録してからそれでログインしたりログアウトしたり

と言う感じですが、まずは細けぇことはあまり気にせず画面ごとの要件を拾っていければ十分でしょう。

テストサイト

このUXガイドラインの参照実装として、テストサイトが用意されています。

https://digitalbank-test.com/

  • メールアドレスや電話番号などは不要
  • ユーザー名、パスワードで登録
  • 登録したユーザー情報は30日で消滅

となっているので気軽にお試しいただけます。が、どうせみんな見ないでしょう。 IDの技術なんてそんなもんです。「後で読む」「後で見る」IDおじさんは知っています。なので動画をとりました。

これはiPadでもちゃんと動いた!(けどAuthenticator登録完了時に必ずエラー出るな!)」って言う動画です。

www.youtube.com

ガイドラインにもこのサイトのスクリーンショットが掲載されています。 この後の説明でも実際に動作させた自分用のスクリーンショットを載せていきます。 では見ていきましょう。

UXガイドラインの内容紹介

Awareness: Sign in Pre-FIDO Registration

目的 : 自サービスでFIDO認証が可能であることを複数の方法で宣伝する

最初に、FIDO認証をサポートするデバイスを利用するユーザーに対して、永続的/一時的なメッセージングによりRelyingParty(FIDO認証を利用するサービス)がFIDOに対する認知度を高めることが説明されています。ユーザー調査によると、登録(Authenticator登録の意味かも?の)前に何度もFIDOの概念について触れる必要がありそうとのこと。

まず、未ログイン状態のログインフォームを見てみましょう。

f:id:ritou:20210701122430p:plain

ここにはFIDOと言う文字は出て来ません。プラットフォーム毎の生体認証っぽい画像を表示することでこのサービスは生体認証に対応してるんだな〜ってのを認識させようと言うのが狙いです。(私の環境はChrome on Mac OSなのでTouchIDっぽい指紋認証のアイコンが表示され、マウスオーバーで説明が出て来ます。AndroidWindows 環境の時はそれぞれのプラットフォームの中で使われている生体認証の画像を使うという細かい出しわけが紹介されています。)

この後に出て来ますが、うちのサービスはFIDO認証に対応してるんだ!ではなく、生体認証に対応してるんだよってアピっていくと言うのがポイントですね。

Because the FIDO brand is not yet familiar to most consumers...

謙虚!

他にも

  • セキュリティ設定変更機能からいつでもAuthenticatorを登録/削除できるようにする
  • eメールのチャンペーンやメール配信、ソーシャルメディアで生体認証が可能であることを通知する
  • CSに教育する

みたいな涙ぐましい努力をしながら、FIDO認証を促進していかなければなりません。

Consideration: Targeted invitations

目的 : FIDO認証をサポートするデバイスにログイン中のユーザーを、Authenticatorの登録画面へと案内する

Chrome on Mac OSSafari on iPad OSなどでテストサイトに登録して再度パスワードを用いてログインし、ログイン状態になったら、画面に案内用のメッセージが表示されます。

f:id:ritou:20210701122503p:plain

ここでの"FIDO認証をサポートするデバイス" ってのはいわゆる Platform Authenticator の利用が可能なブラウザのことであり、WebAuthnの仕様でいうところの "isUserVerifyingPlatformAuthenticatorAvailable()メソッド"を使って判断をしているように見えます。

なので、例えば同じPCでも生体認証が動かないブラウザの場合はこれは表示されません。

あと、ガイドラインにはもう一つ "Spotlight" page takeover invitation format for Mac users ってのが載ってます。 どっかの広告のように全面に主張してくる感じなんだと思いますが、どこで出るんやこれは...

その他、

  • パスワード認証に代わる安全な認証方式だという価値を示す
  • 案内のメッセージは "シンプル(シンプルな認証方式を利用できるよ)" もしくは "オプショナル(安全な認証方式を追加できるよ)" が効果的
  • 対象デバイスの場合は何回も案内しよう!けど設定は必須にせず、ユーザーの制御可能にしておく(ウザがられたらダメ。要はバランス...)
  • Authenticator登録ページにたくさんリンクさせる

などが記載されています。

Registration

目的 : 対応デバイスを利用しているユーザーにFIDOについての教育を行い、Authenticatorを登録させる

先ほどのトースト通知から "Register now" に進んでいくと、Authenticator 登録のための説明画面に遷移します。

f:id:ritou:20210701122534p:plain

  • この設定は必須ではないので右上の "×" から取り消すこともできる。ユーザーが制御可能なように
  • アニメーション画像で生体認証の流れを説明
  • 既にコンピュータのアンロックに使っている方法でアカウントにログインできることを説明
  • "Learn more" を押すとFIDOの説明が展開
  • FIDO CERTIFIEDなロゴ表示

ここぞとばかりにFIDOの説明してますが、これぐらいしないとユーザーは何だか分からなくて使わないってことなんでしょう。 ちなみにFIDOのロゴ利用についてはこの辺りを確認しましょう。

Logo Usage & Style Guide

Organizations that wish to use the FIDO logo on websites must adhere to the terms of our FIDO Trademark and Service Mark Usage Agreement for Websites license. You can download our logo files here.

Authenticate

目的 : Windows HelloやApple Touch IDでログインできるようにAuthenticatorを登録する

いわゆるAuthenticatorの登録処理です。 WebAuthnの仕様でいうところの "navigator.credentials.create()" を呼び出すところですね。

先ほどの画面から "REGISTER" を選ぶと処理が始まります。

f:id:ritou:20210701122600p:plain

成功すると画像も成功っぽくなってます。

f:id:ritou:20210701122624p:plain

失敗の場合もそれっぽく変わります。Platformごとの生体認証の画像に続いてこの辺りは芸が細かいなーと思ってしまいますが、現代のUXとしてはそれぐらいやらんといけないってことなのでしょうね。

ちなみにテストサイトでは Authenticator登録直後に100%の確率でIBMのなんたらエラー画面が出てウワッてなりますが、一旦心を落ち着けてトップのURLにアクセスしなおせば何事もなかったかのように続けられます。

FIDO Sign-in

目的 : 登録済みのデバイス上でユーザーをFIDO認証する

最後に登録済みのAuthenticatorを用いてログインさせる部分です。 WebAuthnの仕様でいうところの "navigator.credentials.get()" を呼び出すところですね。

テストサイトでは、ログアウトした後に

  • ユーザー名
  • "SIGN IN" ここからWebAuthnのログインが始まる
  • FIDO CERTIFIEDロゴ
  • ユーザー変更リンク : 空のパスワード認証フォームへ
  • パスワード認証への切り替え : ユーザー名が保管されたパスワード認証フォームへ

といった構成の画面になります。

f:id:ritou:20210701122653p:plain

これなら一回でログイン処理が走りますが、ここで注目なのは"ログアウト" してもユーザー名が保存されているということでしょう。

我々はつい "ログアウト" というと完全にセッションデータを吹き飛ばし、フォームに何もない状態からのやり直しを想像してしまいますが、そこからこの処理を行う場合は一度ユーザーを特定する必要があります。

この辺りは

  • 最後にログインしていたユーザーのユーザー名を記憶しておく
  • そのユーザーがFIDO認証を利用可能な状況(環境+デバイス)ならばログイン画面からすぐに開始できるようにしておく

というあたりを意識していく必要があるのかなと思います。 昔から "最後にログインした方法" "最後にログインしたユーザー" を記憶しておくみたいなアイディアはあるので、この辺りはもう少し整理しようと思います。

f:id:ritou:20210701122721p:plain

ちなみに後から気づきましたが、このテストサイトは Console Log に処理途中のデータもいくつか流しているのでWebAuthnの具体的な実装を見たいときにも参考にできそうです。

(ログアウト状態でも最後に利用したユーザー名が保存されているあたり)

f:id:ritou:20210701122805p:plain

(WebAuthnのログインが行われる時もログが吐かれるあたり)

f:id:ritou:20210701122821p:plain

それ以外にも、Authenticatorの管理もできるようになっているので、この辺も参考にできそう。

f:id:ritou:20210701122901p:plain

と言ったあたりで一連の流れに沿ってガイドラインの内容を紹介しました。

感想

今回のガイドラインには

アカウント登録 -> Authenticator登録 -> ログアウト -> FIDO認証でログイン

の流れに沿ってどのようなUXにしたら良いかというのが書かれています。 また、FIDOアライアンスからはいわゆる "アカウントリカバリー" のために複数のAuthenticatorを登録させるためのホワイトペーパーも出されていたりするので、今回のと組み合わせて参考にしながら実装してユーザーに提供する必要がありそうです。

すでにいくつかのサービスでFIDO認証は使われ始めていますが、開発者向けのサービスより一般的なユーザーに向けての啓蒙となるとより詳しい説明が必要になるでしょう。

ちなみに、個人的にこの前ちょっとハマったのが

  1. SMS番号やメアドを入力
  2. ユーザーの登録状態によって分岐
    1. Authenticator登録済みユーザーならWebAuthnの認証フロー開始
    2. Authenticator未登録ユーザーならSMS認証

みたいな処理をレガシーなフォーム送信を使って実装しようとしたら、iOS/iPad OSのSafariでうまく動かないと言う挙動にハマり苦戦しました。 理由はちょいと細かい話で、Safariはユーザーの明示的なアクションが起こらないとWebAuthnの処理が行われないようになっており、"ユーザーのクリック"をトリガーにする場合は問題ないものの、"ユーザーのSubmitボタンによるレガシーなWebアプリのフォーム送信"ではWebAuthnのフローが開始できないみたいな癖があります。Chromeでは問題なし)

今後作るときは、今回のUXガイドラインを参考にしたいと思います。

ではまた。

Appleの発表したPasskeys in iCloud KeychainはWebAuthnをどう変えるのか

f:id:ritou:20210615043226p:plain

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

先日のWWDC2021の "Move beyond passwords" というセッションにて発表された "Passkeys in iCloud Keychain" という仕組みについてどんなものかを紹介します。

developer.apple.com

WebAuthn

数年前からパスワード認証を置き換えると言われ続けている認証技術の一つである "WebAuthn" (やFIDO)という技術をご存知でしょうか。(ご存知ない方は "WebAuthn builderscon" "WebAuthn droidkaigi" などで検索してみましょう) 今回の話をするにあたって、WebAuthnがどんなものかをある程度理解しておく必要があります。

  • 公開鍵暗号の仕組みを利用
    • パスワード認証のようにユーザーとログイン対象のWebアプリケーションがパスワードを共有するのではなく、PC/スマートフォンの端末や外付けのセキュリティキーの中に秘密鍵、を保存しつつ生成した署名を送信、ログイン対象のWebアプリケーションが保存している公開鍵を用いて検証する
  • ローカル認証を利用
    • スマートフォンのロック解除に使われるような指紋、顔認証、パターン、4桁PINなど でユーザーを確認する
  • 認証フローにWebブラウザが介入して一連の認証処理が行われたOrigin情報をWebアプリケーション側が検証可能なため、フィッシングのリスクを排除できる

ということが特徴です。

WebAuthnでは秘密鍵が保存されているデバイスを所持していること、ローカル認証により意図したユーザーが操作していることを検証できるので、単体でもWebアプリケーションのログイン機能の要件を満たせる、かつフィッシング耐性もあるので素晴らしいものだ、という主張です。

この機能が普及するためのキモとして

があります。

前者については、YubiKey(Yubico)やTitan(Google)というようないわゆる "SecurityKey"だったり、スマートフォン/PC自体がその機能を持ちます。 後者についてもPC/スマートフォン向けの対応ブラウザが増えており、今後ますますコンシューマ/エンタープライズ両方での利用が増えていくであろうという状態だとここしばらく言い続けています。

しかし、このWebAuthnの悩ましい問題として、リカバリー困難問題” があります。

  • バイスに保存された秘密鍵は他の端末にシェア/コピーできない

という仕組みのために、デバイスが紛失、壊れてしまったら当然、そのデバイス内に保存されている秘密鍵を用いた認証が利用できなくなってしまいます。

  • スマホもセキュリティキーも、壊れたり壊されたり落としたり無くしたりとにかく平和じゃない(経験済み)
  • PC/スマートフォン自体に秘密鍵を保存するケースでは、新しい端末で使い始める時に手間がかかる
    • 一度 "別の方法" でログイン状態にしてから、"このデバイスでWebAuthnを利用する"(鍵ペアの生成、公開鍵のやりとり) という一連のフローをやりなおさないといけない
  • この "別の方法" で安全なものがなくて困る
    • WebAuthnが最強!なので他の認証方式だと認証強度が弱くなってしまうんじゃ
    • ベストなのは 認証に利用できる状態になったセキュリティキーを大事に引き出しの中にしまっておく ことだけど、コンシューマ向けのユーザーにそこまでさせるのは正直きつい

というのが長年議論されてきました。

Appleの動画にある比較表の"Security Key"の項目で、"Always with you" はユーザーがいくらWebAuthn使いたくても秘密鍵が保持されているデバイスがないとダメよってこと、"Recoverable" はデバイスロスト時に困るってことが表現されています。

f:id:ritou:20210615045708p:plain

Passkeys in iCloud keychain

散々前フリしたところで、Passkeys in iCloud keychainは、ここまで話したWebAuthnの困ってるところをうまく解決しようという試みです。

概要

一言で言ってみると、WebAuthnのキモである バイスに保存された秘密鍵は他の端末にシェア/コピーできない という部分を バイスに保存された秘密鍵を「安全な方法」で他の端末とシェア/同期できる ようにすることで、デバイスロスト/リカバリーの問題を解決しようとしています。

Appleの場合は、iCloud Keychainを用いることで他のApple端末とWebAuthnで利用する秘密鍵を共有するぞってわけです。

iCloud キーチェーンを使えば、パスワードやその他の機密情報をお使いのすべてのデバイスで最新の状態に同期できます。

support.apple.com

用語的には、"Passkeys" = "WebAuthnの秘密鍵を共有可能な仕組み" それを iCloud Keychainから利用するんだというのが "Passkeys in iCloud Keychain" だというところでしょう。いや、テキトーなことは言わんほうが良い。

WebAuthnとの利用フローの違い

例えば、WebAuthnに対応したWebサービスAと手元に🍎なデバイスが2台ぐらい(iPhone/iPad)があるとしましょう。

  1. iPhoneでは指紋認証を設定済み
  2. WebサービスAにiPhoneでログインし、新たなログイン方法としてiPhoneを設定
  3. WebサービスAをiPhoneから利用する際は指紋認証でOK
  4. iPadでは顔認証を設定済み
  5. WebサービスAにiPadから何らかの方法でログインし、新たなログイン方法としてiPadを設定
  6. WebサービスAをiPadから利用する際は顔認証でOK

となっていたところが、

  1. iPhoneでは指紋認証を設定済み
  2. WebサービスAにiPhoneでログインし、新たなログイン方法としてiPhoneを設定
  3. WebサービスAをiPhoneから利用する際は指紋認証でOK
  4. iPadでは顔認証を設定済み、iCloud Keychain で秘密鍵を同期!!!
  5. WebサービスAをiPadから利用する際は顔認証でOK

となります。

f:id:ritou:20210615053954p:plain

セキュリティキー(を持ち歩いてUSBの形があれやこれや言いつつとりあえず使えたわ!っていうの)と同じ体験がAppleバイスを持っててiCloud Keychainで同期してるってだけで味わえます。 このリストだけではあんまり便利になった感覚がないですが、これが複数のWebサービス×複数の端末ってなっていくとじわじわ違いとして現れてくるでしょう。

使い物になるかどうか、懸念点はないか

WebAuthnのデバイスロスト/リカバリーの問題を避けられるとなればWebAuthnの普及促進に繋がるでしょう。

秘密鍵の管理という点では、Passkeys in KeyChainはこれまで "デバイス単位" でセキュリティを担保していたものが、プラットフォームを支える "Identity単位" のセキュリティで担保することになります。 秘密鍵にアクセスできる端末が増えたということは、それだけ不正利用のリスクも増えると考えることもできます。 WebAuthnをログインに採用しているサービスから観ると、これまで設定済みの端末のみに絞られていたリスクが、iCloud Keychainで同期している全てのApple端末全体に広がってしまうことを認識する必要があるでしょう。

このメリット/デメリットを意識するまでもないコンシューマ向けサービスにとっては良いニュースだと思いますが、NIST SP800-63シリーズ で定義されている、厳密なユーザー認証の強度(Authenticator Assurance Level, AAL)を厳密に意識する必要のあるレベルのサービスには多少気になるかもしれません。 "他の端末にシェア可能な秘密鍵は利用できない" "Passkeys in Cloud KeyChain経由のアサーションは受け入れない" みたいな細けぇ分岐処理が出てきたらカオスになるので避けたいところです。

ID連携との違い

Appleはこれを独自の技術としてアッピールするというよりは、「他のPlatform(と言ってもGoogle/Microsoftを名指ししているようなものですが)でも同じような仕組みを用意して使って行ったらいいじゃない」みたいなスタンスを取ろうとしているのが新鮮なところですが、結局大きなPlatformを持つ巨人たちのための仕組みじゃねーか と感じる人もいるでしょう。

バイスからIdentityへみたいなことを言ったついでに、Sign in with Appleのようないわゆる "ID連携" と今回の "Passkeys in Keychain" の仕組みの比較をしてみたいと思います。

  • Webアプリ側はWebAuthnのプロトコルを意識するだけで、しれっとAppleアカウントのセキュリティの恩恵を受けられるのが "Passkeys in Keychain"
    • 開発者はWebAuthnに対応するだけ
    • ユーザーは個々のWebアプリごとに一度手元のApple端末でログインの設定が必要
  • Webアプリ側が明示的にAppleアカウントの恩恵を受けにいけるのが "Sign in with Apple"

Sign in with Appleでも顔認証などを使ってパスワードなしで認証できることから、"WebAuthnによるログインやID連携の設定済みの状態でApple端末からのUX" という点では同等になり得そうですが、開発者、ユーザーと言った視点では細かい違いがあることを意識しておきましょう。

この辺りの比較は、実際に動きを見れる状態になってから比較してみるのが良さそうですね。 できるようになったらやります。

まとめ

  • WebAuthnはセキュリティキーや対応デバイス単位で秘密鍵を保管する仕組みだったが、デバイスロストやリカバリーなどに課題があるってずっと言ってる
  • Passkeys in iCloud KeyChainはiCloud KeyChainの同期の方法を用いて、秘密鍵を安全に共有するための仕組みである
  • バイスからIdentityレイヤーにコンテキストが変わることで、ID連携に近い感覚になるが、こまかいところで違いがある

というお話でした。何はともあれ、普及が楽しみですね。 質問があったらマシュマロかTwitterでいつでもどうぞ〜。

marshmallow-qa.com

ではまた!

【久々の投稿と思ったら】Web24というイベントで 5/8 12:50~14:20にお話します【告知】

お久しぶりです、ritou です。

今週の金/土に行われるこんなイベントにお誘いいただいて、5/8 12:50~14:20にWebアプリケーションとIDについてセッションオーナーとして議論させてもらうことになりました。ハッシュタグ#web24_id です。

connpass.com

どんな展開になるのかのネタばれではなく、最近の私の思いを少し書いておきます。

一言で"WebアプリケーションとID" と言っても、

  • いわゆる認証方式
  • 登録から退会までのライフサイクル
  • ID連携

など多岐にわたっています。

さらに、Webアプリケーション単体で見た時も

  1. Webサーバーからの応答だけ
  2. Webサーバー + SPAのようなクライアントの組み合わせ...
  3. Webブラウザが提供するAPIを利用...

という具合に考慮すべき範囲が複雑化しており、IETFW3Cでは新しいプロトコルAPIが続々と策定されていく... 開発者が個別の機能の要件をしっかりと理解して作り込んでいくよりも、フレームワーク、IDaaSなどを用いて安全性を担保しながらアプリケーションを作り込んでいく時代にあると言えるでしょう。 当然、サービスとしてモバイルアプリケーションが存在する場合はさらに考慮すべき点も増えそうです。

そんな時代に開発者はどのようにIDと向き合っていくべきか、というのをお話できたら良いのかなーと思っており、個人的に標準化仕様、ライブラリやプロダクト開発辺りを意識できる3人の方 にお声がけをさせていただきました。 特に打ち合わせも台本もなくやっていくのでどんな展開になるかは想像もつきませんが、なんとか盛り上がったら良いなと思います。 お時間のある方は見に(聞きに)来て下さい♪他のセッションも豪華メンバーで楽しみですね!

ではまた!

f:id:ritou:20210502175057p:plain

最近よく見かける初心者エンジニアが転職の際に作る今時のポートフォリオに必須だと噂の「ゲストログイン」について

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

f:id:ritou:20210131121026p:plain

タイトル長くてすみません。

QiitaでID連携や認証まわりの投稿記事を見ていると、最近「ゲストログイン」という文字列が目に止まることが増えたので何か書いておきます。

ゲストログインとは

「ゲストログイン」の検索結果 - Qiita

実はそんなに引っかかりませんが、このゲストログインと呼ばれる機能、実装者により内容もバラバラです。 どれが正解ってのもない気はしますが、1/31ぐらいの検索結果からどんな認識なのかを見てみます。

qiita.com

ポートフォリオ必須といわれているゲストログイン。

そうなの?

それとは異なり今回のゲストログインはあらかじめ設定されたデータでUserを新しく作成します。 なので「ゲストログイン」より「簡単新規作成」の方が言葉的に近いかもしれません。

  def guest
    user          = User.new(user_params)
    user.name     = "ゲストユーザー"
    user.email    = SecureRandom.alphanumeric(15) + "@email.com"
    user.password = SecureRandom.alphanumeric(10)
    user.save
    sign_in user
    redirect_to how_to_path
  end

ランダムデータで新規登録&ログインさせる系っぽい。

qiita.com

ポートフォリオを作成する際はゲストログイン機能が必須と言われています。

やっぱり、そうなんだ?

Google認証やTwitter認証を頑張って導入しても、自分のアカウントを使うことが心理的なハードルになって、アプリを良く見てもらえない可能性が出てしまいます。

ソーシャルログインに心理的ハードル...ザワザワ

const user = { name: 'ゲストユーザー', email: 'guest@example.com' };

予め用意してたユーザーでセッション生成っぽい。

qiita.com

今回はポートフォリオを見てもらう確率を上げるために必須な、簡単ログインの実装方法を書いていきます。

また必須言うてる。これあれじゃないの?スクールでそう言われた系じゃないの?

  # emailでユーザーが見つからなければ作ってくれるという便利なメソッド
  user = User.find_or_create_by(email: 'guest@example.com') do |user|
  # 自分はユーザー登録時にニックネームを必須にしているのでこの記述が必要
  user.nickname = "ゲスト"
  # 英数字混合を必須にしているので、ランダムパスワードに、英字と数字を追加してバリデーションに引っかからないようにしています。
  user.password = SecureRandom.alphanumeric(10) + [*'a'..'z'].sample(1).join + [*'0'..'9'].sample(1).join
  end

ニックネーム、メアド固定のゲストさんで(最初は登録しつつ)ログイン状態に。パスワードはなんとなく設定する。

qiita.com

新規登録で mail: guest@guest, password: 111111で登録していること

この人でログインさせる。

qiita.com

たくさん読まれてるやつ。自分もコメントしてみたけどもなんだかもうカオス。 上記の予め用意していたアカウント系のリスクが書いてある。

qiita.com

転職活動用のポートフォリオ等にゲストログイン機能があると、多忙な採用担当の方の負担が減り、アプリを利用してもらえる確率が高まるメリットがあります。

利用した気にさせることが大事っぽい!

ゲストログイン用のユーザー(ゲストユーザー)の登録されている

予め用意されている系

※セキュリティをしっかり固める場合、ゲストユーザーのアカウントを通常のユーザーアカウントと一緒に、usersテーブル/Userモデルで管理するのはあまり望ましくありません。

これはわからん。一緒でもいいだろ。

最後に、セキュリティ的な対策です。 もしあなたの開発しているアプリに、ユーザー名やメールアドレス等を編集できる 「ユーザープロフィール編集機能」がある場合、 ゲストユーザー用アカウントのものに関しては変更できないようにした方が良いと思います。

ゲストユーザーを扱うためにアプリにも分岐入れて判断とかが必要。

と言うところで、どうやら

  • ポートフォリオにゲストログインは必須
  • とは言え定義はなんかふんわりしてる
  • **予め用意したユーザーでログインさせる系が多い

ってぐらいな感じです。

本当にこの程度の機能でアピールになるのかは採用側の経験者の意見も聞きたいところですが、 控えめに言うと フレームワークが標準で提供していない機能を追加するだけでも価値はあるでしょうし、想定外のことが起きてそれに対応することも経験としては重要な気はします。

みたいなことをTwitterで言ってたら質問来てました。

せっかくなのでこれぐらいまで実装したら実用的かも?ってのを書きます。

実用的なゲストログインとは

そもそも「ゲストログイン」と言うか「ゲストユーザー」機能ってのの実用的、汎用的な要件はどうなんでしょう。

  • ECサイトで会員登録をせずに商品を購入するゲスト購入
  • ゲーム系のサービスで会員登録をせずにある程度まで使えるお試しプレイ

あたりを考えてみると、

  • メアド確認やパスワード設定など、新規登録をせずともある程度もしくはフルでサービスが利用できる
  • 他の端末、セッションからは同じユーザーでログインできない/紐づけられたデータを流用できない
  • サービスが気に入ったら改めて新規登録を行い、データは引継ぎつつ全てのサービスを利用できたりできなかったり

みたいなところが要件として出てくるのではないかと考えます。 それを設計/実装に落とそうと思うと

  • Userモデル
  • セッション管理
  • アクセスコントロール
  • 本登録処理とデータ引き継ぎ

あたりになります。それぞれのポイントを整理してみましょう。

Userモデル

まぁ、こんなのは一例でしかないわけでありますが、Userモデルに対する要件としては

  • ゲストユーザーであることを判定できる
  • メアドなどを識別子とする場合は空
  • パスワードなどのクレデンシャルが空
  • ニックネームなどプロフィールは登録可能

ぐらいでしょう。

小規模なアプリにありがちなUserモデルであれもこれも含むてんこ盛りモデルだったりすると "必須" 項目になっているものがあり、なかなか対応が困難になる場合もあるかもしれませんが、あまりランダムな値などを入れるのは望ましくないでしょう。

上記の記事でメアドやパスワードにランダムな値をいれるような実装もみられましたが、実際のプロダクトで "設定することでログイン可能になる" 場合は "知らないからいいだろう" と言う話でもないでしょう。

これは極端な例ですが、規模が大きくなることを想定したアプリなどで細かくテーブルを分けるようにするなら、

  • 識別子を払い出す : User(ID発行は既存のものと一緒で良い)
  • 識別子とロール : UserRole としてゲスト状態を保持
  • 識別子とメアドを紐付ける : UserEmail を持たない
  • 識別子と認証方式、クレデンシャルを紐づける : UserCredential を持たない
  • 識別子とプロフィール情報 : UserProfile は持つ

みたいな管理をすることで、ゲストユーザーの要件に対応できそうです。

ゲストユーザー用のモデルを用意する方法もあり得るとは思いますが、この後言及するアクセスコントロールの部分やデータ引き継ぎなどを実装する際に既存のアプリの機能に大きな改修が入らないように気をつける必要があります。

セッション管理

ECのゲスト購入であれば、購入フローで必要な値を引回す必要があります。 これは未ログイン状態のセッションに紐づけることでも実装できますが、この後言及するアクセスコントロールをしっかり実装できるならば、ゲストユーザーによる処理を開始する際に

  • ゲストユーザーを作成
  • セッションとの紐付け

を行うことで実現できそうです。

アクセスコントロール

ビジネス向けに比べて比率は少ないかもしれませんが、コンシューマ向けのサービスでもユーザーのロールを意識してサービスの利用制限を行う例はあるでしょう。 例えばゲームなどでいわゆるユーザーランクによって利用できる機能が解放されると言うことはそれに近いかもしれません。ゲストユーザーを扱う際も同様です。 上記の記事でも触れられていたものもありましたが 「ゲストユーザーが利用できない機能」を決めておき、利用制限をしっかり行うこと も重要でしょう。

本登録処理とデータ引き継ぎ

ゲームなどのゲストログインでは

  • ゲストユーザーの利用が制限された機能を利用しようとした
  • 利用可能なゲームのポイントを失った

と言うあたりで本登録処理を促し、そのまま設定などを引継いで利用できるような実装が必要となりそうです。 ECサイトでは購入フローが完了した後などに会員登録を促して継続的な利用を促したりもするかもしれません。

Userモデルのところで意識したゲストユーザーと通常のユーザーの違いを意識して、既に設定してあるプロフィール情報や同一セッションでユーザーが入力した情報は生かしつつ不足している部分を埋める実装が必要となるでしょう。

まとめ

  • 最近目に付くゲストログインについて調べた
  • ここまでやれば実用的かな?ってとこを説明した
  • スクールかなんか知らんけど、ゲストログインを課題にするならもうちょい詳しく教えろ

転職を考えている初心者エンジニアの皆さんの成功と、より一層のご活躍をお祈り申し上げます。

以上です。

おまけ画像

f:id:ritou:20210201034652p:plain

f:id:ritou:20210201034706p:plain

ではまた!

噂のマイナンバー要求APIをCIBA+FIDOで作れないか考えてみた

f:id:ritou:20201212024655j:plain

こんばんは、ritouです。

Digital Identity技術勉強会 #iddance Advent Calendar 2020 15日めの記事です。

qiita.com

何の話か?

このプレスリリース、ニュースを覚えていますでしょうか。

www.jiji.com

マイナンバー要求APIの説明としては

事業者が同APIを導入すると、利用者にマイナンバー提出をリクエストすることが可能となります。それを受けた利用者は、PINコードを入力、もしくは生体認証(指紋または顔認証)した後、マイナンバーカードのQRコードスマホで読み取り、そしてカード本体をNFCで読み取るだけで瞬時にマイナンバーを提出することができます。

とあります。これはこれで "瞬時にマイナンバーを提出"とはどの部分にかかってるのかが気になる文章ではありますが、xIDのサイトにも説明があります。

my-number.x-id.me

今日はこのマイナンバー要求APIの仕組みを標準化仕様、既存の技術を組み合わせて作るとしたらどんな感じかと言うところを考えてみます。

マイナンバー要求APIのフローを理解する

上述のサービスに概要が書かれております。

f:id:ritou:20201210034727j:plain

(引用元 : https://my-number.x-id.me/)

xIDアプリでやることっていうのがこの図ですね。

f:id:ritou:20201210041807p:plain

(引用元 : https://www.jiji.com/jc/article?k=000000023.000037505&g=prt)

これを5つの処理に分解しましょう。

  1. サービスからのマイナンバーリクエスト : 属性情報要求
  2. ユーザーへの通知、確認コード選択 : ユーザーへの通知
  3. PIN or 生体認証 : ユーザー認証
  4. マイナンバー入力 or QR読み通り + マイナンバーカード読み取り : マイナンバー取り扱いのためのお約束
  5. 暗号化して送信 : 属性情報応答

で、それぞれの処理に標準化仕様をあてはめてみましょう。

  1. 社内のマイナンバー登録フローみたいなのに入り、サービスはこのユーザーのマイナンバーが欲しいとxIDに要求、ユーザーには確認コードを表示 : →CIBA
  2. xIDはアプリへの通知を用いて「サービスがマイナンバーを欲しがってるよ」と通知、ユーザーは表示された確認コードと一致するものを選択 : →CIBA
  3. 通知を受けたxIDアプリでPINもしくは生体認証を行うことで2FA完了 : →FIDO
  4. マイナンバーを入力もしくはQR読み取りを行い、さらにマイナンバーカードをNFCで読み取る : マイナンバー取り扱いのお約束
  5. xIDアプリで暗号化されたマイナンバーがサービスに返される : →CIBA(w/ JWE?)

1,2,3について、「手元のスマホでログイン」的な流れ、利便性を上げるためにFIDOとの組み合わせ、なんてところは本ブログで何度も扱ってきた通りです。 4の処理については「民間事業者におけるマイナンバーカードの活用」というPDFなどに記載されている券面事項入力補助APを使う際のアクセスコントロールに従っているようです。

https://www.cao.go.jp/bangouseido/pdf/topic_card_minkan.pdf

あとは5のところでモバイルアプリの段階で暗号化して送ってサーバーも見れませんよ!って言えるなら出来上がりでしょう。

CIBA/FIDO で作るマイナンバー要求API

ここまで分解できれば、あとはシーケンス書いてそれぞれの処理を整理しましょう。

登場人物

実際はxIDのアプリの他にxIDのバックエンドサーバーがいるだろうと言うところで、登場人物は4人と言うところでしょうか。

  • RP : マイナンバーを要求するサービス
  • IdP(Backend) : マイナンバー要求API(xIDのサーバー)
  • IdP(MobileApp) : xIDアプリ
  • EndUser : マイナンバーカードを保持するユーザー

シーケンス

シーケンスはこんな感じでいけそうです。

f:id:ritou:20201212015723j:plain

それぞれを見ていきましょう。

RPからIdPにマイナンバー要求、受付応答

f:id:ritou:20201212020534j:plain

CIBAにおける、バックチャネル認証リクエストに相当します。

  • RPは社員番号、メールアドレスなどを login_hint などとして指定
  • 確認コードは binding_message として指定

CIBAにはPOLL/PING/PUSHと言ったモードがありますが、ここではPOLLモードを想定し、auth_req_id が返されます。

確認番号の選択

f:id:ritou:20201212021643j:plain

これは以前、手元のスマホでログインみたいなのをCIBAで実現しよう!みたいな記事でも触れた気がしますが、EndUserがこのフローの正しい利用者であることを確認する仕組みとして binding_message をうまく使うことで実現できそうです。 この、3つ表示して1つ選ばせるみたいな部分は結構普通に使いそうなので、CIBAの仕様として組み込んでも良さそうな気はしています。

PIN or 生体認証

f:id:ritou:20201212022016j:plain

xIDアプリは所持 + αの2要素認証をうたっています。 それに相当する仕組みとして、ここでは FIDO を用いることで実現できそうです。

マイナンバーカードのお約束

f:id:ritou:20201212022229j:plain

ここは標準化仕様というよりもガイドライン的なものに従って作る感じですね。

暗号化したマイナンバーのやりとり

f:id:ritou:20201212022438j:plain

この辺りはモバイルアプリでJWE形式にしたIDTokenを生成、もしくはマイナンバーだけを暗号化するなどよしなにしてバックエンドサーバーが復号できない状態とし、RPまで返してやる、そして破棄することで実現できそうです。

まとめ

  • マイナンバー要求APIの仕組みを分解してみた
  • CIBA/FIDOとかを組み合わせたら作れそうだったのでシーケンスなどを考えてみた

いかがでしたでしょうか。 ある程度、認証認可周りのプロトコルの理解が進んだ状態であれば、何か新しい仕組みが出てきたときにこんな感じかなーと考えて見ることもできるでしょう。 まぁ実際に実装するとなると細かくいろんなこと決めないといけないんでしょうけど、頭の体操をしてみても面白いのではないでしょうか。

何か気になることがありましたら、匿名でも質問受け付けておりますので気軽にどうぞ!

marshmallow-qa.com

私のアドカレ次回作は12/25の予定です。

qiita.com

で、その間に会社のやつを20日にQiitaの方に書く予定です。

qiita.com

ではまた!

参考

qiita.com

ritou.hatenablog.com

ritou.hatenablog.com

ritou.hatenablog.com