2023年買ってよかったもの

今年も盛大に金の無駄遣いをしてしまった。 思い出せるものの中で買ってよかったエスプレッソマシンを紹介します。ラテアートまだ上手にできてないけど。

10 年くらい使ってたデロンギの 15K 円くらいのエスプレッソマシンが壊れそうになったので、新しいもの買った。 買ったのは Lelit Bianca V3 ってやつ。

www.espressocoffeeshop.com

Black Friday のセールで 15% 安くなったのでポチってしまった。けどまじめに計算してみたら、やはり円安 + 送料(イタリア→東京)+ 関税でめっちゃ高かった。 ただ毎日ラテかカプチーノかなにかを妻と一杯ずつ飲むし、自宅勤務の関係でスタバにもコンビニにもほとんどいかないので、自宅のエスプレッソマシンは日常必需品になっているので、買わざるを得なかった。

で、1ヵ月くらい使ってみたらやっぱ買ってよかった。 普通にリビングにおけるサイズだし、性能的に何一つ不満もない。あと見た目もかっこいい(重要)。値段も LA MARZOCCO LINEA MINI に比べるとだいぶやすい(LA MARZOCCO LINEA MINI ほしいけどね)。 スペック的には必要なものが全部そろえたと思う。

  • デュアルボイラー
  • PID
  • スチームが強力(ノズル 2 穴 / 4 穴が一つずつついてくる)

それに加えて、いくつかの素晴らしい機能がついている。

1. コンピューターによる抽出圧の自動制御

エスプレッソって、別に一気に 9 bar まで圧力をかけて、9 bar を維持するまま抽出できれば最高、ってわけではなく、コーヒーにかける圧力を変化させることで味も変わったりする。 このマシンは、事前に設定すれば、自動的に抽出する際の圧力を調整してくれる。

例えば

  • 0 秒 ~ 5 秒: pre-infusion (粉に少量な水をかける)
  • 5 秒 ~ 8 秒:何もしない(水を粉に浸透させる)
  • 8 秒 ~ 25 秒:通常の 9 bar の圧力をかけて抽出する
  • 25秒 ~ :圧力を 4 bar 程度まで落としてフィニッシュ

みたいな操作が自動的にやってくれる。

2. 抽出圧調整パドル

抽出圧を自動で調整させるではなく、自分で様子を見ながら調整することも可能。 このマシンの真ん中にパドルがあって、抽出時にエスプレッソハンドルにかける圧を調整できる。 一番右にすると、抽出時の気圧が max になる。この状態で抽出を開始して、徐々に左に回していくと、圧が徐々に下がる。

これによってクレマの出来具合が変わってくるし、味(というより香的なもの)も微妙に変わる。

3. 水タンクの場所が変えられる

水タンクは固定式ではなく、左・右・後の 3 箇所に取り付けられる。 これマジでありがたい。水タンクをマシンの後方に置くと、テーブルの奥行が足りないのでおけなくなるので、右側にしている。

注意点

海外通販で買う場合、220 V しかない可能性が高い。その際、昇圧器が必須となる。 しかもこいつ 1500W もするので、余裕を持たせて 2500W の昇圧器 を 15000 円くらいで買った。

日本で頑張って探せば 110V のマシンも購入できるみたいんだけど、昇圧器がいらない分、110V で使うと、予熱の時間が長くなる。 ちなみにこのマシン、スイッチ ON にしてから使える状態になるまで、15 分程度待たなければならない。なので、朝の忙しい時間はあまり使えないかも。 まぁ予熱時間が短くても、コーヒーを入れる手間がかなりかかるので、自分はランチを取る前に電源を入れて、ごはん終わった後にコーヒーを入れるようにしている。

グライダー

DF64 ってグライダーも購入した。 これ最近新型?の DF64P が出ているみたい。

世の中家庭用のグライダーでしたら Niche というブランドが人気なんだけど、10万円超えなので買わなかった。

DF64 は 64mm のフラットバールを使っていて、モーターも力強い(1400W 固定)ので、十分に使える。 バーは交換可能で、SSP のもの も存在する(味にどう影響するかはわからないが、かなり高価なので、おそらく買うことないだろう)。

その他

エスプレッソを入れるためのアクセサリーも色々購入した。

  • ピッチャー:Barista&Co 600ml
  • マグカップ:LOVERAMICS 300ml x2

あとはスケールとディストリビューターかな。どちらもアマゾンで適当に買ったものを使っている。 タンパーは Lelit 純正のものがついてくるので、買わなくて OK。

終わりに

マシンの性能が良くても、まともにラテアート作れないやつはまともにラテアート作れない(練習します)。

Ubie に入社しました。

nerocrux です。

Ubie という医療系スタートアップに基盤開発エンジニアとして入社し、もうすぐ 4 か月が経ちました。 今日は Ubie に入社することを決めた理由や、基盤開発エンジニアとして実際に Ubie で働いてみてどうだったかを振り返りたいと思います。

