inertia-rails-auth

Compare original and translation side by side

🇺🇸

Original

English
🇨🇳

Translation

Chinese

Inertia Rails Authentication & Authorization

Inertia Rails 认证与授权

Guide to implementing authentication and authorization in Inertia Rails applications.
在Inertia Rails应用中实现认证与授权的指南。

Key Principle

核心原则

Inertia uses your existing Rails authentication infrastructure. No special OAuth or token-based auth required. Since your frontend and backend share the same domain, session-based auth works seamlessly.
Inertia 会复用你现有的Rails认证基础设施,无需特殊的OAuth或基于令牌的认证方案。由于前端与后端共享同一域名,基于会话的认证可以无缝工作。

Authentication with Devise

使用 Devise 实现认证

Setup

配置步骤

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem 'devise'

```bash
bundle install
rails generate devise:install
rails generate devise User
rails db:migrate
gem 'devise'

```bash
bundle install
rails generate devise:install
rails generate devise User
rails db:migrate

Share Authentication State

共享认证状态

ruby
undefined
ruby
undefined

app/controllers/application_controller.rb

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base inertia_share do { auth: { user: current_user&.as_json(only: [:id, :name, :email, :avatar_url]), signed_in: user_signed_in? } } end end
undefined
class ApplicationController < ActionController::Base inertia_share do { auth: { user: current_user&.as_json(only: [:id, :name, :email, :avatar_url]), signed_in: user_signed_in? } } end end
undefined

Create Login Page

创建登录页面

ruby
undefined
ruby
undefined

app/controllers/sessions_controller.rb

app/controllers/sessions_controller.rb

class SessionsController < Devise::SessionsController def new render inertia: {} end
def create self.resource = warden.authenticate(auth_options)
if resource
  sign_in(resource_name, resource)
  redirect_to after_sign_in_path_for(resource), notice: 'Signed in successfully!'
else
  redirect_to new_session_path(resource_name), inertia: {
    errors: { email: 'Invalid email or password' }
  }
end
end
def destroy sign_out(resource_name) redirect_to root_path, notice: 'Signed out successfully!' end end
undefined
class SessionsController < Devise::SessionsController def new render inertia: {} end
def create self.resource = warden.authenticate(auth_options)
if resource
  sign_in(resource_name, resource)
  redirect_to after_sign_in_path_for(resource), notice: 'Signed in successfully!'
else
  redirect_to new_session_path(resource_name), inertia: {
    errors: { email: 'Invalid email or password' }
  }
end
end
def destroy sign_out(resource_name) redirect_to root_path, notice: 'Signed out successfully!' end end
undefined

Login Component (Vue)

登录组件(Vue)

vue
<!-- app/frontend/pages/sessions/new.vue -->
<script setup>
import { useForm, Link } from '@inertiajs/vue3'

const form = useForm({
  email: '',
  password: '',
  remember: false,
})

function submit() {
  form.post('/users/sign_in', {
    onSuccess: () => form.reset('password'),
  })
}
</script>

<template>
  <form @submit.prevent="submit">
    <h1>Sign In</h1>

    <div>
      <label>Email</label>
      <input v-model="form.email" type="email" autofocus />
      <span v-if="form.errors.email" class="error">{{ form.errors.email }}</span>
    </div>

    <div>
      <label>Password</label>
      <input v-model="form.password" type="password" />
    </div>

    <div>
      <label>
        <input v-model="form.remember" type="checkbox" />
        Remember me
      </label>
    </div>

    <button type="submit" :disabled="form.processing">
      {{ form.processing ? 'Signing in...' : 'Sign In' }}
    </button>

    <p>
      <Link href="/users/sign_up">Create an account</Link>
      <Link href="/users/password/new">Forgot password?</Link>
    </p>
  </form>
</template>
vue
<!-- app/frontend/pages/sessions/new.vue -->
<script setup>
import { useForm, Link } from '@inertiajs/vue3'

const form = useForm({
  email: '',
  password: '',
  remember: false,
})

function submit() {
  form.post('/users/sign_in', {
    onSuccess: () => form.reset('password'),
  })
}
</script>

