Authentication
:Current
cho từng request.require_authentication
trừ khi được phép truy cập công khai qua allow_unauthenticated_access
. Module cũng cung cấp helper authenticated?
để kiểm tra trạng thái đăng nhập dễ dàng hơn trong views, thay thế cách dùng if current_user
.Để truy cập trực tiếp user object trong view, bạn cần exposeCurrent.user
bằnghelper_method
trong controller.
user_id
) trên bảng sessions
trong cơ sở dữ liệu.Ưu điểm | Mô Tả |
---|---|
Bảo mật hơn | Dữ liệu người dùng lưu server, cookie client chỉ chứa session_id |
Hỗ trợ đa thiết bị | Một user có thể có nhiều session trên các thiết bị khác nhau |
Lưu thêm thông tin | Ghi lại user agent, IP,... trong bản ghi session |
start_new_session_for(user)
của Authentication module với các thuộc tính: httponly: true
, same_site: :lax
, secure
(production).Current
Current
kế thừa ActiveSupport::CurrentAttributes
để lưu trữ thông tin session & user một cách thread-safe trong vòng đời 1 request HTTP.Current.session
chứa session hiện tại.Current.user
là user được authenticated tương ứng (được ủy quyền từ session).User
với has_secure_password
(bcrypt).Session
quản lý phiên làm việc.Authentication
và controller Passwords
, Sessions
.Cần tự tạo controllers và views cho việc đăng ký (sign up) và quản lý users vì generator không tạo sẵn.
rack-cors
để cho phép React app (đang chạy ở origin khác) truy cập API Rails:# config/initializers/cors.rbRails.application.config.middleware.insert_before 0, Rack::Cors do allow do origins Rails.credentials.trusted_origins resource '*', headers: :any, methods: [:get, :post, :put, :patch, :delete, :options, :head], credentials: true endend
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
cho đăng ký user.Api::SessionsController
quản lý phiên đăng nhập.class Api::UsersController < ApplicationController allow_unauthenticated_access only: [: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
class Api::SessionsController < ApplicationController allow_unauthenticated_access only: [: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
namespace :api do resource :session, only: [:create, :show, :destroy] resource :user, only: [:create]end
vite.config.js
:export default { server: { proxy: { '/api': 'http://localhost:3000' } }}
package.json
(Create React App):"proxy": "http://localhost:3000"
AuthForm.jsx
xử lý đăng ký và đăng nhập: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 error"))
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 error"))
App.jsx
mount hoặc reload trang, dùng useEffect
gọi: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(data.authenticated) 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") } })
Lưu ý: Tham sốcredentials: 'include'
rất quan trọng để đảm bảo cookie session được gửi kèm theo request.
trusted_origins
chính xác cho môi trường dev và prod.cookie domain
đúng.Current
. Việc tích hợp với frontend React qua API rất dễ dàng và an toàn nếu bạn làm đúng các bước cấu hình.Bạn có thể tham khảo thêm tài liệu chính thức của Rails và giữ liên hệ để trao đổi các cải tiến về code!