Skip to main content

kemal-auth

User authentication and session management in Kemal, following established project patterns.

소스 정보

저장소
kemalcr/kemal
최근 소스 활동
2026년 8월 21일 14:44
감지된 SKILL.md 언어
영어
스타
3,931
포크
206

설치 방법

기본적으로 소스를 먼저 확인하는 Prompt가 선택됩니다. 직접 명령으로 전환하거나 로컬 사본을 다운로드할 수도 있습니다.

소스 파일 검토

설치 여부를 결정하기 전에 SKILL.md와 SkillsMP에 표시된 보조 파일을 읽어 보세요.

SKILL.md 표시 중

SKILL.md
소스 지침 · 읽기 전용 미리보기
name
kemal-auth
description
User authentication and session management in Kemal, following established project patterns.
license
MIT
# Kemal Authentication & Sessions This skill provides expert guidance on implementing user authentication and session management in Kemal, strictly following patterns from [`kemal-by-example/ecommerce`](https://github.com/sdogruyol/kemal-by-example/tree/master/ecommerce) and [`kemal-by-example/oauth-login`](https://github.com/sdogruyol/kemal-by-example/tree/master/oauth-login). ## Core Mandates - **Dependencies:** Always `require "kemal-session"`. - **Session Configuration:** Use `Kemal::Session.config` to set `secret`, `cookie_name`, and `gc_interval`: ```crystal Kemal::Session.config do |config| config.secret = ENV["KEMAL_SESSION_SECRET"]? || raise "KEMAL_SESSION_SECRET not set" config.cookie_name = "your_app_session" config.gc_interval = 2.minutes end ``` - **Auth Helpers:** Implement auth logic in a module (e.g., `Ecommerce::Auth`): - `current_user(env)`: Use `env.session.bigint?("user_id")` to retrieve the ID and find the user - `require_user(env)`: Call `current_user(env)` and redirect to `/login` if `nil` - `sign_in(env, user)`: Set `env.session.bigint("user_id", user.id || raise "User ID required")` - `sign_out(env)`: Call `env.session.destroy` - **Password Hashing:** Use `Crypto::Bcrypt::Password` for securely storing and authenticating passwords. - **Error Handling:** Use specific exception types (like `DB::Error`) in auth helpers. ```crystal def current_user(env) : User? user_id = env.session.bigint?("user_id") return unless user_id User.find(user_id) rescue DB::Error nil end ``` ## Patterns from Source Code ### Auth Helper Module (ecommerce/src/helpers/auth.cr) ```crystal module Ecommerce module Auth extend self def current_user(env) : User? user_id = env.session.bigint?("user_id") return unless user_id User.find(user_id) rescue DB::Error nil end def require_user(env) : User? user = current_user(env) return user if user env.redirect "/login" nil end def sign_in(env, user : User) user_id = user.id || raise ArgumentError.new("Cannot sign in user without ID") env.session.bigint("user_id", user_id) end def sign_out(env) env.session.destroy end end end ``` ### Session Secret (Explicit Configuration) From ecommerce and oauth-login: ```crystal Kemal::Session.config do |config| config.secret = ENV["KEMAL_SESSION_SECRET"]? || raise "KEMAL_SESSION_SECRET not set" config.cookie_name = "ecommerce_session_id" config.gc_interval = 2.minutes end ``` ### User Model with Bcrypt From ecommerce/src/models/user.cr: ```crystal require "crypto/bcrypt" class User include DB::Serializable getter id : Int64? getter name : String getter email : String getter password_hash : String getter created_at : String getter updated_at : String def self.create(name : String, email : String, password : String) : User now = Time.utc.to_s normalized_email = normalize_email(email) password_hash = Crypto::Bcrypt::Password.create(password, cost: 12).to_s # ... insert and return user end def self.authenticate(email : String, password : String) : User? user = find_by_email(normalize_email(email)) return unless user return user if Crypto::Bcrypt::Password.new(user.password_hash).verify(password) nil end def self.normalize_email(value : String) : String value.strip.downcase end end ``` ### Login Route Pattern ```crystal post "/login" do |env| email = env.params.body["email"]?.try(&.strip) || "" password = env.params.body["password"]?.try(&.strip) || "" user = User.authenticate(email, password) if user Ecommerce::Auth.sign_in(env, user) env.redirect "/products" else current_user = nil cart_count = 0_i64 error_message = "Invalid email or password." env.response.status = :unprocessable_entity render "src/views/auth/login.ecr", "src/views/layouts/application.ecr" end end post "/logout" do |env| Ecommerce::Auth.sign_out(env) env.redirect "/products" end ``` ## Best Practices - **Local Variables for Errors:** Pass error messages as local variables directly to the `render` macro (e.g., `error_message = "Invalid email or password."`). - **Security:** Use `POST` (never `GET`) for login and logout so state changes are not triggerable via simple links — but note that using `POST` alone does **not** prevent CSRF. Add real CSRF protection: validate a per-session CSRF token on state-changing requests (e.g. the [`kemal-csrf`](https://github.com/kemalcr/kemal-csrf) handler) and set session cookies with `SameSite`. Regenerate the session on login to prevent session fixation. - **Session Safe Access:** Use `env.session.bigint?("user_id")` or similar to safely retrieve session data. - **Model Methods:** Implement `User.authenticate(email, password)` and `User.find_by_email(email)` in the model. ## When to Use - When implementing signup, login, or logout features. - When protecting specific routes from unauthorized access. - When managing user-specific state across requests.
GitHub에서 보기