<template>
  <form @submit.prevent="submit">
    <h1>Sign In</h1>

    <div>
      <label>Email</label>
      <input v-model="form.email" type="email" autofocus />
      <span v-if="form.errors.email" class="error">{{ form.errors.email }}</span>
    </div>

    <div>
      <label>Password</label>
      <input v-model="form.password" type="password" />
    </div>

    <div>
      <label>
        <input v-model="form.remember" type="checkbox" />
        Remember me
      </label>
    </div>

    <button type="submit" :disabled="form.processing">
      {{ form.processing ? 'Signing in...' : 'Sign In' }}
    </button>

    <p>
      <Link href="/users/sign_up">Create an account</Link>
      <Link href="/users/password/new">Forgot password?</Link>
    </p>
  </form>
</template>

Login Component (React)

登录组件(React)

jsx
// app/frontend/pages/sessions/new.jsx
import { useForm, Link } from '@inertiajs/react'

export default function Login() {
  const { data, setData, post, processing, errors, reset } = useForm({
    email: '',
    password: '',
    remember: false,
  })

  function submit(e) {
    e.preventDefault()
    post('/users/sign_in', {
      onSuccess: () => reset('password'),
    })
  }

  return (
    <form onSubmit={submit}>
      <h1>Sign In</h1>

      <div>
        <label>Email</label>
        <input
          type="email"
          value={data.email}
          onChange={(e) => setData('email', e.target.value)}
          autoFocus
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <div>
        <label>Password</label>
        <input
          type="password"
          value={data.password}
          onChange={(e) => setData('password', e.target.value)}
        />
      </div>

      <div>
        <label>
          <input
            type="checkbox"
            checked={data.remember}
            onChange={(e) => setData('remember', e.target.checked)}
          />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={processing}>
        {processing ? 'Signing in...' : 'Sign In'}
      </button>

      <p>
        <Link href="/users/sign_up">Create an account</Link>
        <Link href="/users/password/new">Forgot password?</Link>
      </p>
    </form>
  )
}
jsx
// app/frontend/pages/sessions/new.jsx
import { useForm, Link } from '@inertiajs/react'

export default function Login() {
  const { data, setData, post, processing, errors, reset } = useForm({
    email: '',
    password: '',
    remember: false,
  })

  function submit(e) {
    e.preventDefault()
    post('/users/sign_in', {
      onSuccess: () => reset('password'),
    })
  }

  return (
    <form onSubmit={submit}>
      <h1>Sign In</h1>

      <div>
        <label>Email</label>
        <input
          type="email"
          value={data.email}
          onChange={(e) => setData('email', e.target.value)}
          autoFocus
        />
        {errors.email && <span className="error">{errors.email}</span>}
      </div>

      <div>
        <label>Password</label>
        <input
          type="password"
          value={data.password}
          onChange={(e) => setData('password', e.target.value)}
        />
      </div>

      <div>
        <label>
          <input
            type="checkbox"
            checked={data.remember}
            onChange={(e) => setData('remember', e.target.checked)}
          />
          Remember me
        </label>
      </div>

      <button type="submit" disabled={processing}>
        {processing ? 'Signing in...' : 'Sign In'}
      </button>

      <p>
        <Link href="/users/sign_up">Create an account</Link>
        <Link href="/users/password/new">Forgot password?</Link>
      </p>
    </form>
  )
}

Authentication with has_secure_password

使用 has_secure_password 实现认证

User Model

用户模型

ruby
undefined
ruby
undefined

app/models/user.rb

app/models/user.rb

class User < ApplicationRecord has_secure_password
validates :email, presence: true, uniqueness: true validates :password, length: { minimum: 8 }, allow_nil: true end
undefined
class User < ApplicationRecord has_secure_password
validates :email, presence: true, uniqueness: true validates :password, length: { minimum: 8 }, allow_nil: true end
undefined

Sessions Controller

会话控制器

ruby
undefined
ruby
undefined

app/controllers/sessions_controller.rb

app/controllers/sessions_controller.rb

class SessionsController < ApplicationController skip_before_action :authenticate!, only: [:new, :create]
def new render inertia: {} end
def create user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Welcome back!'
else
  redirect_to login_path, inertia: {
    errors: { email: 'Invalid email or password' }
  }
