Skip to main content

kemal-auth

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

Quellinformationen

Repository
kemalcr/kemal
Letzte Quellaktivität
21. August 2026 um 14:44
Erkannte Sprache von SKILL.md
Englisch
Sterne
3.931
Forks
206

Installationsoptionen

Standardmäßig ist der Prompt ausgewählt, der zuerst die Quelle prüft. Sie können zu einem direkten Befehl wechseln oder eine lokale Kopie herunterladen.

Quelldateien prüfen

Lesen Sie SKILL.md und alle von SkillsMP angezeigten Begleitdateien, bevor Sie sich für eine Installation entscheiden.

SKILL.md wird angezeigt

SKILL.md
Quellanweisungen · Schreibgeschützte Vorschau
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.
Auf GitHub ansehen