You signed in with another tab or window. Reload to refresh your session.You signed out in another tab or window. Reload to refresh your session.You switched accounts on another tab or window. Reload to refresh your session.Dismiss alert
{{ message }}
Repository navigation
feature: wheels generate auth — session auth scaffold on the existing wheels.auth primitives #3155
Classified as framework-design (API/feature design question).
Cross-framework research will follow shortly — Rails, Laravel, Django,
and other modern frameworks will be consulted before any path forward
is proposed.
Two-command split (server vs UI); per-flow config toggles
Owned pages; crypto stays in lib
Prints migration fields; --force re-run + git-diff
+1 — RedwoodJS: modern "generate the code you own" session/cookie auth; adds a server-setup vs UI-gen split and per-flow config toggles while keeping crypto in the framework.
Dominant pattern
Four of six (Rails 8, Phoenix, Breeze, Redwood) converge on "scaffold editable code you own, no upgrades" and agree on the shape: a User model with a hashed-password field + authenticate(); a session resource controller (login = create a session, logout = destroy it); a separate passwords controller splitting reset into request (new/create) and confirm (edit/update); server views; a migration; an injected route block. Phoenix makes generated specs first-class — exactly Wheels' requirement. Django/Spring are the counter-model (library, central CVE patching, nothing generated). Divergences: registration (Rails omits; Phoenix/Breeze/Redwood include) and session storage (Rails/Phoenix use a DB table; others use session/cookie scope).
Recommended path for Wheels
Adopt the Phoenix/Rails shape — it maps ~1:1 onto existing primitives, so the generator is pure glue, not new framework infrastructure. SessionStrategy already gives login()/logout()/currentUser() with sessionRotate() fixation defense. Emit plural-PascalCase controllers (Sessions, Passwords, Registrations), a singular User model, server views, a migration, generated specs; inject routes + config/services.cfm strategy wiring. --strategy= selects the registered strategy; registration defaults on (3 of 4 include it) via --no-registration. This promotes the existing wheels generate snippets auth output (already super.config() + injection-safe builder) into a fully-wired scaffold.
// cli/lucli/Module.cfc — new case under generate():
// wheels generate auth [ModelName=User] [--strategy=session|token|jwt]
// [--registration | --no-registration] [--force]
case "auth": return generateAuth(remaining);
// app/controllers/Sessions.cfc — login is a "session" you create and destroy
component extends="Controller" {
function config() {
super.config(); // #2960: inherit CSRF
verifies(only="create", params="user"); // all-named args (anti-pattern #1)
}
function new() {}
function create() { // injection-safe builder, never a where string
var user = model("User").where("email", params.user.email).first();
if (IsObject(user) && user.authenticate(params.user.password)) {
service("authenticator").getStrategy("session")
.login(principal = { id = user.key(), email = user.email });
redirectTo(route="root");
} else {
flashInsert(error="Invalid email or password.");
renderView(action="new");
}
}
function delete() {
service("authenticator").getStrategy("session").logout();
redirectTo(route="login");
}
}
// routes injected into config/routes.cfm:
// .get(name="login", to="sessions##new").post(name="signIn", to="sessions##create")
// .delete(name="logout", to="sessions##delete").resources(name="passwords", only="new,create,edit,update")
Lives in: cli/lucli/Module.cfc (dispatch) + cli/lucli/services/Scaffold.cfc (orchestration); templates under cli/lucli/templates/auth/. Composes with the Authenticator strategy registry, AuthMiddleware, the chainable query builder (where().first()), and model beforeSave password hashing.
Cross-engine notes
The generator runs on the bundled Lucee CLI, but emitted templates run on every engine — keep them clean: super.config() (#2960), all-named args, private filters, CURRENT_TIMESTAMP (not NOW()), injection-safe where(). Two traps: (1)services.cfm must hoist any SessionStrategy(onLogin=...) closure out of the constructor call — an inline closure as a named constructor arg throws ArrayStoreException: ASTcffunction on Adobe CF and can drop a TestBox bundle (Cross-Engine Invariant #5); (2) bcrypt isn't uniformly built-in across Lucee/Adobe/BoxLang, so the generated hashing needs an all-engine approach (or a declared dependency).
Open questions
Registration default — Rails omits; Phoenix/Breeze/Redwood include. Proposed: on by default, --no-registration to opt out. Confirm.
--strategy= semantics — wire only the selected strategy, or scaffold session + emit token/jwt commented? Token/JWT are header-based — do they get views?
Upgrade policy — all four scaffolders freeze generated code. Confirm a stamped header + "re-run --force and git-diff", no framework patching.
Session storage — CFML-session-scope now, or an optional DB sessions table (per-device logout) behind a flag?
Password hashing — which hash works across Lucee 5/6/7, Adobe 2018-2025, BoxLang?
Confidence: medium
Dominant pattern is unambiguous and maps cleanly onto existing primitives, but the medium downgrade fired: I adapted two conventions (singular-resource routing -> Wheels named routes; DB-tracked sessions -> session-scope), the +1 (Redwood's two-command split) diverges non-trivially, and registration-default is a real Rails-vs-rest split — not pristine high; the buildable core spec is clear, so not low.
The proposed fix touches a sensitive area (generated password/token auth code with an unresolved cross-engine hashing decision) and the bot's safety net requires a human in the loop before any code is written.
Specifically, the research-recommended path would have the new wheels generate auth scaffold emit a User model and sessions/passwords controllers that hash and verify passwords and mint reset tokens — and those templates run on every supported engine. The research comment itself flags that the core security primitive is unresolved:
bcrypt isn't uniformly built-in across Lucee/Adobe/BoxLang, so the generated hashing needs an all-engine approach (or a declared dependency).
That hits two stop-triggers at once: password/token code and a cross-engine concern requiring different code paths. The research landed at confidence medium with five open maintainer design calls still outstanding — registration default, --strategy= semantics, generated-code upgrade policy, session storage (session-scope vs DB table), and the password-hashing algorithm itself. The issue body names three of these as "Design calls needing the maintainer."
A maintainer should settle at minimum the password-hashing approach (which API is correct and uniform across Lucee 5/6/7, Adobe 2018–2025, and BoxLang) and the registration / --strategy= / upgrade-policy calls before the scaffold templates are authored. Once those are decided, this is good to proceed as a normal TDD fix.
Activity
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Triage
Classified as framework-design (API/feature design question).
Cross-framework research will follow shortly — Rails, Laravel, Django,
and other modern frameworks will be consulted before any path forward
is proposed.
<!-- wheels-bot:triage:3155 -->
<!-- wheels-bot:triage-class:framework-design -->
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Cross-Framework Research
Question
Add a
wheels generate authCLI command that scaffolds session-based authentication over the existingvendor/wheels/authprimitives (Authenticator,SessionStrategy/TokenStrategy/JwtStrategy,AuthMiddleware) — emitting aUsermodel, sessions + passwords controllers, views, routes, migrations, and generated specs as editable "code you own." Hard requirements:super.config()in generated controllers (#2960 CSRF), all-named args, generated specs. Maintainer design calls: registration on/off flag,--strategy=selector, generated-code upgrade policy.How other frameworks handle this
generate authentication; Sessions/Passwords controllers,Current,Authenticationconcern, DBSessionhas_secure_passwordbreeze:install;AuthenticatedSessionController,PasswordReset*,RegisteredUserroutes/auth.php+ views; thin controller +LoginRequest--no-registration; reuses defaultUserLoginView/PasswordResetView, swappableAUTH_USER_MODELcontrib.auth+ URL includes; customize via subclassphx.gen.authphx.gen.auth Accounts User users;UserAuthplug,User*Controller, generated testsusers_tokens= 3 token kinds; pw change kills sessionsSecurityFilterChain,UserDetailsService,BCryptPasswordEncoderformLogin()); nothing generated/login; no registration/resetsetup auth dbAuth+generate dbAuth;DbAuthHandler, Login/Signup/Forgot/Reset pages--forcere-run + git-diff+1 — RedwoodJS: modern "generate the code you own" session/cookie auth; adds a server-setup vs UI-gen split and per-flow config toggles while keeping crypto in the framework.
Dominant pattern
Four of six (Rails 8, Phoenix, Breeze, Redwood) converge on "scaffold editable code you own, no upgrades" and agree on the shape: a
Usermodel with a hashed-password field +authenticate(); a session resource controller (login = create a session, logout = destroy it); a separate passwords controller splitting reset into request (new/create) and confirm (edit/update); server views; a migration; an injected route block. Phoenix makes generated specs first-class — exactly Wheels' requirement. Django/Spring are the counter-model (library, central CVE patching, nothing generated). Divergences: registration (Rails omits; Phoenix/Breeze/Redwood include) and session storage (Rails/Phoenix use a DB table; others use session/cookie scope).Recommended path for Wheels
Adopt the Phoenix/Rails shape — it maps ~1:1 onto existing primitives, so the generator is pure glue, not new framework infrastructure.
SessionStrategyalready giveslogin()/logout()/currentUser()withsessionRotate()fixation defense. Emit plural-PascalCase controllers (Sessions,Passwords,Registrations), a singularUsermodel, server views, a migration, generated specs; inject routes +config/services.cfmstrategy wiring.--strategy=selects the registered strategy; registration defaults on (3 of 4 include it) via--no-registration. This promotes the existingwheels generate snippets authoutput (alreadysuper.config()+ injection-safe builder) into a fully-wired scaffold.// cli/lucli/Module.cfc — new case under generate(): // wheels generate auth [ModelName=User] [--strategy=session|token|jwt] // [--registration | --no-registration] [--force] case "auth": return generateAuth(remaining); // app/controllers/Sessions.cfc — login is a "session" you create and destroy component extends="Controller" { function config() { super.config(); // #2960: inherit CSRF verifies(only="create", params="user"); // all-named args (anti-pattern #1) } function new() {} function create() { // injection-safe builder, never a where string var user = model("User").where("email", params.user.email).first(); if (IsObject(user) && user.authenticate(params.user.password)) { service("authenticator").getStrategy("session") .login(principal = { id = user.key(), email = user.email }); redirectTo(route="root"); } else { flashInsert(error="Invalid email or password."); renderView(action="new"); } } function delete() { service("authenticator").getStrategy("session").logout(); redirectTo(route="login"); } } // routes injected into config/routes.cfm: // .get(name="login", to="sessions##new").post(name="signIn", to="sessions##create") // .delete(name="logout", to="sessions##delete").resources(name="passwords", only="new,create,edit,update")Lives in:
cli/lucli/Module.cfc(dispatch) +cli/lucli/services/Scaffold.cfc(orchestration); templates undercli/lucli/templates/auth/. Composes with theAuthenticatorstrategy registry,AuthMiddleware, the chainable query builder (where().first()), and modelbeforeSavepassword hashing.Cross-engine notes
The generator runs on the bundled Lucee CLI, but emitted templates run on every engine — keep them clean:
super.config()(#2960), all-named args, private filters,CURRENT_TIMESTAMP(notNOW()), injection-safewhere(). Two traps: (1)services.cfmmust hoist anySessionStrategy(onLogin=...)closure out of the constructor call — an inline closure as a named constructor arg throwsArrayStoreException: ASTcffunctionon Adobe CF and can drop a TestBox bundle (Cross-Engine Invariant #5); (2) bcrypt isn't uniformly built-in across Lucee/Adobe/BoxLang, so the generated hashing needs an all-engine approach (or a declared dependency).Open questions
--no-registrationto opt out. Confirm.--strategy=semantics — wire only the selected strategy, or scaffold session + emit token/jwt commented? Token/JWT are header-based — do they get views?--forceand git-diff", no framework patching.sessionstable (per-device logout) behind a flag?Confidence:
mediumDominant pattern is unambiguous and maps cleanly onto existing primitives, but the medium downgrade fired: I adapted two conventions (singular-resource routing -> Wheels named routes; DB-tracked sessions -> session-scope), the +1 (Redwood's two-command split) diverges non-trivially, and registration-default is a real Rails-vs-rest split — not pristine
high; the buildable core spec is clear, so notlow.Sources
wheels-bot commentedon Jun 12, 2026
Wheels Bot — Fix on hold for human review
The proposed fix touches a sensitive area (generated password/token auth code with an unresolved cross-engine hashing decision) and the bot's safety net requires a human in the loop before any code is written.
Specifically, the research-recommended path would have the new
wheels generate authscaffold emit aUsermodel and sessions/passwords controllers that hash and verify passwords and mint reset tokens — and those templates run on every supported engine. The research comment itself flags that the core security primitive is unresolved:That hits two stop-triggers at once: password/token code and a cross-engine concern requiring different code paths. The research landed at confidence
mediumwith five open maintainer design calls still outstanding — registration default,--strategy=semantics, generated-code upgrade policy, session storage (session-scope vs DB table), and the password-hashing algorithm itself. The issue body names three of these as "Design calls needing the maintainer."A maintainer should settle at minimum the password-hashing approach (which API is correct and uniform across Lucee 5/6/7, Adobe 2018–2025, and BoxLang) and the registration /
--strategy=/ upgrade-policy calls before the scaffold templates are authored. Once those are decided, this is good to proceed as a normal TDD fix.fix(cli): make generate auth Adobe-safe and close the blank-password …
feat(auth): PBKDF2 password hashing service (PasswordHasher) (#3288)
feat(cli): wheels generate auth — session/token/jwt scaffold on the a…