end
end
def destroy session.delete(:user_id) redirect_to root_path, notice: 'Signed out successfully!' end end
class SessionsController < ApplicationController skip_before_action :authenticate!, only: [:new, :create]
def new render inertia: {} end
def create user = User.find_by(email: params[:email])
if user&.authenticate(params[:password])
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Welcome back!'
else
  redirect_to login_path, inertia: {
    errors: { email: 'Invalid email or password' }
  }
end
end
def destroy session.delete(:user_id) redirect_to root_path, notice: 'Signed out successfully!' end end

Routes

路由

config/routes.rb

config/routes.rb

get 'login', to: 'sessions#new' post 'login', to: 'sessions#create' delete 'logout', to: 'sessions#destroy'
undefined
get 'login', to: 'sessions#new' post 'login', to: 'sessions#create' delete 'logout', to: 'sessions#destroy'
undefined

Application Controller Helper

应用控制器助手方法

ruby
undefined
ruby
undefined

app/controllers/application_controller.rb

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base before_action :authenticate! helper_method :current_user, :user_signed_in?
inertia_share do { auth: { user: current_user&.as_json(only: [:id, :name, :email]), signed_in: user_signed_in? } } end
private
def current_user @current_user ||= User.find_by(id: session[:user_id]) end
def user_signed_in? current_user.present? end
def authenticate! unless user_signed_in? redirect_to login_path, alert: 'Please sign in to continue' end end end
undefined
class ApplicationController < ActionController::Base before_action :authenticate! helper_method :current_user, :user_signed_in?
inertia_share do { auth: { user: current_user&.as_json(only: [:id, :name, :email]), signed_in: user_signed_in? } } end
private
def current_user @current_user ||= User.find_by(id: session[:user_id]) end
def user_signed_in? current_user.present? end
def authenticate! unless user_signed_in? redirect_to login_path, alert: 'Please sign in to continue' end end end
undefined

Authorization

授权

Passing Permissions as Props

以Props形式传递权限

Since frontend components can't access server-side authorization helpers, pass permission results as props:
ruby
undefined
由于前端组件无法访问服务器端的授权助手方法,需要将权限验证结果以Props形式传递:
ruby
undefined

app/controllers/users_controller.rb

app/controllers/users_controller.rb

class UsersController < ApplicationController def index render inertia: { can: { create_user: policy(User).create? }, users: User.all.map do |user| serialize_user(user) end } end
def show user = User.find(params[:id])
render inertia: {
  user: serialize_user(user),
  can: {
    edit: policy(user).edit?,
    delete: policy(user).destroy?
  }
}
end
private
def serialize_user(user) user.as_json(only: [:id, :name, :email]).merge( can: { edit: policy(user).edit?, delete: policy(user).destroy? } ) end end
undefined
class UsersController < ApplicationController def index render inertia: { can: { create_user: policy(User).create? }, users: User.all.map do |user| serialize_user(user) end } end
def show user = User.find(params[:id])
render inertia: {
  user: serialize_user(user),
  can: {
    edit: policy(user).edit?,
    delete: policy(user).destroy?
  }
}
end
private
def serialize_user(user) user.as_json(only: [:id, :name, :email]).merge( can: { edit: policy(user).edit?, delete: policy(user).destroy? } ) end end
undefined

Using Pundit

使用 Pundit

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem 'pundit'
gem 'pundit'

app/controllers/application_controller.rb

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized redirect_to root_path, alert: 'You are not authorized to perform this action' end end
class ApplicationController < ActionController::Base include Pundit::Authorization
rescue_from Pundit::NotAuthorizedError, with: :user_not_authorized
private
def user_not_authorized redirect_to root_path, alert: 'You are not authorized to perform this action' end end

app/policies/user_policy.rb

app/policies/user_policy.rb

class UserPolicy < ApplicationPolicy def index? true end
def show? true end
def create? user.admin? end
def update? user.admin? || record == user end
def destroy? user.admin? && record != user end end
undefined
class UserPolicy < ApplicationPolicy def index? true end
def show? true end
def create? user.admin? end
def update? user.admin? || record == user end
def destroy? user.admin? && record != user end end
undefined

