Skip to content

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/svelterouter, use:inertia, <Link>)가 가로채는 SPA 입니다. 1pass 로그인은 cross-origin 302 리다이렉트로 시작하는데, 클라이언트 라우터는 이 302 를 XHR 로 따라가려다 조용히 실패합니다 (Turbo Drive 와 동일한 함정). 그래서 세 군데에서 처리가 달라집니다.

① 로그인 시작 — 클라이언트 라우터를 우회하라 (가장 중요)

"1pass 로 로그인" 버튼은 반드시 전체 페이지 네비게이션이어야 합니다. use:inertia / <Link> / router.visit 를 쓰면 안 됩니다.

svelte
<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 를 씁니다.

ruby
# 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
end

front-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직접 읽으세요.

svelte
<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_shareauth.user 내려줌, current_user 는 세션 기반
  • [ ] Svelte 에서 $page 가 아니라 page.props 직접 접근
  • [ ] RP 등록 = Confidential, secret 은 서버 env 에만

더 보기

  • Rails 8 — 컨트롤러 · PKCE · 토큰 교환 · JWT 검증 (서버 측 전부)
  • SPA + 서버리스 — 백엔드 없는 순수 SPA (Public client)
  • 로그인 버튼 — 버튼 디자인 · 시작 라우트 컨벤션

Identity가 제품의 신뢰를 만듭니다.