Curated library of atomic skills and personas for Hanami, dry-rb, and ROM Ruby development. Covers actions, slices, repositories, relations, changesets, providers, DI, operations, TDD, CLI, views, routing, validation, and 10 orchestration personas. Shared Ruby process skills have moved to ruby-core-skills. Uses Markdown + Front-matter architecture.
95
95%
Does it follow best practices?
Impact
96%
1.20xAverage score across 45 eval scenarios
Passed
No known issues
Use this workflow when implementing authentication in Hanami 2.x.
Core principle: Authentication is explicit in every Action. No global magic — each Action declares its auth requirements.
inject-dependencies)Deps["authentication"]register-provider)config/providers/authentication.rbstart blockExample — config/providers/authentication.rb:
Hanami.app.register_provider(:authentication) do
start do
require "bcrypt"
register("authentication", MyApp::Authentication::Service.new)
end
endcreate-action)Example — minimal auth service:
# app/authentication/service.rb
module MyApp
module Authentication
class Service
def authenticate(email:, password:, session:)
user = UserRepository.new.find_by_email(email)
return false unless user
return false unless BCrypt::Password.new(user.password_digest) == password
session[:user_id] = user.id
true
end
def current_user(session)
return nil unless session[:user_id]
UserRepository.new.find(session[:user_id])
end
def authenticated?(session)
!current_user(session).nil?
end
end
end
endExample — login Action:
# app/actions/sessions/create.rb
module MyApp
module Actions
module Sessions
class Create < MyApp::Action
include Deps["authentication"]
params do
required(:email).filled(:string)
required(:password).filled(:string)
end
def handle(request, response)
halt 422 unless request.params.valid?
authenticated = authentication.authenticate(
email: request.params[:email],
password: request.params[:password],
session: request.session
)
if authenticated
response.redirect_to "/dashboard"
else
response.status = 401
response.body = { error: "Invalid credentials" }.to_json
end
end
end
end
end
endExample — protected Action:
# app/actions/dashboard/show.rb
module MyApp
module Actions
module Dashboard
class Show < MyApp::Action
include Deps["authentication"]
def handle(request, response)
halt 401 unless authentication.authenticated?(request.session)
user = authentication.current_user(request.session)
response.body = { user: user.email }.to_json
end
end
end
end
endhandle-errors)After each step, verify the workflow is progressing correctly:
# Verify provider is registered
bundle exec hanami console
> Hanami.app["authentication"] # Should return auth service instance
# Test login endpoint
curl -X POST http://localhost:2300/sessions \
-H "Content-Type: application/json" \
-d '{"email":"user@example.com","password":"secret"}'
# Expect: redirect or 200 with session cookie on success; 401 on failure
# Test protected endpoint without auth
curl http://localhost:2300/dashboard
# Expect: 401 Unauthorized
# Test protected endpoint with session cookie
curl http://localhost:2300/dashboard --cookie "<session-cookie>"
# Expect: 200 with user data| Mistake | Reality |
|---|---|
| "I'll put auth in a base controller" | Hanami has no base controller. Each Action must explicitly check auth. |
| "I'll store passwords in plain text" | Always hash passwords with bcrypt or argon2. |
| "I'll skip session security configuration" | Configure secure sessions with http_only, secure, and same_site. |
| "I'll return specific error messages that reveal valid emails" | Return generic "Invalid credentials" for both missing user and wrong password. |
| Auth logic scattered across Actions | Centralise all auth logic in the auth service; Actions only call it. |
.tessl-plugin
evals
scenario-1
scenario-2
scenario-3
scenario-4
scenario-5
scenario-6
scenario-7
scenario-8
scenario-9
scenario-10
scenario-11
scenario-12
scenario-13
scenario-14
scenario-15
scenario-16
scenario-17
scenario-18
scenario-19
scenario-20
scenario-21
scenario-22
scenario-23
scenario-24
scenario-25
scenario-26
scenario-27
scenario-28
scenario-29
scenario-30
scenario-31
scenario-32
scenario-33
scenario-34
scenario-35
scenario-36
scenario-37
scenario-38
scenario-39
scenario-40
scenario-41
scenario-42
scenario-43
scenario-44
scenario-45
skills
actions
build-json-api
create-action
handle-errors
validate-params
context
load-context
db
create-changeset
create-repository
define-relation
write-migration
dry-monads
handle-result-pattern
dry-rb
create-operation
create-validation-contract
personas
providers
configure-providers
implement-di
review-security
routing
define-routes
slices
configure-slice
create-slice
extract-slice
review-slice-boundaries
test-slice