Using Action Policy

使用 Action Policy

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem 'action_policy'
gem 'action_policy'

app/controllers/users_controller.rb

app/controllers/users_controller.rb

class UsersController < ApplicationController def index render inertia: { can: { create_user: allowed_to?(:create?, User) }, users: User.all.map do |user| user.as_json(only: [:id, :name]).merge( can: { edit: allowed_to?(:edit?, user), delete: allowed_to?(:destroy?, user) } ) end } end end
undefined
class UsersController < ApplicationController def index render inertia: { can: { create_user: allowed_to?(:create?, User) }, users: User.all.map do |user| user.as_json(only: [:id, :name]).merge( can: { edit: allowed_to?(:edit?, user), delete: allowed_to?(:destroy?, user) } ) end } end end
undefined

Frontend Permission Checks (Vue)

前端权限验证(Vue)

vue
<script setup>
import { Link, usePage } from '@inertiajs/vue3'

const props = defineProps(['users', 'can'])
const { auth } = usePage().props
</script>

<template>
  <div>
    <h1>Users</h1>

    <!-- Global permission -->
    <Link v-if="can.create_user" href="/users/new" class="btn">
      Create User
    </Link>

    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }}

        <!-- Per-record permissions -->
        <Link v-if="user.can.edit" :href="`/users/${user.id}/edit`">
          Edit
        </Link>

        <Link
          v-if="user.can.delete"
          :href="`/users/${user.id}`"
          method="delete"
          as="button"
        >
          Delete
        </Link>
      </li>
    </ul>
  </div>
</template>
vue
<script setup>
import { Link, usePage } from '@inertiajs/vue3'

const props = defineProps(['users', 'can'])
const { auth } = usePage().props
</script>

<template>
  <div>
    <h1>Users</h1>

    <!-- 全局权限 -->
    <Link v-if="can.create_user" href="/users/new" class="btn">
      Create User
    </Link>

    <ul>
      <li v-for="user in users" :key="user.id">
        {{ user.name }}

        <!-- 单条记录权限 -->
        <Link v-if="user.can.edit" :href="`/users/${user.id}/edit`">
          Edit
        </Link>

        <Link
          v-if="user.can.delete"
          :href="`/users/${user.id}`"
          method="delete"
          as="button"
        >
          Delete
        </Link>
      </li>
    </ul>
  </div>
</template>

Frontend Permission Checks (React)

前端权限验证(React)

jsx
import { Link, usePage } from '@inertiajs/react'

export default function UsersIndex({ users, can }) {
  const { auth } = usePage().props

  return (
    <div>
      <h1>Users</h1>

      {can.create_user && (
        <Link href="/users/new" className="btn">
          Create User
        </Link>
      )}

      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name}

            {user.can.edit && (
              <Link href={`/users/${user.id}/edit`}>Edit</Link>
            )}

            {user.can.delete && (
              <Link
                href={`/users/${user.id}`}
                method="delete"
                as="button"
              >
                Delete
              </Link>
            )}
          </li>
        ))}
      </ul>
    </div>
  )
}
jsx
import { Link, usePage } from '@inertiajs/react'

export default function UsersIndex({ users, can }) {
  const { auth } = usePage().props

  return (
    <div>
      <h1>Users</h1>

      {can.create_user && (
        <Link href="/users/new" className="btn">
          Create User
        </Link>
      )}

      <ul>
        {users.map((user) => (
          <li key={user.id}>
            {user.name}

            {user.can.edit && (
              <Link href={`/users/${user.id}/edit`}>Edit</Link>
            )}

            {user.can.delete && (
              <Link
                href={`/users/${user.id}`}
                method="delete"
                as="button"
              >
                Delete
              </Link>
            )}
          </li>
        ))}
      </ul>
    </div>
  )
}

History Encryption

历史记录加密

Prevent sensitive data exposure via browser back button after logout:
ruby
undefined
登出后,防止敏感数据通过浏览器后退按钮泄露:
ruby
undefined

config/initializers/inertia_rails.rb