Ubie は現在 300 人弱の従業員が所属しており、あまりスタートアップとは言えないかもしれませんが、私のキャリアの中で経験した会社の中で Ubie は一番規模が小さく、働き方も完全にスタートアップになっていると思います。

本記事を通して、大手企業からスタートアップに転職して、自分のキャリアの新しい可能性を探ってみたい方の参考になれたら幸いです。

経歴紹介

私の経歴は大まかに以下となります。

一社目はグリー株式会社で、主にソーシャルゲームのバックエンドサーバーの開発を開発しました。主な技術スタックは、PHP / AWS (EC2, DynamoDB, MySQL) を使ってまして、C# (Unity) も多少使いました。

二社目は株式会社メルカリで、認証基盤の開発をしていました。メインで使う言語は Go に変わり、インフラは GCP (Kubernetes / Spanner) に変わりました。仕事内容としては、主に To B / To C 向けの OpenID Connect に基づく認証認可・ID 連携の関連システムの開発/導入/運用、または社内のマイクロサービス基盤のための認証認可とアクセス制御の仕組みの開発でした。

三社目は日本マイクロソフト株式会社で、主に Azure AD というエンタープライズアプリケーション向けの認証 IaaS のサポートエンジニアの仕事をしました。

Ubie に転職した理由

私が転職する際に会社を選ぶ際に、主に以下 3 つのポイントで判断しました。

  1. 自分のやりたいことができる環境であるか。
  2. 一緒に働く人のスキルや人間性
  3. 会社全体的な雰囲気、働きやすさ、成長性等。

Ubie は上記 3 つの会社選びポイントに一番合いましたので、Ubie へ入社することにしました。それぞれについてもう少し詳しく説明させていただきます。

1. 自分のやりたいことができる環境であるか。

私はここ 4、 5 年間ずっと認証認可に関連するシステムの開発等に関わってまいりました。具体的にやることとしては

  • B / C ユーザー向けの本人認証のシステムの開発運用
  • ユーザーの権限管理、認可システムの開発運用
  • 外部システムとの ID 連携の仕組みの開発運用
  • 認証ありきの API サーバーを保護するための仕組みの設計、実装等

この分野では 暗号技術や Web / Web セキュリティ等の基礎知識が必要としており、さらに OAuth 2.0、 OpenID Connect、FIDO2(Passkey)等、数多くの標準プロトコル群に対する理解が求められています。仕事としてこの分野は大変ではありますが、非常に魅力的だと感じています。

私はしばらくこの分野から離れるつもりはありませんが、世の中ではこの分野に特化した求人がまだ少ないと感じています。転職活動の時でも、セキュリティエンジニアの求人が多いものの、「認証認可」に関連するシステムの開発運用に特化したポジションはほとんどありませんでした。

Ubie の場合、医療分野のスタートアップ企業として、「AI 問診」というサービスを提供しており、このサービスを通して収集した一般ユーザーの問診データを管理しています。このようなデータは非常にセンシティブで、適切に管理する上、誰(ユーザーあるいはシステム)がどういった条件でデータにアクセスできるかをきちんと制御できるようにしなければなりません。

また、Ubie では「AI 問診」のような To C 向けのサービスのみならず、医師向け / 病院の受診者向けのシステムも提供しており、アクセス主体となる様々なユーザーに対する適切な認証認可を行える必要がありました。また、これらのシステムに対して、(一部法令によって)厳格な要件が課せられています。

Ubie では、セキュリティや認証認可に関連するシステムの構築が Ubie 社のビジネスにおいてとても重要であることを認識しており、そのため認証基盤を開発するチームを設け、「認証技術」に特化したエンジニアを積極的に採用していました。

私は入社前に Ubie の認証基盤を担当するエンジニアと何度も話したことがあり、この会社がやりたいことは自分のやりたいことと一致していることを確認できたことが、入社に決めた一つの重要な要素だと思います。

2. 一緒に働く人のスキルや人間性

会社の同僚とは、一週間のうち 5/7 の時間を共有しますので、この人たちと一緒に働きたいかは非常に大切な要素だと考えています。

Ubie に応募する前から、今後同じチームで働きそうな Ubie メンバーと何度も話す機会がありました。そこで出会った方々のスキルの高さを感じていて、一緒に働くことで自分がさらに成長できそうと感じました。

3. 会社全体的な雰囲気、働きやすさ、成長性等。

Ubie では、カルチャーガイドを公開しており、応募する前に会社の考え方や雰囲気を把握するのに参考になりました。

このガイドは Ubie が求める人物像や、Ubie が採用している組織制度などを非常に細かく言語化しており、理解するには大変でしたが、会社の雰囲気や考え方を掴めるに役に立ちました。その当時非常に印象的だったのは以下の 3 点でした。

  • 権限は極限までに各メンバーに委ねる(当事者意識を重視し、自由度を与える)
  • 効率至上主義(ROI 重視、検査適応重視で素早く方針転換、などなど)
  • 評価なし

