Rails + Inertia.js + Svelte
먼저 읽어주세요
이 페이지는 Rails 8 가이드의 보완편입니다. 컨트롤러 · PKCE · 토큰 교환 · JWT 검증 등 서버 측 로직은 모두 Rails 가이드와 동일합니다. 여기서는 Inertia.js + Svelte 프론트엔드에서만 달라지는 3가지만 다룹니다.
📋 Confidential client 등록이 필요합니다
서버(Rails)에서 client_secret 을 /oauth/token 에 전달하므로 RP 등록 시 Client type: Confidential 을 선택하세요. 프론트엔드(Svelte)에는 secret 을 절대 노출하지 않습니다.
Inertia 앱은 페이지 전환을 클라이언트 라우터(@inertiajs/svelte 의 router, use:inertia, <Link>)가 가로채는 SPA 입니다. 1pass 로그인은 cross-origin 302 리다이렉트로 시작하는데, 클라이언트 라우터는 이 302 를 XHR 로 따라가려다 조용히 실패합니다 (Turbo Drive 와 동일한 함정). 그래서 세 군데에서 처리가 달라집니다.
① 로그인 시작 — 클라이언트 라우터를 우회하라 (가장 중요)
"1pass 로 로그인" 버튼은 반드시 전체 페이지 네비게이션이어야 합니다. use:inertia / <Link> / router.visit 를 쓰면 안 됩니다.
<script lang="ts">
import { inertia } from "@inertiajs/svelte"
</script>
<!-- ✅ 로그인 시작: plain <a>. use:inertia 금지.
logi 의 authorize 는 cross-origin 302 라서 클라이언트 라우터가 먹어버립니다. -->
<a href="/auth/logi/start" data-no-inertia>1pass 로 로그인</a>
<!-- ❌ 절대 금지: 콘솔/네트워크에 에러도 없이 아무 일도 안 일어납니다 -->
<!-- <a href="/auth/logi/start" use:inertia>1pass 로 로그인</a> -->/auth/logi/start 라우트는 RP 가 자체 결정합니다 (로그인 버튼 참고). 서버 컨트롤러의 redirect_to(..., allow_other_host: true) 는 Rails 가이드와 동일합니다.
콜백은 신경 쓸 게 없습니다
/auth/logi/callback 은 서버 컨트롤러가 받아 redirect_to root_path 로 끝내므로 Inertia 가 자연스럽게 다음 페이지를 렌더합니다. 시작(②아님 ①)만 우회하면 됩니다.
② 로그인 상태 — inertia_share 로 current_user 전달
별도 API 왕복 없이 모든 Inertia 페이지에 로그인 사용자를 내려주려면 ApplicationController#inertia_share 를 씁니다.
# app/controllers/application_controller.rb
class ApplicationController < ActionController::Base
inertia_share do
{
auth: {
user: current_user && {
id: current_user.id,
email: current_user.email,
displayName: current_user.display_name
}
},
flash: { notice: flash[:notice], alert: flash[:alert] }
}
end
private
def current_user
@current_user ||= User.find_by(id: session[:user_id]) if session[:user_id]
end
endfront-channel 데이터로 User 를 만들지 마세요
current_user 는 서버 세션(session[:user_id])에서만 옵니다. 사용자 생성은 서버 측 userinfo 호출 결과로만 하세요 — Rails 가이드: 첫 로그인.
③ Svelte 측 — page 는 store 가 아니라 rune (page.props)
@inertiajs/svelte v3 함정
v2 의 $page store 패턴은 v3 에서 깨집니다. v3 의 page 는 $state rune 객체라서 $page 로 구독하면 store_invalid_shape 로 hydration 이 실패합니다. page.props 를 직접 읽으세요.
<script lang="ts">
import { inertia, page, router } from "@inertiajs/svelte"
let { children } = $props()
// ✅ v3: page 는 rune. $page (store 구독) 아님.
let user = $derived(
(page.props as any)?.auth?.user as
| { displayName: string; email?: string }
| null
| undefined
)
function logout() {
router.delete("/auth/logi/logout") // 로컬 세션 종료 → root 로 full reload
}
</script>
<header>
{#if user}
<span title={user.email}>👤 {user.displayName}</span>
<button type="button" onclick={logout}>로그아웃</button>
{:else}
<!-- ① 다시 강조: 로그인 시작은 plain <a> -->
<a href="/auth/logi/start" data-no-inertia>1pass 로 로그인</a>
{/if}
</header>로그아웃은 router.delete 로 안전합니다 (same-origin DELETE — cross-origin 302 가 아니므로 클라이언트 라우터로 처리해도 됨).
체크리스트
- [ ] 로그인 시작 버튼이 plain
<a href>(use:inertia/<Link>아님) - [ ]
inertia_share로auth.user내려줌,current_user는 세션 기반 - [ ] Svelte 에서
$page가 아니라page.props직접 접근 - [ ] RP 등록 = Confidential, secret 은 서버 env 에만
더 보기
- Rails 8 — 컨트롤러 · PKCE · 토큰 교환 · JWT 검증 (서버 측 전부)
- SPA + 서버리스 — 백엔드 없는 순수 SPA (Public client)
- 로그인 버튼 — 버튼 디자인 · 시작 라우트 컨벤션