config/initializers/inertia_rails.rb

InertiaRails.configure do |config|

Enable globally

config.encrypt_history = true end
InertiaRails.configure do |config|

全局启用

config.encrypt_history = true end

Or per-controller for sensitive areas

或针对敏感区域的控制器单独设置

class Admin::BaseController < ApplicationController inertia_config(encrypt_history: true) end
class Admin::BaseController < ApplicationController inertia_config(encrypt_history: true) end

Or per-request

或针对单个请求设置

def show render inertia: { secret_data: data }, encrypt_history: true end
undefined
def show render inertia: { secret_data: data }, encrypt_history: true end
undefined

Clear History on Logout

登出时清除历史记录

ruby
def destroy
  sign_out(current_user)

  # Clear encrypted history (rotates encryption key)
  redirect_to root_path, inertia: { clear_history: true }
end
Client-side:
javascript
import { router } from '@inertiajs/vue3'

function logout() {
  router.clearHistory()
  router.post('/logout')
}
ruby
def destroy
  sign_out(current_user)

  # 清除加密历史记录(轮换加密密钥)
  redirect_to root_path, inertia: { clear_history: true }
end
客户端实现:
javascript
import { router } from '@inertiajs/vue3'

function logout() {
  router.clearHistory()
  router.post('/logout')
}

Protected Routes

受保护路由

Middleware Approach

中间件方式

ruby
undefined
ruby
undefined

app/controllers/application_controller.rb

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base before_action :authenticate!
private
def authenticate! unless user_signed_in? redirect_to login_path, alert: 'Please sign in' end end end
class ApplicationController < ActionController::Base before_action :authenticate!
private
def authenticate! unless user_signed_in? redirect_to login_path, alert: 'Please sign in' end end end

Skip for public controllers

为公开控制器跳过验证

class PagesController < ApplicationController skip_before_action :authenticate!, only: [:home, :about] end
undefined
class PagesController < ApplicationController skip_before_action :authenticate!, only: [:home, :about] end
undefined

Role-Based Access

基于角色的访问控制

ruby
class Admin::BaseController < ApplicationController
  before_action :require_admin!

  private

  def require_admin!
    unless current_user&.admin?
      redirect_to root_path, alert: 'Admin access required'
    end
  end
end
ruby
class Admin::BaseController < ApplicationController
  before_action :require_admin!

  private

  def require_admin!
    unless current_user&.admin?
      redirect_to root_path, alert: 'Admin access required'
    end
  end
end

CSRF Protection

CSRF 防护

Inertia handles CSRF automatically. Ensure Rails is configured:
ruby
undefined
Inertia 会自动处理CSRF防护。确保Rails已正确配置:
ruby
undefined

app/controllers/application_controller.rb

app/controllers/application_controller.rb

class ApplicationController < ActionController::Base protect_from_forgery with: :exception end

Axios (used by Inertia) automatically:
1. Reads `XSRF-TOKEN` cookie set by Rails
2. Sends `X-XSRF-TOKEN` header with requests
class ApplicationController < ActionController::Base protect_from_forgery with: :exception end

Inertia 使用的Axios会自动:
1. 读取Rails设置的`XSRF-TOKEN` Cookie
2. 在请求中发送`X-XSRF-TOKEN`请求头

Registration Flow

注册流程

ruby
undefined
ruby
undefined

app/controllers/registrations_controller.rb

app/controllers/registrations_controller.rb

class RegistrationsController < ApplicationController skip_before_action :authenticate!
def new render inertia: {} end
def create user = User.new(user_params)
if user.save
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Welcome!'
else
  redirect_to register_path, inertia: { errors: user.errors }
end
end
private
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end
undefined
class RegistrationsController < ApplicationController skip_before_action :authenticate!
def new render inertia: {} end
def create user = User.new(user_params)
if user.save
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Welcome!'
else
  redirect_to register_path, inertia: { errors: user.errors }
end
end
private
def user_params params.require(:user).permit(:name, :email, :password, :password_confirmation) end end
undefined

Password Reset Flow

密码重置流程

ruby
undefined
ruby
undefined

app/controllers/password_resets_controller.rb