正直これらの考え方は当時比較的大きい会社でしか働いた経験のない自分に合うかどうかは全くわからなかったが、更なる成長を求めるスタートアップ企業としてはきわめて妥当な考え方で、「このやり方でさらに成長できそう」「色々大変かもしれないが自分がやりたい仕事をどんどん推し進めできそう」と感じました。

また、Ubie が掲げた「テクノロジーで人々を適切な医療に案内する」というミッションも大変素敵だと感じました。

実際はたいてみたらどうだったか

正直まだ入社して半年も経っていないので、100% 正確にお伝え出来ない部分もあると思いますが、おおむね入社前の想定とは大きくずれていませんでした。

1. 自分のやりたいことができる環境であるか。

この点に関しては、現状ではほぼ満足しています。

自分の職務内容では今のところほぼ 100% 認証基盤にコミットしていて、既存のシステムの改善をしつつ、社内のマイクロサービス基盤向けの認証認可の仕組みを設計・実装・普及活動をしています。

ただ Ubie は事業が成長しているなかで、開発人員はまだまだ足りていなし、自分のスキルと工数に限界があるため、やはりもう一名認証認可やセキュリティに強い基盤系エンジニアを採用して、一緒に働けたらいいなと考えています。

2. 一緒に働く人のスキルや人間性

Ubie では、各分野での専門性の高い、シニア(年齢的ではなく)な人たちが集まっていまして、自分が詳しくない分野で相談が必要な場合、専門性の高い人とすぐに相談ができるのが非常にいい体験だと思います。

また、Ubie でのコミュニケーションがフラットで、どのチームと話すのもハードルが低いと思います。 仕事上全く関わりのない人にいきなり Google Meet の招待を送っても全く問題ないのは、個人的にはポイントが高いです。

3. 会社全体的な雰囲気、働きやすさ、成長性等。

ウィズコロナの時代ですが、Ubie では出社するかどうかを自由に選択可能です。出社するメンバーは少なくありませんが、自分は引き続きリモートワークになりますので、Ubie 社の雰囲気は完全に把握していないかもしれません。

印象としては

  • 自由だが、自律的に、のびのびと働いている人が多い
  • 事業が伸びていて、上場に向けて士気が高い & 忙しい
  • 仲がいい、仕事終わったあとの時間も楽しんでいる

また組織制度に関しては、Ubie では「ホラクラシー」と呼ばれる分散型組織統制手法を導入しており、今まで馴染みがなかったため最初は戸惑いましたが、3 か月くらい経った時点でなんとなく雰囲気を把握しまして、面白い制度だと思いました。 ただこの制度もすべてのメンバーに強い当事者意識が求められており、言われた通りのことをこなす人には向いてないかもしれません。

最後に

最後まで読んでいただきましてありがとうございました。

Ubie に入社してまだ 4 か月しか経っていませんが、働きやすい、これからでも可能性が非常に高い会社だと感じました。

ぜひ一緒にテクノロジーで医療 DX を推進して、よりよい医療体験を実現しましょう!

We are hiring!

recruit.ubie.life

draft-ietf-gnap-core-protocol 読書メモ

趣味で認証認可をやっている nerocrux です。Digital Identity技術勉強会 #iddance Advent Calendar 2020 22日目の記事です。

今年10月に GNAP という、一時的に OAuth 3 とも呼ばれていた認可プロトコルのワーキンググループドラフトが公開されましたので、時間をかけて読んでみました。

GNAP は 120P (PDF版)にも及ぶ膨大な仕様で、本一冊分書けそうです。せっかく時間かけてメモ取りながら読んだので、その記録も公開します。 結果的にただの翻訳になってしまいあまり参考にならないかもしれないが、とりあえず興味があればどうぞ...

https://github.com/nerocrux/draft-ietf-gnap-core-protocol-jp

また、GNAP についての解説記事Merpay Advent Calenar にて公開していますので、よかったら合わせてお読みください。

あまり記事にならない気がするが、時間があったらGNAPのハンドルやキーバインディングあたりに仕組みを詳しく招待できたらと思います。それでは、メリークリスマス。

Sign in with Apple を本番でやらかしをしないために考えてみた

認証認可を趣味でやっている@nerocruxです。この記事は 認証認可技術 Advent Calendar 2019 第11日目の記事です。

