11月は一体どこに行ってしまったんだと呆然としながら記事を書いている今日この頃、みなさま如何お過ごしでしょうか。いや本当にもう師走とは…。
嘆いていても仕方ないので早速始めます。今回のお題は Cookie です。って今更かい!と自分でも思ってしまうのですが、自分やっぱり分かってないことが今回も明らかになりました。…ええはい。
ことの発端は以前に記事にした個人開発プロジェクトでのことでした。SPA なのでページ遷移でのセッション管理は必要ないとはいえ、APIサーバーにアクセスしてデータをなんやかんやする以上、API サーバーでの認証管理は必要です。今時はどんな方法が主流なのかを早速調べてみます。
sessionStorageが良いかも!と思いつつ sessionStorage でもう少し見ていくと、 javascript でアクセス出来るのを知り、うーんとなってしまいました。今時の Web 開発で使用する javascript ライブラリの多さに怯んでいる小心者の私としては、万一その中に問題のあるコードが入っていても絶対にわからないだろうなあと半ば諦めているので必要以上にビビります。
やっぱり今まで通りのCookie・SessionIDが(・∀・)イイ!! となるのに5分もかかりませんでした。…ええはい。
認証部分は認証機能の外部サービスを使いたいと思っていたのですが、一刻も早く実際のサービス機能を作りたかったので、以前に Golang で開発していたログイン機能をそのまま使うことにします。特に問題なくフロントエンドで axios から json 形式でユーザー情報を POST して、API サーバー側でログインされたログを確認し、ヨシヨシ問題ないね!と微笑んでいたトコロ、なんの気なしに Chrome の開発者ツール上で保存されているであろう Cookie を見てみてびっくり!
無い、無いよ、私の Cookie !
そこから数日間も悩み続けることに…。
今回、きちんとAppServer と APIServer を分けての開発を始めていたので、てっきりCORS問題だと頭から決めてかかっていたのが敗着でした。
Proxy から AppServer と APIServer を https 接続へ
ヘッダーを以下のように各種設定
w.Header().Set("Content-Type", "text/html")
w.Header().Set("Access-Control-Allow-Origin", "https://originurl:originport")
w.Header().Set("Access-Control-Allow-Credentials", "true")
w.Header().Set("Access-Control-Allow-Methods", "GET,POST,PUT,PATCH,DELETE,HEAD,OPTIONS")
w.Header().Set("Access-Control-Allow-Headers", "Content-Type, X-Requested-With, Origin, X-Csrftoken, Accept, Cookie")
Cookie を作成する際にも
SameSite: http.SameSiteNoneMode,
と、もうやれることは一通り全部やったにも関わらず、 Chrome の開発者ツール上での Header にはSet-Cookie 情報が出ているのに、同じ Chrome の開発者ツール上の Aplication の Cookie には保存されているCookie は無いと表示されています。
何故だ…何故だ…何故なんだーと数日あーでもないこーでもないとひたすら調べる・修正を繰り返していましたが、とうとう万策尽きました。…Cookie とはなんぞやと呟きが漏れるに至りまして、もう最初から Cookie を調べ直してみることにしました。
Cookie とはなんぞや?
HTTP cookie(エイチティーティーピークッキー、単にクッキー (cookie)とも表記される)は、マジッククッキーの一種であり、RFC 6265などで定義されたHTTPにおけるウェブサーバとウェブブラウザ間で状態を管理する通信プロトコル、またそこで用いられるウェブブラウザに保存された情報のことを指す。ユーザ識別やセッション管理を実現する目的などに利用される。
https://ja.wikipedia.org/wiki/HTTP_cookie
と最初はやっぱり wikipedia から。一通り見てみてましたが、うーんそうですよね… という感じです。もう藁にも縋る気持ちで RFC 6265 – HTTP State Management Mechanism 日本語訳 を見てみます。
とりあえず設定を行う属性を見てみましょう
Expires属性
Cookieの最大有効期間。Cookieが期限切れになる日時を設定。
設定しなかった場合は、Session Cookie (ユーザーがブラウザを閉じるまで)となります。
今回は Session Cookie にしたかったので、この属性はこのままだよね。
Max-Age属性
ってあれ、こちらも Cookieの最大有効期間だよね。違い秒数までだったような?
CookieにMax-Age属性とExpires属性の両方がある場合、Max-Age属性が優先され、Cookieの有効期限を制御します。 CookieにMax-Age属性もExpires属性もない場合、ユーザーエージェントは「現在のセッションが終了する」まで(ユーザーエージェントによって定義されたとおり)Cookieを保持します。
https://tex2e.github.io/rfc-translater/html/rfc6265.html
なるほどなるほど。
ドメイン属性
このあたりが最も匂うんだよなー。基本的には Cookieの送信先となるホストを指定するんだけど、以前にセキュリティ的にはむしろ指定しない方が良いような記事を読んだ気がするんだけど…。
たとえば、ドメイン属性の値が「example.com」の場合、ユーザーエージェントは、example.com、www.example.com、www.corp.exampleにHTTPリクエストを送信するときに、CookieヘッダーにCookieを含めます。 com。 (先頭の%x2E( “。”)が存在する場合、その文字は許可されていませんが無視されますが、末尾の%x2E( “。”)が存在する場合、ユーザーエージェントは属性を無視します。 )サーバーがドメイン属性を省略した場合、ユーザーエージェントはCookieを元のサーバーにのみ返します。
ドメイン属性を example.com にした場合、example.com に送るけど、www.example.com と www.corp.example.com にも送るよってことですね。そんでドメイン属性を省略した場合は origin server のみに返すと。それなら確かに指定しない方が良いね。でもこれじゃないなあ。
パス属性
各Cookieのスコープは、Path属性で制御される一連のパスに制限されています。サーバーがPath属性を省略した場合、ユーザーエージェントはrequest-uriのパスコンポーネントの「ディレクトリ」をデフォルト値として使用します。 (詳細については、セクション5.1.4を参照してください。)
ユーザーエージェントは、request-uriのパス部分がCookieのPath属性に一致する(またはそのサブディレクトリである)場合にのみ、HTTPリクエストにCookieを含めます。ここで、%x2F( “/”)文字はディレクトリセパレータとして解釈されます。
あれ? なんか「request-uriのパス部分がCookieのPath属性に一致する(またはそのサブディレクトリである)場合にのみ、HTTPリクエストにCookieを含めます」って言ってるね。でも「サーバーがPath属性を省略した場合、ユーザーエージェントはrequest-uriのパスコンポーネントのディレクトリをデフォルト値として使用します」とも言ってるね。て事は省略している場合はリダイレクトでもしてURLが変わらないのであれば POST した URL ならCookie が送信されるよね?
実際、以前に作った Golang でサーバーサイドレンダリングまでしていたものでは問題出てなかったんだよなあ。でもここが相当疑わしいよね…。試しに Path=’/’ をセットして Build し直してみよう。ってことで APIServer リスタートでログインしてみると、
あった!あった!ありましたよ! 私の Cookie !!!
Yes! Yes!! Yes!!! とひとしきり大喜びした後で冷静になってみると、なんか当然のような気がしてきた…。
そりゃそうだよね。ドメインのルートを指定してるんだから。わかってみれば当然なんだけどなんだかもやります。
だって省略したら request-uri に送信されるんだよね。 前回は同様のやり方でキチンと保存されていたんだよなぁ。 私のCookie…。
でも実際に運用するなら普通にドメインの全てで使えないと困るんだから、最初から設定しておけば良かった…。
とまぁ、すったもんだの挙句にようやくスタートが切れた個人開発プロジェクトですが、知らなかったことを新たに知ったこともあったので、とりあえずよしとしましょう。特に知らなかったのは下のやつ。
実装に関する考慮事項
実用的なユーザーエージェントの実装では、保存できるCookieの数とサイズに制限があります。汎用ユーザーエージェントは、以下の最小機能のそれぞれを提供する必要があります(SHOULD)。
o Cookieごとに少なくとも4096バイト(Cookieの名前、値、および属性の長さの合計で測定)。
o ドメインごとに少なくとも50のCookie。
o 合計で少なくとも3,000のクッキー。
サーバーは、これらの実装の制限に達しないように、またすべてのリクエストにCookieヘッダーが含まれているためにネットワーク帯域幅を最小限に抑えるために、可能な限り少ない数のCookieを使用してください。
マジで? こんなルールあったの? 知らんかった。まぁ流石に 4MB の Cookie はやらないと思うけど。
正直、この記事投稿するかどうかかなり迷ったけど、ひょっとしてどこかの誰かが自分と同じような穴に落ちた時に役に立てば良いなと思い投稿することにしました。現場からは以上です。
コメント