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
Sponsored
·
Your Podcast. Everywhere. Effortlessly.
Share. Educate. Inspire. Entertain. You do you. We'll handle the rest.
→
kokuyouwind
September 26, 2025
Programming
46
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
590
謎解きサイトを Rails SPA で作って RubyKaigi で配布した話
kokuyouwind
0
95
Do LLMs dream of Type Inference?
kokuyouwind
0
49
Let's use LLMs from Ruby 〜 Refine RBS types using LLM 〜
kokuyouwind
0
8.6k
APMをちゃんと使おうとしたら、いつのまにか独自gemを作っていた話
kokuyouwind
0
980
RBS meets LLMs - Type inference using LLM
kokuyouwind
0
1.1k
オンラインボードゲームを作りたい人生だった
kokuyouwind
0
710
1年間本番運用してわかった、スタートアップこそAWS Copilot CLIを使うべきNつの理由
kokuyouwind
2
12k
なるべく楽したいAWSセキュリティ
kokuyouwind
1
120
Other Decks in Programming
See All in Programming
Can LLMs Replicate 4 Years of Compose Migration? Exploring the boundaries of automation with 279 XML files from a real product
makun
0
130
LoopHub - ローカルで動く GitHub で、AI と共同開発
jugyo
1
470
tsc.rip を支える技術 / Kyoto.なんか #8
susisu
0
4.4k
ALB ログから Trace を気合で繋げる技術
fohte
7
880
GraphRAGのKnowledge Graphを 直接!見る/View-GraphRAG's-KnowledgeGraph-directly!
tyumugi1113
1
270
thread_parallel_with_free-threaded_Python_and_NumPy.pdf
riku_sakamoto
0
200
kubernetes コンポーネント開発入門 / 新卒N年目の勉強会&交流会!〜〇〇への誘い〜 #n_study
mazrean
0
180
フロントエンドUIフレームワークのこれまでとこれから
ssssota
2
1.3k
{ Android | Kotlin } Gradle Plugin in 2026
ryunen344
1
290
AIエージェント時代のコードレビューを設計する
nogu66
6
2.6k
LL言語やWebフレームワークのPostgreSQL対応 〜DBの機能がユーザーに届くまで〜
kentaroutakeda
0
140
新卒PdEのリアル
ryu1013
1
470
Featured
See All Featured
Chrome DevTools: State of the Union 2024 - Debugging React & Beyond
addyosmani
10
1.3k
Redefining SEO in the New Era of Traffic Generation
szymonslowik
1
410
New Earth Scene 8
popppiees
3
2.5k
Marketing Yourself as an Engineer | Alaka | Gurzu
gurzu
0
300
Claude Code どこまでも/ Claude Code Everywhere
nwiizo
67
58k
Collaborative Software Design: How to facilitate domain modelling decisions
baasie
1
320
Statistics for Hackers
jakevdp
799
230k
Effective software design: The role of men in debugging patriarchy in IT @ Voxxed Days AMS
baasie
0
510
How to Align SEO within the Product Triangle To Get Buy-In & Support - #RIMC
aleyda
2
1.8k
The AI Search Optimization Roadmap by Aleyda Solis
aleyda
1
6.2k
Primal Persuasion: How to Engage the Brain for Learning That Lasts
tmiket
0
450
Designing for Performance
lara
611
70k
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