Skip to main content

kemal-auth

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

معلومات المصدر

المستودع
kemalcr/kemal
آخر نشاط في المصدر
٢١ أغسطس ٢٠٢٦ في ١٤:٤٤
لغة SKILL.md المكتشفة
الإنجليزية
النجوم
٣٬٩٢٤
التفرعات
٢٠٦

خيارات التثبيت

يُحدَّد 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