Upgrade to Pro
— share decks privately, control downloads, hide ads and more …
Speaker Deck
Sign up for free
Menu
Search
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Features
All features
Private URLs
Password Protection
Custom URLS
Scheduled publishing
Remove Branding
Restrict embedding
Deck Collections
Notes
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Explore
Featured decks
Featured speakers
Programming
Technology
Storyboards
Pricing
Search
Sign in
Sign up for free
ドメイン指定Cookieとサービス間共有Redisで作る認証基盤サービス
Search
kokuyouwind
September 26, 2025
Programming
47
0
Share
Embed
Copy iframe code
Copy JS code
Copy link
Start on current slide
ドメイン指定Cookieとサービス間共有Redisで作る認証基盤サービス
Kaigi on Rails 2025 の発表資料です。
https://kaigionrails.org/2025/talks/kokuyouwind/#day2
kokuyouwind
September 26, 2025
More Decks by kokuyouwind
See All by kokuyouwind
疑似コードによるプロンプト記述、どのくらい正確に実行される?
kokuyouwind
0
610
謎解きサイトを Rails SPA で作って RubyKaigi で配布した話
kokuyouwind
0
99
Do LLMs dream of Type Inference?
kokuyouwind
0
50
Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜
kokuyouwind
0
8.7k
APMをちゃんと使おうとしたら、いつのまにか独自gemを作っていた話
kokuyouwind
0
990
RBS meets LLMs - Type inference using LLM
kokuyouwind
0
1.1k
オンラインボードゲームを作りたい人生だった
kokuyouwind
0
720
1年間本番運用してわかった、スタートアップこそAWS Copilot CLIを使うべきNつの理由
kokuyouwind
2
12k
なるべく楽したいAWSセキュリティ
kokuyouwind
1
130
Other Decks in Programming
See All in Programming
すこし踏み込む CancellationToken
htkym
2
1.5k
カツオ、ご期待ください
suneo3476
0
130
Embedded Swiftで作る自作USBデバイス によるiOSデバイスの自動テスト / iOSDC Japan 2026 glassfiber
glassfiber
0
240
iOSDC Japan 2026 - Swiftで作って学ぼう!データベース自作入門
kaseken
2
410
標準パッケージに uuid が追加された 背景から見る Go らしい意思決定 / go_127_uuid_decision
convto
5
9.3k
AGENTS.md Is Not Enough:Build Skills, Don't Download Them
lx_t
0
130
Androidだけじゃない、DroidKaigiで広がった私の技術スタック
akkeylab
0
110
手動確認はもう限界 〜XCUITestでCustom URL Schemeの遷移を起動種別ごとに自動テストする〜 / Testing Custom URL Schemes with XCUITest
otouto
0
370
UnityでSystem.Net.WebSocketsなWebSocketサーバが動かないのでUnity Monoのコードを覗いてみた / about implementing websocket server with unity mono
drumath2237
1
510
Streamlitで実現する自然言語データアプリ開発
ayumu_yamaguchi
1
330
技術的負債を組織課題として解く-増えすぎたマイクロサービスとの戦い-
reimaru
1
2.6k
Verilogで学ぶCPU自作入門.pdf
uyuki234
7
3.9k
Featured
See All Featured
コードの90%をAIが書く世界で何が待っているのか / What awaits us in a world where 90% of the code is written by AI
rkaga
63
46k
Designing Dashboards & Data Visualisations in Web Apps
destraynor
232
55k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
490
My Coaching Mixtape
mlcsv
0
330
Improving Core Web Vitals using Speculation Rules API
sergeychernyshev
21
1.6k
Java REST API Framework Comparison - PWX 2021
mraible
34
9.7k
Testing 201, or: Great Expectations
jmmastey
46
8.3k
Conquering PDFs: document understanding beyond plain text
inesmontani
PRO
4
3.1k
The Power of CSS Pseudo Elements
geoffreycrofte
82
6.6k
Embracing the Ebb and Flow
colly
88
5.2k
SEOcharity - Dark patterns in SEO and UX: How to avoid them and build a more ethical web
sarafernandez
0
290
Writing Fast Ruby
sferik
630
63k
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