require_authentication
dưới dạng before_action
cho tất cả action trong controller. Để miễn một action khỏi kiểm tra xác thực, ta dùng allow_unauthenticated_access
.authenticated?
, có thể dùng trong view để kiểm tra trạng thái đăng nhập, thay thế dần cho pattern if current_user
.Để truy cập thông tin user trong view, bạn cần expose đối tượng user qua helper_method thủ công.
sessions
trên server, chỉ gửi session_id
trong cookie phía client:Current
Current
trong Rails 8 kế thừa từ ActiveSupport::CurrentAttributes
, là một singleton kiểu thread-safe, giữ trạng thái phiên và user trong suốt vòng đời một request.Current
không bị rò rỉ giữa các yêu cầu khác nhau, cũng như đa luồng. Module Authentication thiết lập Current.session
và Current.user
để truy cập toàn cục dễ dàng mà không cần truyền tham số thủ công.Thành phần | Chức năng |
---|---|
Model User | Quan hệ one-to-many với sessions , dùng has_secure_password với bcrypt . |
Model Session | Lưu dữ liệu session, quan hệ belongs_to :user . |
Model Current | Quản lý bối cảnh request (không dùng bảng DB). |
Controllers | SessionsController , PasswordsController để xử lý đăng nhập, đặt lại mật khẩu... |
Concern Authentication | Xử lý logic xác thực và cookies session. |
UsersController
để đăng ký (sign up), bạn cần viết thêm.Đọc kỹ module Authentication concern giúp hiểu rõ cáchUser
,Session
vàCurrent
phối hợp tạo nên hệ thống xác thực.
rack-cors
vào Gemfile:gem 'rack-cors'
bundle install
, sau đó tạo file config/initializers/cors.rb
:Rails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins Rails.credentials.trusted_origins resource "*", headers: :any, methods: %i[get post put patch delete options head], credentials: true endend
trusted_origins
trong credentials cho cả dev và prod.ApplicationController
class ApplicationController < ActionController::Base protect_from_forgery with: :exception
private
def verified_request? origins = Rails.application.credentials.trusted_origins || [] valid = super || origins.include?(request.origin) Rails.logger.warn("Blocked CSRF request from #{request.origin}") unless valid valid endend
Api::UsersController
class Api::UsersController < ApplicationController allow_unauthenticated_access only: %i[create]
def create @user = User.new(user_params) if @user.save render json: { user: @user, notice: "User created successfully" } else render json: { errors: @user.errors.full_messages, status: :unprocessable_entity } end end
private
def user_params params.require(:user).permit(:email_address, :password, :password_confirmation) endend
Api::SessionsController
class Api::SessionsController < ApplicationController allow_unauthenticated_access only: %i[create]
def create if user = User.authenticate_by(params.permit(:email_address, :password)) start_new_session_for user render json: { user: user, authenticated: true, notice: "Successfully logged in" } else render json: { error: "Invalid email or password" }, status: :unauthorized end end
def show if Current.session.user render json: { authenticated: true, user: Current.session.user } else render json: { authenticated: false, error: "Not logged in" }, status: :unauthorized end end
def destroy begin terminate_session render json: { notice: 'successfully logged out' } rescue => e render json: { error: "Failed to log out: #{e.message}" }, status: :internal_server_error end endend
config/routes.rb
:namespace :api do resource :session, only: [:create, :show, :destroy] resource :user, only: [:create]end
// vite.config.jsexport default { server: { proxy: { '/api': 'http://localhost:3000' } }}
package.json
:"proxy": "http://localhost:3000"
AuthForm.jsx
quản lý cả đăng nhập và đăng ký.fetch("/api/user", { method: "POST", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ user: { email_address: e.target.email_address.value, password: e.target.password.value, password_confirmation: e.target.password_confirmation.value, }, }),}).then(async res => { const data = await res.json(); if (res.ok) { setIsSignUp(false); showAlert(data.notice); } else { showAlert(data.errors ? data.errors.join(", ") : "Sign up failed"); }}).catch(err => { showAlert(err.message || "Sign up failed");});
fetch("/api/session", { method: "POST", credentials: "include", headers: { "Content-Type": "application/json" }, body: JSON.stringify({ email_address: e.target.email_address.value, password: e.target.password.value, }),}).then(async res => { const data = await res.json(); if (res.ok) { handleSignIn(data); showAlert(data.notice); } else { showAlert(data.error || "Sign in failed"); }}).catch(err => { showAlert(err.message || "Sign in failed");});
useEffect(() => { fetch('/api/session', { credentials: 'include' }) .then(async res => { const data = await res.json(); if (res.ok) { setAuthChecked(true); setUser(data.user); setLoggedIn(data.authenticated); } else { setLoggedIn(false); setAuthChecked(true); showAlert(data.error || "Authentication failed"); } }) .catch(() => setAuthChecked(true));}, []);
fetch('/api/session', { method: 'DELETE' }) .then(async res => { const data = await res.json(); if (res.ok) { showAlert(data.notice); setUser(null); setLoggedIn(false); } else { showAlert(data.errors ? data.errors.join(', ') : "Log out failed"); } });
Chú ýcredentials: 'include'
để trình duyệt gửi và nhận cookies xác thực.
trusted_origins
đúng cho môi trường phát triển và sản xuất.RegistrationsController
nếu muốn mở rộng tính năng đăng ký.Current
giúp quản lý phiên cũng như user context hiệu quả.