Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Features
Speaker Deck
PRO
Sign in
Sign up for free
Search
Search
ドメイン指定Cookieとサービス間共有Redisで作る認証基盤サービス
Search
kokuyouwind
September 26, 2025
Programming
0
11
ドメイン指定Cookieとサービス間共有Redisで作る認証基盤サービス
Kaigi on Rails 2025 の発表資料です。
https://kaigionrails.org/2025/talks/kokuyouwind/#day2
kokuyouwind
September 26, 2025
Tweet
Share
More Decks by kokuyouwind
See All by kokuyouwind
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
370
謎解きサイトを Rails SPA で作って RubyKaigi で配布した話
kokuyouwind
0
48
Do LLMs dream of Type Inference?
kokuyouwind
0
13
Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜
kokuyouwind
0
7.7k
APMをちゃんと使おうとしたら、いつのまにか独自gemを作っていた話
kokuyouwind
0
890
RBS meets LLMs - Type inference using LLM
kokuyouwind
0
940
オンラインボードゲームを作りたい人生だった
kokuyouwind
0
630
1年間本番運用してわかった、スタートアップこそAWS Copilot CLIを使うべきNつの理由
kokuyouwind
2
11k
なるべく楽したいAWSセキュリティ
kokuyouwind
1
98
Other Decks in Programming
See All in Programming
FOSDEM 2026: STUNMESH-go: Building P2P WireGuard Mesh Without Self-Hosted Infrastructure
tjjh89017
0
140
HTTPプロトコル正しく理解していますか? 〜かわいい猫と共に学ぼう。ฅ^•ω•^ฅ ニャ〜
hekuchan
2
670
Spinner 軸ズレ現象を調べたらレンダリング深淵に飲まれた #レバテックMeetup
bengo4com
1
220
コントリビューターによるDenoのすゝめ / Deno Recommendations by a Contributor
petamoriken
0
200
GISエンジニアから見たLINKSデータ
nokonoko1203
0
200
AI Agent の開発と運用を支える Durable Execution #AgentsInProd
izumin5210
7
2.3k
CSC307 Lecture 03
javiergs
PRO
1
490
AgentCoreとHuman in the Loop
har1101
5
210
AIによるイベントストーミング図からのコード生成 / AI-powered code generation from Event Storming diagrams
nrslib
2
1.8k
Apache Iceberg V3 and migration to V3
tomtanaka
0
130
コマンドとリード間の連携に対する脅威分析フレームワーク
pandayumi
1
440
カスタマーサクセス業務を変革したヘルススコアの実現と学び
_hummer0724
0
510
Featured
See All Featured
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
110
Being A Developer After 40
akosma
91
590k
Information Architects: The Missing Link in Design Systems
soysaucechin
0
760
Making Projects Easy
brettharned
120
6.6k
The Art of Delivering Value - GDevCon NA Keynote
reverentgeek
16
1.8k
Organizational Design Perspectives: An Ontology of Organizational Design Elements
kimpetersen
PRO
1
75
JavaScript: Past, Present, and Future - NDC Porto 2020
reverentgeek
52
5.8k
The Cost Of JavaScript in 2023
addyosmani
55
9.5k
Building an army of robots
kneath
306
46k
Embracing the Ebb and Flow
colly
88
5k
[RailsConf 2023 Opening Keynote] The Magic of Rails
eileencodes
31
9.9k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.1k
Transcript
ドメイン指定Cookie と サービス間共有Redis で作る 認証基盤サービス Leaner Technologies, Inc. 黒曜 (@kokuyouwind)
1
$ whoami 黒曜 / @kokuyouwind Leaner Technologies Inc. 所属 Rails
エンジニア・SRE 今はLeaner の認証基盤サービスを 作ってます 2
認証基盤サービス? 3
シンプルに Rails アプリを作る場合、 認証( ログイン) も1 つの機能として実装 認証機能 プロダクト機能 ログイン
機能利⽤ 4
複数のアプリがあるとそれぞれログインが必要 認証機能 プロダクトA 機能 認証機能 プロダクトB 機能 ログイン 機能利⽤ ログイン
機能利⽤ 5
認証機能を単独のサービスに切り出す 認証機能 プロダクトB 機能 ログイン 機能利⽤ 機能利⽤ 6 プロダクトA 機能
認証基盤サービス 認証機能 プロダクトB 機能 ログイン 機能利⽤ 機能利⽤ プロダクトA 機能 7
認証基盤、どう作る? セッション管理 アクセストークン セキュリティ リフレッシュトークン JWT OIDC SSO ローカルストレージ PKCE
nonce state IdP SP Auth0 Cognito ActiveDirectory SAML SCIM 8
弊社の認証基盤で採⽤した 「ドメイン指定Cookie + 共有Redis 」構成の 紹介をします 9
アジェンダ 認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ 10
認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ アジェンダ 11
認証・認可のイメージ( 郵便受取) 1. 私は黒曜です 荷物の受取をお願いします 2. 確かに正当な本⼈証明書でした あなたは黒曜さんです( 認証) 3.
この荷物は黒曜さん宛なので 黒曜さんに渡してOK( 認可) 12
認証・認可のイメージ(Web) 1. 私は黒曜です パスワードはxxx です 2. 確かに正しいパスワードでした あなたは黒曜さんです( 認証) 3.
毎回本⼈確認を⾏うと⼤変なので、 クッキーにあなたの情報をメモしました クッキーの中身はサーバーからしか⾒えないので、 以降はこのクッキーを送ってください( セッションの記録) 13
認証・認可のイメージ(Web) 4. ファイルにアクセスしたいです クッキーはこれです 5. クッキーの中には「黒曜さん」が 記録されているので、 この⼈は黒曜さんです( セッションの復元) 6.
リクエストされたファイルの持ち主は黒曜さんなので、 黒曜さんにこのファイルを送ってもOK( 認可) 14
リアルとWeb の違い HTTP はステートレス 認証リクエストと、認可の必要なリクエストは独⽴ 認証の結果をセッションに保存することで、 以降のリクエストの認可制御が⾏える 認証機能を切り出そうと考えると… 認可は切り分けて考えやすそう セッション管理は密接に関連しそう
15
ここからは、 「認証結果をどうセッションに保存するか」に フォーカスして掘り下げます 16
Rails の認証 よく使われるのは以下のgem Rails 8 では認証ジェネレータも導⼊された bin/rails generate authentication 16:30
からのwillnet さんのセッションで詳しく聞けそう Devise Sorcery Rodauth 17
Devise # lib/devise/controllers/sign_in_out.rb def sign_in(resource_or_scope, *args) # ... warden.session_serializer.store(resource, scope)
# ... end # lib/warden/session_serializer.rb def store(user, scope) return unless user method_name = "#{scope}_serialize" specialized = respond_to?(method_name) session[key_for(scope)] = specialized ? send(method_name, user) : serialize(user) end 1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 session['warden.user.user.key'] = [user.id, user.salt] セッションにユーザーID( とsalt) を保存 18
基本的には User Model のid を session[]= で保存するだけ 19
Rails のセッション管理 session[] と session[]= で読み書きできる config.session_store で保存⽅法を決定 デフォルトは CookieStore
暗号化してCookie に丸ごと保存 サーバー側にデータは持たない ミドルウェア系Store(MemCacheStore, Redis::Store など) Cookie にセッションID のみを保存 サーバー側ミドルウェアでセッションID をキーにデータを保存 20
CookieStore イメージ 私の情報はこの⾦庫に⼊ってます 鍵を使って中身を取り出しました 黒曜さんのuser.id が書いてあるので この⼈は黒曜さんですね 21
ミドルウェア系のStore イメージ 私の情報は16 番⾦庫に⼊ってます 16 番⾦庫からメモを取り出しました 黒曜さんのuser.id が書いてあるので この⼈は黒曜さんですね 22
認証・認可・セッション管理 まとめ 認証は「本⼈確認」、認可は「権限確認」 認可は都度判断なので認証とは切り離せる 認証結果はセッションに保存する必要がある セッションの保存⽅法は⼤まかに2 種類 CookieStore は暗号化して全部Cookie に⼊れる
ミドルウェア系Store はサーバー側にデータを持って 取り出し⽤の鍵だけCookie に⼊れる 23
認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ アジェンダ 24
そもそも認証基盤は必要? ずっと1 プロダクトしか作らないならまず必要ない 分離するコスト・保守するコストがかかる 責務の分割だけなら Rails Engine 切り出しや gem を適切に使えば⼗分そう
ユーザ層が被る複数プロダクトを提供するならほぼ必須 マルチプロダクト戦略・コンパウンド戦略 ユーザ視点・開発視点でメリットが多い 25
認証機能を単独のサービスに切り出す( 再掲) 認証機能 プロダクトB 機能 ログイン 機能利⽤ 機能利⽤ プロダクトA 機能
26
課題 認証機能 ログイン 機能利⽤ プロダクトA 機能 認証した結果をどう渡す? セッションをそれぞれで管理する? なんらかの⽅法で共通化する? 27
案A. 認証結果だけ渡してセッション個別管理 認証機能 ログイン ログイン結果連携 機能利⽤ プロダクトA 機能 なんらかの⼿段で認証結果を渡す セッションは各プロダクトで個別に管理
( それぞれのプロダクトのuser.id を保存) (OIDC はセッション管理に責務を持たないのでこれ) 28
案B. 前段でセッションを集約管理 認証機能 1. ログイン 3. 機能利⽤ プロダクトA 機能 (ALB
でのCognito 認証連携などはこれ) 2. 認証結果をセッションに記憶 29 4. 認証結果を取り出してプロダクトA に⼀緒に送 信
案C. セッションを共有 ( 今回の本題) 認証機能 1. ログイン 4. 機能利⽤ (
鍵を渡す) プロダクトA 機能 2. ユーザーID を記録 3. 鍵を渡す 5. ユーザーID を取得 30 (JWT トークンは鍵⾃体に情報を埋め込むが、原理はこれに近い)
どれがいいの? 認証情報をOIDC で受け渡す形にすると技術標準に乗れる … が、事前にOAuthApplication 払い出しが必要だったり state, nonce, PKCE などセキュリティ担保のための仕様が多く複雑
OIDC はサードパーティーにID を提供する⽬的の仕様 ⾃社サービスだけで使うのにほんとにそこまで必要? 前段で受けるのはインフラ構成の制約が⼤きい セッションをうまく共有できれば良いのでは? という⽅針でこの後の話をしていきます 31
認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ アジェンダ 32
Leaner の認証基盤サービス構成 認証基盤 1. ログイン 4. 機能利⽤ (Cookie も送信) 2.
ユーザーsub を記録 (via. redis-session-store) 3. Cookie にsession_id を保存 5. ユーザーsub を取得 33
ログイン ~ ユーザーsub を記録 認証基盤 1. ログイン 4. 機能利⽤ (Cookie
も送信) 2. ユーザーsub を記録 (via. redis-session-store) 3. Cookie にsession_id を保存 5. ユーザーsub を取得 34
ログイン ~ ユーザーsub を記録 認証: + セキュリティ強そうなのと拡張性⾼そうなので選定 パスワードログイン: Rodauth 標準
SAML ログイン: で独⾃Feature を実装 セッション管理: Sidekiq とか使うかなと思ってRedis を選定 Rodauth Rodauth Rails ruby-saml redis-session-store 35
ログイン ~ ユーザーsub を記録 # app/misc/rodauth_main.rb class RodauthMain < Rodauth::Rails::Auth
configure do after_login do # save user session[:shared_sub] = user.sub end end end 1 2 3 4 5 6 7 8 9 # leaner:session:2::b0db908bd1428e4638... { "shared_sub": "ZiJkFfmqOzSuTtvMwHflJDxWD7h1", "leaner_id_user_id": 6, "leaner_id_active_session_id": "q_KRmXTSw3...", "leaner_id_authenticated_by": [ "password" ] } 1 2 3 4 5 6 7 8 9 36
Cookie の保存 ~ 送信 認証基盤 1. ログイン 4. 機能利⽤ (Cookie
も送信) 2. ユーザーsub を記録 (via. redis-session-store) 3. Cookie にsession_id を保存 5. ユーザーsub を取得 37
Cookie の保存 ~ 送信 認証基盤 auth.leaner.app mitsumori.leaner.app 認証基盤とプロダクトはURL が異なる (
後出しの制約) Cookie は基本的に同⼀ドメインにしか送らないため、 認証基盤でセッションID をCookie に格納しても プロダクト側に送られない! 38
Cookie の保存 ~ 送信 # config/application.rb Rails.application.config.session_store :redis_session_store, key: 'LEANER_SESSION_ID',
domain: '.leaner.app', secure: true, same_site: :lax, serializer: :json, redis: { # ... } 1 2 3 4 5 6 7 8 9 10 Cookie に domain を指定すると、後⽅⼀致で送信するか決まる こうすると auth.leaner.app と mitsumori.leaner.app の 両⽅に同じCookie を送信する 39
ユーザーsub を取得 認証基盤 1. ログイン 4. 機能利⽤ (Cookie も送信) 2.
ユーザーsub を記録 (via. redis-session-store) 3. Cookie にsession_id を保存 5. ユーザーsub を取得 40
ユーザーsub を取得 # leaner:session:2::b0db908bd1428e4638... { "shared_sub": "ZiJkFfmqOzSuTtvMwHflJDxWD7h1", "leaner_id_user_id": 6, "leaner_id_active_session_id":
"q_KRmXTSw3...", "leaner_id_authenticated_by": [ "password" ] } 1 2 3 4 5 6 7 8 9 # app/controller/application_controller.rb class ApplicationController < ActionController::API def current_user User.find_by(sub: session[:shared_sub]) end end 1 2 3 4 5 6 41
Leaner の認証基盤サービス構成( 再掲) 認証基盤 1. ログイン 4. 機能利⽤ (Cookie も送信)
2. ユーザーsub を記録 (via. redis-session-store) 3. Cookie にsession_id を保存 5. ユーザーsub を取得 42
認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ アジェンダ 43
認証基盤の実装詳細・運⽤など 44
プロダクト側で使う独⾃gem LeanerAuthenticatable gem を使って プロダクト側処理を共通化 # app/controller/application_controller.rb class ApplicationController <
ActionController::API # 認証基盤からユーザーを取得する include LeanerAuthenticatable.create_module( organization_class: Organization, user_assoc_method: 'users', product_code: 'mitsumori', session_key_prefix: 'mitsumori' ) # 以下メソッドが利⽤できるようになる # def current_user: () -> User end 1 2 3 4 5 6 7 8 9 10 11 12 13 45
組織からのユーザー取得 # app/controller/application_controller.rb class ApplicationController < ActionController::API # 認証基盤からユーザーを取得する include
LeanerAuthenticatable.create_module( organization_class: Organization, user_assoc_method: 'users', # Organization # .find_by(sub: session[:shared_organization_sub]) # .users.find_by(sub: session[:shared_sub]) 1 2 3 4 5 6 7 8 9 # leaner:session:2::b0db908bd1428e4638... { "shared_organization_sub": "M0qMtiOCrv5...", "shared_sub": "ZiJkFfmqOzSuTtvMwHflJDxWD7h1", "shared_product_licenses": [ "mitsumori" ], ... } 1 2 3 4 5 6 7 8 9 46
プロダクト利⽤権の判定 # app/controller/application_controller.rb class ApplicationController < ActionController::API # 認証基盤からユーザーを取得する include
LeanerAuthenticatable.create_module( # ... product_code: 'mitsumori', # session[:shared_product_licenses] # .member?('mitsumori') 1 2 3 4 5 6 7 8 # leaner:session:2::b0db908bd1428e4638... { "shared_organization_sub": "M0qMtiOCrv5...", "shared_sub": "ZiJkFfmqOzSuTtvMwHflJDxWD7h1", "shared_product_licenses": [ "mitsumori" ], ... } 1 2 3 4 5 6 7 8 9 47
session の名前空間分割 # app/controller/application_controller.rb class ApplicationController < ActionController::API # 認証基盤からユーザーを取得する
include LeanerAuthenticatable.create_module( # ... session_key_prefix: 'mitsumori' # class SessionStoreWrapper # def [](key) # parent[shared_key?(key) # ? key : :"#{prefix}_#{key}"] # end 1 2 3 4 5 6 7 8 9 10 11 # leaner:session:2::b0db908bd1428e4638... { # session[:login_method] = "leaner_auth" "mitsumori_login_method": "leaner_auth" # shared_ から始まるキーはそのまま読み書きできる "shared_sub": "ZiJkFfmqOzSuTtvMwHflJDxWD7h1", ... 1 2 3 4 5 6 7 48
gem のリリースとバージョン管理 GitHub Packages で配信し、 プロダクト側のGemfile で依存バージョンを管理 49
# docker-compose.yml services: auth_server: image: ghcr.io/.../leaner-auth/leaner-auth-api:latest # <= 認証基盤 redis:
image: redis:alpine # <= session store ⽤のredis proxy: image: ghcr.io/.../leaner-auth/minica-proxy:latest # <= ??? 1 2 3 4 5 6 7 8 ローカル開発⽤ 認証基盤コンテナ プロダクト側のローカル開発時に使えるよう、 認証基盤をコンテナ化してGitHub Packages で配信 50
ローカル開発⽤ プロキシ ローカル開発でもCookie でセッション共有できるよう minica を使って証明書を発⾏、 nginx でプロキシ 認証基盤 https://auth.leaner.localhost
https://mitsumori.leaner.localhost w/Cookie SetCookie domain: .leaner.localhost 51
データの同期 認証基盤と各プロダクトでDB が異なるため、 sub をキーとしてデータの同期が必要 ( 現在はプロダクト側にそれぞれユーザー管理がある) 情報変更時の都度同期のほか、⼀括同期タスクも作成 認証基盤 POST
/internal/users/[:sub] 52
想定 Q & A 53
Q. CookieStore じゃできないの? A. secret_key 依存があるので厳しい CookieStore ではセッション値を暗号化して設定するが、 このときの暗号化鍵が Rails
の secret_key_base に依存している。 すでに稼働しているプロダクトの secret_key_base を共通化するのは厳しい。 RedisStore ではセッションID からRedis key を計算する際 ハッシュ関数しか使わないので、どのプロダクトで計算しても同じになる。 多分他のミドルウェア系SessionStore でも同じ感じのはず。 54
Q. Rodauth どう? A. 慣れると結構良いがおすすめはしない めちゃくちゃ柔軟性が⾼くカスタム処理が書きやすいが、 独⾃DSL でかなり慣れが必要なうえ、 ドキュメントはRDoc メインで使い⽅例みたいなのはほとんどない。
困ったら元コードを掘るパワーが必要。 あとAI との相性が死ぬほど悪い。 正直素直に広く使われているdevise とかを使うほうが AI のサポートを受けやすくて良いと思う。 55
Q. 既存のIDaaS 使わないの? A. 検討したけどコスパや柔軟性でやめた 正直むずかしいのは認証というよりセッション同期 SAML 使うと⼀気に⾼くなり、⻑期的には独⾃でやるほうが良いと判断 実は Google
Identity Platform を認証基盤化するつもりで⼀部採⽤してたが パスワードポリシーが弄れないなど⾊々しんどくて諦めた パスワードハッシュexport したらBcrypt じゃなく独⾃scrypt で 計算するのにC build が必要と⾔われてインポートを泣く泣く諦め ログイン時の都度migration に倒している 56
認証・認可・セッション管理 認証共通化のアーキテクチャ選択肢 Leaner の認証基盤サービス構成 各種Tips まとめ アジェンダ 57
まとめ マルチプロダクト戦略だと認証基盤サービスが欲しくなる 実現⽅法はいろいろある 認証結果の連携はOIDC が技術標準だが、 サードパーティーを考慮していて仕様が複雑 ⾃社サービス内だけならセッション共有がシンプルでは Cookie を親レベルにすればRails の設定レベルで実現できる
共通ドメインが必要など制約も多いので、ハマる局⾯は限られる これが良いやり⽅か⾃分もわからないので、⾊々話しましょう! 58