app/controllers/password_resets_controller.rb

class PasswordResetsController < ApplicationController skip_before_action :authenticate!
def new render inertia: {} end
def create user = User.find_by(email: params[:email]) user&.send_password_reset_email
# Always show success to prevent email enumeration
redirect_to login_path, notice: 'Check your email for reset instructions'
end
def edit user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid?
  render inertia: { token: params[:token] }
else
  redirect_to login_path, alert: 'Invalid or expired reset link'
end
end
def update user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid? && user.update(password_params)
  user.clear_password_reset!
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Password updated!'
else
  redirect_to edit_password_reset_path(token: params[:token]),
    inertia: { errors: user&.errors || { token: 'Invalid token' } }
end
end
private
def password_params params.permit(:password, :password_confirmation) end end
undefined
class PasswordResetsController < ApplicationController skip_before_action :authenticate!
def new render inertia: {} end
def create user = User.find_by(email: params[:email]) user&.send_password_reset_email
# 始终显示成功提示,防止邮箱枚举
redirect_to login_path, notice: 'Check your email for reset instructions'
end
def edit user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid?
  render inertia: { token: params[:token] }
else
  redirect_to login_path, alert: 'Invalid or expired reset link'
end
end
def update user = User.find_by(password_reset_token: params[:token])
if user&.password_reset_valid? && user.update(password_params)
  user.clear_password_reset!
  session[:user_id] = user.id
  redirect_to dashboard_path, notice: 'Password updated!'
else
  redirect_to edit_password_reset_path(token: params[:token]),
    inertia: { errors: user&.errors || { token: 'Invalid token' } }
end
end
private
def password_params params.permit(:password, :password_confirmation) end end
undefined

Best Practices

最佳实践

1. Never Trust Client-Side Auth Checks

1. 绝不信任客户端的认证检查

Always verify permissions server-side:
ruby
def destroy
  user = User.find(params[:id])
  authorize user  # Pundit/ActionPolicy check

  user.destroy
  redirect_to users_path
end
始终在服务器端验证权限:
ruby
def destroy
  user = User.find(params[:id])
  authorize user  # Pundit/ActionPolicy 检查

  user.destroy
  redirect_to users_path
end

2. Minimize Exposed User Data

2. 最小化暴露的用户数据

ruby
undefined
ruby
undefined

Bad

不良示例

inertia_share auth: { user: current_user }
inertia_share auth: { user: current_user }

Good

良好示例

inertia_share auth: { user: current_user&.as_json(only: [:id, :name, :email]) }
undefined
inertia_share auth: { user: current_user&.as_json(only: [:id, :name, :email]) }
undefined

3. Use Secure Session Configuration

3. 使用安全的会话配置

ruby
undefined
ruby
undefined

config/initializers/session_store.rb

config/initializers/session_store.rb

Rails.application.config.session_store :cookie_store, key: '_myapp_session', secure: Rails.env.production?, httponly: true, same_site: :lax
undefined
Rails.application.config.session_store :cookie_store, key: '_myapp_session', secure: Rails.env.production?, httponly: true, same_site: :lax
undefined

4. Handle Session Expiry Gracefully

4. 优雅处理会话过期

javascript
// Check auth state on each navigation
router.on('navigate', () => {
  const { auth } = usePage().props
  if (!auth.signed_in && requiresAuth(page.component)) {
    router.visit('/login')
  }
})
javascript
// 在每次导航时检查认证状态
router.on('navigate', () => {
  const { auth } = usePage().props
  if (!auth.signed_in && requiresAuth(page.component)) {
    router.visit('/login')
  }
})

5. Implement Rate Limiting

5. 实现速率限制

ruby
undefined
ruby
undefined

Gemfile

Gemfile

gem 'rack-attack'
gem 'rack-attack'

config/initializers/rack_attack.rb

config/initializers/rack_attack.rb

Rack::Attack.throttle('login attempts', limit: 5, period: 60) do |req| req.ip if req.path == '/login' && req.post? end
undefined
Rack::Attack.throttle('login attempts', limit: 5, period: 60) do |req| req.ip if req.path == '/login' && req.post? end
undefined