最初に結論
Safari で Basic認証 が成功した後、Authorization Header を Basic の認証結果で上書きしてしまう。JWT のアクセストークンを上書きするので、APIアクセスが失敗してしまう。解決策として、アクセストークンは Authorization Header で渡さず、別の方法で渡せば良い ※。
Safari 12 replaces authorization header - Stack Overflow
※ OpenID Connect ではクエリパラメータやフォームで渡す方法も定義されている。これらを使えば回避できると考えているが、試せてはいない。
ある日のこと
アルバイトの方から「検証環境のアクセスが401になります...」と報告があった。彼には開発中 Webアプリケーションの iPhoneデバッグをお願いしていたのだが、どうやら iPhoneでアプリケーションに繋ぐとエラーが発生するらしい。
アプリケーションの構成
アプリケーションは認証認可に OpenID Connect を使っている。クライアントは、IdP で認証が成功したら JWTのアクセストークン を受け取る。そのアクセストークンを HTTPリクエスト の Authorization Header に設定し、APIサーバと疎通する仕組みだ。
ざっと図にするとこんな感じ。ちょっと端折っている。
- アプリケーションは SPA で CloudFront + S3 で配信される
- CloudFront は
/api/* は Fargate
と/* は S3
で参照先を変更している - 認証はAuth0を使用。クライアントが認証成功するとアクセストークンを取得する
- Fargate からデータ取得するとき、クライアントはアクセストークンをリクエストヘッダに設定して /api/* へ APIリクエストを行う
- Fargate はアクセストークンを検証して、正しいものであればデータを返す。逆に不正なアクセストークンの場合は401エラーを返す
よくあるアーキテクチャ構成かと思う。今回問題となったのは、検証環境のためBasic認証を導入していたという点だ。本番環境にはBasic認証はない。
ちなみに上記の図では、CloudFront のところに Lambda@Edge を使用してBasic認証を導入していた。
影響範囲を確認
発生当時はすぐ本番環境を確かめたが再現しなかった。よってすぐ検証環境のみで発生していると辺りがついた。モバイルのみで発生? Android Chrome でアプリを開いたが再現しなかった。なので Safari が怪しいと当たりをつけて調査を行った。
その結果、どうやら MacのSafari と iPhoneのSafari の両方で発生していた。
リクエストを覗いて見る
バックエンドのレスポンスが401になっていたため、Chrome と Safari のリクエストの差異を調べて見た。(実際にはアルバイトがリクエストを見て報告してくれた🙏)。下記は /api/* へのAPIリクエストの中を覗いたもの。
Safari | Chrome |
---|---|
Authorization: Basic abc... | Authorization: Bearer xyz... |
...Basic?
Safari の Authorization Header の値が Basic認証の結果で上書きされている...。まさか、と思いすぐさまググってみると、同事象で困っている人が。この記事だと、旧Edge や IE でも発生するらしい。
- Safari 12 replaces authorization header - Stack Overflow
- How to prevent/override authorization header in Safari? - Stack Overflow
日本語のハマった記事も見つけました。
結局 どうしたか
🤷♂️
まだ何もしてない...
できればアプリケーションに手を加えず解決したいところ。だって本番影響のない事象で修正をしたくない。検証環境はアクセスを絞り込みしたいため、グローバルIP制限に変更する...とか?
検証環境へのアクセスを絞る別方法、なにか良いアイデアがあれば教えてほしいです。
回避策
そもそも CloudFront を使っているのだから、Basic認証ではなく「署名付きCookie」でアクセス制御しようよって話な気がしている。