本番でやらかした人のAdvent Calendarではありませんのでご注意ください。 ...しかしこんなのやったら本番でもやらかしちゃうぞとの指摘があるかもしれませんので予めお詫びをしときます (´;ω;`)ウッ

そろそろ Sign In With Apple (以降、SIWA) を実装する時期がやってきました。本番で SIWA を実装したサービスも増えてきました。最近 pocket 以外に、vivinoも実装しており、さらにadobe creative cloudも実装しています(なぜかweb版のみだが)。

またAppleのCEOであるTime Cookさんもわざわざ日本に来てSIWAを実装したTimeTree社をtwitterで褒めたりしていて、SIWAを頑張って実装すればひょっとしたらTim氏が褒めに来るかもしれません。

仕事では自分が実装しているわけではないですが、趣味本位&相談されるときに困らない程度で仕様を調べたり、実装方法を考えたりしている程度で、せっかくなので今まで調べたことをまとめたいと思いましてこの記事を作成しました。

主に以下の観点から整理してみたいと思います。

  • iOS, Android, Web, いろんなUAやOSバージョンがあるなかで、そもそも全部実装しなければならないか
  • 実装する場合、各UAに対してどうやって実装すれば合理的なのか

あまり新しいネタないからつまらないかもしれませんが、しばらくお付き合いください。

UAに対して、SIWA対応の必要性について個人的な考え

iOS 13+

ガイドラインに従い、実装が必要です。ネイティブでサポートしているので、AuthenticationServicesに実装されている SIWA 関連のクラスを利用して実装するのが一般的だと考えます。

iOS 13 以外

AuthenticationServices 自体はiOS12からなんですが、SIWAに使われるクラスはiOS13からなので、SDKを利用した実装ができなくなります。

そのため、iOS13と同じように実装するのが不可能になるため、実装コストがかかります。

実装するかどうかの判断はビジネスに依存するんでどうすれば正しいかはケースバイケースで判断すると思いますが、一応自分の考え方をまとめます。

iOS 9Android 6 未満

Sign in with Apple の仕様上、redirect_uri というパラメータをIDP(Apple)に登録する際、Custom URL Scheme (myapp://login/callback のようなスキーマ)を利用することが不可能となっており、必ず http(s):// から始まる必要がある。

f:id:nerocrux:20191211155113p:plain

そのため、iOS の UniversalLinks, Android の AppLinks の仕組みを利用することが必要となっている。

iOS 9, Android 6 未満のOSでは、このような仕組みをサポートしておらず、普通にやるなら Sign in with Apple の実装はできないはず。

iOS 9 ~ 12

iOS 13以下のバージョンに対して、必ずSIWAを実装しなければならないかをAppleのサポートに問い合わせたところ、「審査する前に特定のアイディアに対して予め承認することができません。アプリを提出したあとに適切なアドバイスをするよ」的な回答が返ってきました。なのでわからないです。

iOSそんなに詳しくないですが、色々聞いたり調べたりしたところ、ネイティブでこの機能サポートしてないので、古いバージョンのiOSを実装してなくても大丈夫なはず、との意見が多いです。

そうすると、個人的には以下の理由でiOS12以下にSIWAの実装を一旦しなくてもいいかなと思っています。

  1. 別のOSでSIWA経由でアカウント連携し、iOS12以下の端末でログインするニーズが少ないはず
    • iOS13から入ったひとはiOS12にダウングレードする可能性が低い
    • Androidの人でSIWA経由でユーザ登録、アカウント連携する人が少ない
    • Web... どうだろう...
  2. 新規ユーザを獲得する際にSIWAというパスがなくてもそれほど影響がない
    • iOS13に比べると UX 特段よいでもないので、Apple ID 使うより Google ID 使う人が多い気がする
  3. iOS13未満の端末はどんどん減っていく

ただAndroidを対応する予定があれば、複雑な認可部分は同じフローで実装できそうなので、一緒に対応しちゃっていいかなと個人的に思います。

また、ユーザ数の多い大規模なサービスや社会的の影響の大きいサービスであれば、割合が少ないけど母数が大きいので、やはり捨てられないところがあると思います。

Android 6+

もともと Android のユーザだったら Sign in with Apple でユーザ登録することは多分ないと思うが、iOSから引っ越ししてくる人は数少なくないはず(Android で WebAuthn やりたい人とかね)です。また、iOS, Android両方利用する人も少なくありません。

なので、もしiOS, Android両方提供していれば、Androidは対応はやったほうがいいと思います。まぁそこもサービスの規模や性質、ユーザ数的の割合で判断すると思いますが。

Web

アプリと同等、あるいはそれ以上の機能をWebで提供していれば、SIWAも実装する必要があると思います。

特にパスワードのリセット、アカウントの停止など、アカウント/セキュリティまわりの機能をWebのみに実装しているサービスが多いと思いますので、そういったユーザに提供しなければならない機能がWebにあれば、ログインの手段を提供しなければなりません。

UAに対して安全な実装方法について

訂正: @nov さんの指摘より、iOS 13/macOS catalina上で(最新の)safari経由でnonce付きの認可リクエストを送っても、帰ってくるID Token(frontchannel, backchannel 両方とも)にnonceが入らないことがわかりました。
また、iOS13の場合、safariだけでなく、chrome/firefox経由でも、AppleIDPに対する認可リクエストがネイティブにフックされ認証がネイティブのポップアップで行う場合、取得したID Tokenにnonceが入りません。macOSのchrome/firefoxの場合ID Tokenにnonceが入ります、古いバージョンのsafariは検証できてませんでした。
認証がiOS/macOSネイティブの認証機構にフックされた場合、ブラウザからIDPにリクエストが直接に届かれることなく、OSが認可リクエストを送り直している、その際nonceをちゃんとセットしてくれないって感じかな..?
iOS12の場合、SIWAの実装がまだないので、ネイティブの認証画面が出ず、nonceもID Tokenに入ってました(frontchannel, backchannel 両方とも)。

なので、iOS13 + AuthenticationServices利用しない、または最新版のPC Safariに対する実装は、nonceの検証ができません。

Sign in with Appleとは、ID連携したいサービスが何かしらの方法で、Apple社が提供している(ほぼ)OpenID Connectに準する認可サーバと会話して、ユーザの認証と認可を行った上で、ユーザのID Tokenを入手する。いわゆる3-legged authorizationってやつです。

このID Tokenに、Appleで認証済みのユーザの(同じTeamID内で)一意なユーザIDが含まれており、Appleでのログインセッションをサービスのログインセッションに"引き継ぐ"ことで、サービスにログインする、というプロセスです。

iOS13では、Sign in with Appleに対して、ネイティブレベルでサポートしており、iOS開発者はネイティブのクラスを使って開発できる。

iOS13以外では、AppleがJSのSDKしか提供していない(しかも機能が単純、当たり前だが)ので、開発者はある程度OIDCのプロトコルを理解した上で、特にセキュリティ上の特性を把握した上で、実装しなければならないです。

なんだかわからんけどID Token取れた、そのsubからユーザIDも取れたから、subを後ろのサーバに送って終わり!と考えて実装したら、来年の本番の認証認可システムをやらかした人のアドカレにデビューする可能性があるので、お気をつけください。

SIWAは Sign in with Google, Sign in with Lineなどと同じ、ソーシャルログインの一つなので、ネットに存在する"(OIDCを利用した)ソーシャルログインの安全な実装"に関連する記事がたくさんあるので、参考してみるといいです。

それでは、UAごとに自分が考えた実装例を紹介したいと思います。

※ シーケンス図にあるセッションについてに記述はあくまでも例です。

iOS13

f:id:nerocrux:20191211155938j:plain

iOSネイティブのAuthenticationServicesを利用して実装するのがベストでしょう。

認証はTouchID/FaceIDがサポートされ、認可はネイティブなUIによって行われ、ユーザからするとUX的にはよい。

開発者としてはAppleのドキュメントに従い実装すれば良いはず。特にOIDC的な要素がなくて、ASAppleIDCredentailが取得でき、以下の情報が取れるようになる(ASPasswordCredentialは割愛)

  • identityToken(ID Token)
  • authorizationCode
  • email
  • fullName
  • etc...

identityToken は JWT になっていて、パースすると以下のようなパラメータが取れる。

ASAuthorizationAppleIDRequestを作るときにnonceをセットすることができ、セットしていればレスポンスで受信するidentityTokenの中に含まれるので、検証可能となっています。

✻ Header
{
  "kid": "AIDOPK1",
  "alg": "RS256"
}

✻ Payload
{
  "iss": "https://appleid.apple.com",
  "aud": "com.example.apple-samplecode.juice",
  "exp": 1575545337,
  "iat": 1575544737,
  "sub": "000738.b65425db0ef845379c5fbc5148c3516d.1047",
  "nonce": "jf329fui290-3uvj43290vjh3409hv",
  "c_hash": "eLyPhH3tcU22gPSF6zOxhQ",
  "email": "xxxxxxxxxxxx@privaterelay.appleid.com",
  "email_verified": "true",
  "is_private_email": "true",
  "auth_time": 1575544737
}

また、authorizationCodeを利用することで、上記identityTokenとほぼ同じようなID Tokenをバックチャンネルで取得できます。

後述、AuthenticationServicesを利用せずに実装するパターンを見るとわかりますが、SDKを使って実装する場合のレスポンスは大きな差異がなく、サーバサイトとしては同じように処理すればよいです。

注意してほしいのは、Appleが提供しているサンプルコードでは、nonceをASAuthorizationAppleIDRequestに渡しておらず、ガイドを読んだ感じでは"セッション引き継ぎ"の方法についても言及してなかったので、Apple UserID やメアドを使ってセッションを引き継ぎするのが危険なので、上記ID Tokenや認可コードをサーバに送ってセッションを引き継ぐほうが安全だと思います。 その際、nonceの生成・セット・検証を行うべきです。

iOS 9~12, Android 6+

f:id:nerocrux:20191211155914j:plain

AuthenticationServiceを利用せずに実装する場合、利用する場合と同じように、Front channel に id_token と auth_code、そしてBack ChannelにID Tokenを発行することができます。

ネイティブ用のSDKが提供されてないので、認可リクエストを組み立てて、Sign in with Appleのボタンをネイティブ画面上にセットしたり、Webviewを利用している場合JS SDKを利用することになると思います。

SIWAボタンがタップされた場合、ブラウザに遷移され、認証認可を行った上で、RedirectURIによってアプリに遷移し戻し、id_token や auth_code を入手する。ネイティブはこれらの情報をサーバに送り、サーバがログイン処理を行う。

こちらサーバ側の実装はiOS13の場合と同じと考えています。

リクエスト & レスポンス例

/authorize

リクエス

https://appleid.apple.com/auth/authorize?
client_id=com.nerocrux.siwa.service&
scope=name%20email&
response_type=code&                                                  // code id_token もOK
redirect_uri=https%3A%2F%2Fsiwa.nerocrux.com%2Fcallback&
prompt=login&
response_mode=form_post&
state=RpigrS5w9E97jtLUoUPgQ4RgZ6TuezU8          // なくてもAppleに怒られない
nonce=k320fk3029jg50ijg6i5j409jf0o3jfk3409jh50  // なくてもAppleに怒られない

レスポンス

Host: https://siwa.nerocrux.com/callback

state: RpigrS5w9E97jtLUoUPgQ4RgZ6TuezU8
code: c359c8401ed5540a6a47cd532ed38781d.0.nxty.UdOTBkNQu173S_vuxGmQ3Q
id_token: eeyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLm5lcm9jcnV4LnNpd2Euc2VydmljZSIsImV4cCI6MTU3NTM5MjU4NiwiaWF0IjoxNTc1MzkxOTg2LCJzdWIiOiIwMDA3MzguYjczYjI4MDBkZTBmNDhhMzkxNjVjZTI3YTUxYWM3YjAuMTYyMSIsIm5vbmNlIjoiazMyMGZrMzAyOWpnNTBpamc2aTVqNDA5amYwbzNqZmszNDA5amg1MCIsImNfaGFzaCI6Imt6eEFJVTVZcmt0ZFpYaHhpcFVvZVEiLCJhdXRoX3RpbWUiOjE1NzUzOTE5ODZ9.hbz0QX5PkwT1Gmq8I22M7mnOkU5cPY8ot9FCFP4wLBOmBYFcCeiVmojNNcrEfCZH4dxMptf1OTnNTsb56IPCQFByTHkiSULm9VJbMTt_oIvCSC8yfUPl7zKAPZtjJv3OxxJFS9kZ7bdjPYO-1iByKaqgJSG1OipmmjoC67zUgi4uMjCsWhLykqG2OWnWbTZ6PaVvM8QX-Ugl7WnhMNnTtJeeRAle8fgApiyYUXcHlaCDfX2i56HsuwXgPu1ARXlcJtjw2nYIDevzR2ekuLyAV9OKmuQymH4wY3xhMegp5low0ej-7CKRkN4x41AnRi2YDgyhaAEFiJoLopd_gxU8kg

ID Token

✻ Header
{
  "kid": "AIDOPK1",
  "alg": "RS256"
}

✻ Payload
{
  "iss": "https://appleid.apple.com",
  "aud": "com.nerocrux.siwa.service",
  "exp": 1575392586,
  "iat": 1575391986,
  "sub": "000738.b73b2800de0f48a39165ce27a51ac7b0.1621",
  "nonce": "k320fk3029jg50ijg6i5j409jf0o3jfk3409jh50",
  "c_hash": "kzxAIU5YrktdZXhxipUoeQ",
  "auth_time": 1575391986
}

/token

リクエス

POST /auth/token HTTP/1.1
Host: appleid.apple.com
Content-Type: application/x-www-form-urlencoded

client_id=com.nerocrux.siwa.service&
client_secret=<CLIENT SECRET JWT>&
code=c359c8401ed5540a6a47cd532ed38781d.0.nxty.UdOTBkNQu173S_vuxGmQ3Q&
grant_type=authorization_code&
redirect_uri=https%3A%2F%2Fsiwa.nerocrux.com%2Fcallback

レスポンス

{
    "access_token": "a0e1055f108144071a8bb1bf915643c12.0.nxty.YAwyi9gPzMXB625gaqNYBg",
    "token_type": "Bearer",
    "expires_in": 3600,
    "refresh_token": "<REFRESH_TOKEN>",
    "id_token": "eyJraWQiOiJBSURPUEsxIiwiYWxnIjoiUlMyNTYifQ.eyJpc3MiOiJodHRwczovL2FwcGxlaWQuYXBwbGUuY29tIiwiYXVkIjoiY29tLm5lcm9jcnV4LnNpd2Euc2VydmljZSIsImV4cCI6MTU3NTM5MjYxMSwiaWF0IjoxNTc1MzkyMDExLCJzdWIiOiIwMDA3MzguYjczYjI4MDBkZTBmNDhhMzkxNjVjZTI3YTUxYWM3YjAuMTYyMSIsIm5vbmNlIjoiazMyMGZrMzAyOWpnNTBpamc2aTVqNDA5amYwbzNqZmszNDA5amg1MCIsImF0X2hhc2giOiJmMmlkSGlSQ3RhclFqVkZSR3RyVVFRIiwiYXV0aF90aW1lIjoxNTc1MzkxOTg2fQ.kVNeCAxZVhPrXHwb4FzFzcs4OTVJ2LQOZUjFr2Ein0dnijfvM5qUV_srpnXPn2uJPIx_VkJ_Fyk144nyBpFbqAhDNt5qroqIQrHzgJYOt6QvOGCivPIa8Bj1qzXxUp3T56XCQgHTRaxL8Ym8hKXDbAX3JUDtwj9BKlZM__nNyliigGptDEsTa_97popZtYaNi8MOGi89pl3gmfC3JnzGuyUKcw-r1Z2lwdzwAkpL9RzsHsMzCsduGtM9cnu4WvsDG8UFHsXkaEzY8_pitvvVBVNdsinYanHdaNW1wrSFftJ_pfGSd603-Al2SFOAiedkgIyJaaSpjZnttYlfMqdkVg"
}

ID Token

✻ Header
{
  "kid": "AIDOPK1",
  "alg": "RS256"
}

✻ Payload
{
  "iss": "https://appleid.apple.com",
  "aud": "com.nerocrux.siwa.service",
  "exp": 1575392611,
  "iat": 1575392011,
  "sub": "000738.b73b2800de0f48a39165ce27a51ac7b0.1621",
  "nonce": "k320fk3029jg50ijg6i5j409jf0o3jfk3409jh50",
  "at_hash": "f2idHiRCtarQjVFRGtrUQQ",
  "auth_time": 1575391986
}

Web

f:id:nerocrux:20191211155954j:plain

Webの実装は、AuthenticationServiceを利用せずに実装するパターンとは同じです。異なる点としては、アプリを介さないため、サーバからnonceをアプリに送ったり、アプリからid_token, auth_codeをサーバに送ったりする必要がありません。

アップルはWeb向けに、JS SDKを提供しています。以下のようにかんたんに利用できます。

<html>
    <head>
    </head>
    <body>
        <script type="text/javascript" src="https://appleid.cdn-apple.com/appleauth/static/jsapi/appleid/1/en_US/appleid.auth.js"></script>
        <div id="appleid-signin" data-color="black" data-border="true" data-type="sign in"></div>
        <script type="text/javascript">
            AppleID.auth.init({
                clientId : '[CLIENT_ID]',
                scope : '[SCOPES]',
                redirectURI: '[REDIRECT_URI]',
                state : '[STATE]',        
                nonce : '[NONCE]'      // nonce も設定できるぞ
            });
        </script>
    </body>
</html>

当然ながら、このライブラリの役割はOAuth2/OIDCが知らない人でもかんたんに認可URLを組み立てるように、必要なパラメータを入れるだけで、認可URLを組み立ててくれます。それ以上のことはできません。 JS SDKによって生成されたボタンをクリックすると、以下のURLに飛ばされます。

https://appleid.apple.com/auth/authorize?
client_id=<CLIENT ID>&
redirect_uri=<REDIRECT URI>&
response_type=code%20id_token&
state=<STATE>&
scope=<SCOPE>&
nonce=<NONCE>&
response_mode=form_post&
frame_id=fabb5677-bef5-4227-a828-527bb3cc9d64&
m=12&
v=1.4.1

なのでJS SDKを利用する場合、認可レスポンスに auth_code, id_token 両方返ってきます。 JS SDK使わなずに自前で認可URLを組み立てることも当然可能です。その場合、auth_codeのみ、id_tokenのみ取得するような設定もできます。 ただ、m, vなどApple独自っぽいパラメータはどうすればいいか、つけなくていいかはわからない。

サーバでの実装

実装内容

  1. セッションと nonce の生成部分

  2. アプリを起動時にサーバと通信し、新しいセッションとランダムなnonce値を生成して、紐付きしてDBに保存する。

  3. Session IDとnonceをアプリに返す

  4. Auth Code / ID Token を受け取るためのエンドポイント

(1) アプリがこのAPIを利用して、Auth Code あるいは ID Token をバックエンドサーバに送信する。その後、サーバは以下の処理を行う。

backchannel ID Token を利用する場合 * frontchannel ID Token の有効性を検証した上で、c_hashを使ってAuth Codeの有効性を検証する(置き換えられているか) * Auth Codeを利用してトークンエンドポイントに叩いてAccessToken, RefreshToken, ID Tokenを取得する。 * 取得した ID Token を検証する * このID Tokenの中にat_hashが含まれているけど、access_token実際使いみちが今の所ないし、backchannelで発行されたので置き換えられる可能性が低いため、検証する必要があるかは悩ましいが、一応検証しとく

frontchannel ID Token を利用する場合

受信した ID Token の有効性を検証するだけ。

ID Token の検証について

検証はOIDCに定められている基準で行うとよいでしょう。

response_type = code id_token の場合、HybridFlowのFrontchannel ID Token検証方法Backchannel ID Token検証方法をそれぞれご参照ください。

かんたんにリストアップすると、以下の項目が検証対象になります。

  • signature : JWT自体は偽造されているか
  • iss : 発行者は Apple
  • exp : 有効期限切れているか
  • nonce : DBにある、当セッションと紐付いているnonceの値と一致するかどうか(ID Token置き換え攻撃対策)。また、無効化されているかどうか(リプレイ攻撃対策)。
  • c_hash: auth_code と id_token は同じセッションで発行されたか(auth_codeの置き換え対策)
  • at_hash: access_token と id_token は同じセッションで発行されたか(access_tokenの置き換え対策)

(2) ID Token をパースして、sub (AppleUserID) を当セッションと紐付ける。

  • もしユーザがすでに登録していれば、該当するユーザをログインさせ、アプリに知らせる。
  • もしユーザはまだ未登録であれば、ネイティブアプリに知らせ、ユーザ登録画面を表示し、新規登録を実行する。登録後、ユーザをログイン完了にする。

(3) nonce を無効化にする

Front Channel ID Token vs Back Channel ID Token

ご覧のように、ASAuthorizationAppleIDCredentialの中にID TokenとAuth Code両方入っています。このID Tokenをサーバに渡して使ってもらうか、それともAuth Codeをサーバ側渡して、サーバがAuth Codeをtokenエンドポイントに送って新たに取得したID Tokenを利用するかの二択があります。

ではどっちを利用するべきなのか。

自分は特に理由がなければ、バックエンドで取得したID Tokenを利用したいと考えています。理由は以下だと考えています。

  • OAuth2のSecurity BCPはImplicit Grantを非推奨にしています。SIWAでresponse_type=code id_tokenのリクエストはHybridFlowなんだけどFrontchannelのID TokenはImplicitFlowで出るものに等しいので、トークン置換攻撃などに弱いので、利用するにはリスクが高くなりそう。
  • BackchannelでAuth Code使ってID Tokenを取得する際、Auth Codeの置換攻撃を比較的に容易に防げる。また、クライアント認証が必須であるため、取得したID Tokenの正当性を担保する根拠が強い。

AuthenticationServicesを利用する場合、ID Token と Auth Code 両方帰ってくるので、ID Token を使うとしても、 c_hash を使って Auth Code を検証するためのみに利用する、くらいの用途しかない気がします。

また、AuthenticationServices利用しない場合、response_type = code にし、HybridFlowではなくAuthCodeFlowを利用してもいいかなと思っております。バックエンドでインターフェースを揃えたいなら、AuthenticationServicesと同じように、frontchannelのID TokenはあくまでもAuth Code検証用に使うようにしたい。

やむを得ない理由でfrontchannelのID Tokenを利用したい場合、ID Tokenが置き換えられていないことを担保するために、必ず認可リクエストにnonceをセットし、ID Tokenを受信したあとにnonceを検証する必要があると思います。

セキュアに実装するためのポイント

SIWAを利用してSSOを実装するとき、以下のようなリスクを注意すべきだと考えています。

  1. ネイティブの場合、サーバに送られた情報は偽造されたり、置換されたりするリスク
  2. セッションが攻撃者に奪われ、被害者に装わってログインする
  3. リプレイ攻撃

これらのリスクを軽減するために、サービスがSIWA実装時に、以下の措置を取るべきだと考えています。

  1. User Identity情報偽造/置換に対する防衛
    • Apple User ID をサーバに送信するではなく、検証可能なID Tokenをサーバに送信する
    • ID Token, Auth Code をOIDCの仕様に沿って検証する
  2. セッションの漏洩対策
  3. リプレイ攻撃を防止するために
    • nonceを検証する(存在、有効性)
    • ログイン成功後にnonceを破棄する

終わりに

Sign in with Appleについて大したネタがなくて、愚痴しか言ってなかった気がしますが参考になれると嬉しいです。

アップルのガイドラインに従い、SIWAの実装がある場合、新しく作成するアプリではすでにSIWAを組み込む必要があって、既存のアプリでは2020年4月まで対応する必要がある言われています。実際ちゃんと実装しなければリジェクトされるかどうかまだわからないですが、天下のアップル様と仲良くしたいなら早めに実装しといたほうが無難でしょう。

明日はcorocnさんによるOAuth3のお話です。楽しみですね!

では良いお年を。

おまけ

またけさんがSIWAが出たまもなく頃のつぶやきですが

この問題、最近思い出して試してみたらまだ存在していました。

具体的にやったのは、Chrome Canaryで same site by default cookies を enabled し、Apple JSを使ったログインボタンをクリックするだけです。認証してアプリに対して許可すると認可レスポンスが400になります。

Apple JSを利用せず、自分でURLを組み立て、response_mode = form_pose ではなく、fragment, query にすると、一応認可レスポンスは成功しますが、Appleのポリシーにより、これらのresponse_modeではscopeの設定ができません。なので、ID Tokenは取れるが、ユーザ名(FullName)とEMAILは取れないです。

response_mode must be form_post when name or email scope is requested.

先日、Chrome は 80 から(2020年2月stableリリース) SameSite = LAX がデフォルトになると発表されました。Apple様はこの問題を直してくれないとちょっとやばいかもしれませんので、ちょいちょい様子をみてみたいです。