| name | Wheels Auth Generator |
| description | Generate authentication system with user model, sessions controller, and password hashing. Use when implementing user authentication, login/logout, or session management. Provides secure authentication patterns and bcrypt support. |
Wheels Auth Generator
When to Use This Skill
Activate when:
- User requests authentication/login system
- User wants user registration
- User mentions: auth, login, logout, session, password, signup
User Model with Authentication
component extends="Model" {
function config() {
validatesPresenceOf(property="email,password");
validatesUniquenessOf(property="email");
validatesFormatOf(
property="email",
regEx="^[A-Z0-9._%+-]+@[A-Z0-9.-]+\.[A-Z]{2,}$"
);
validatesLengthOf(property="password", minimum=8);
validatesConfirmationOf(property="password");
beforeSave("hashPassword");
}
private function hashPassword() {
if (structKeyExists(this, "password") && len(this.password) && !isHashed(this.password)) {
this.password = hash(this.password, "SHA-512");
}
}
private boolean function isHashed(required string password) {
return len(arguments.password) == 128;
}
public any function authenticate(required string email, required string password) {
var user = this.findOne(where="email = '#arguments.email#'");
if (!isObject(user)) return false;
var hashedAttempt = hash(arguments.password, "SHA-512");
return (user.password == hashedAttempt) ? user : false;
}
}
Sessions Controller
component extends="Controller" {
function new() {
// Show login form
}
function create() {
var user = model("User").authenticate(
email=params.email,
password=params.password
);
if (isObject(user)) {
session.userId = user.id;
flashInsert(success="Welcome back!");
redirectTo(controller="home", action="index");
} else {
flashInsert(error="Invalid email or password");
renderPage(action="new");
}
}
function delete() {
structDelete(session, "userId");
flashInsert(success="You have been logged out");
redirectTo(controller="home", action="index");
}
}
Authentication Filter
// In any controller requiring authentication
function config() {
filters(through="requireAuth");
}
private function requireAuth() {
if (!structKeyExists(session, "userId")) {
flashInsert(error="Please log in");
redirectTo(controller="sessions", action="new");
}
}
Password Reset (Security Best Practices)
PasswordResets Controller
component extends="Controller" {
/**
* Process password reset request
* SECURITY: Always show same message regardless of email existence
*/
function create() {
if (!structKeyExists(params, "email") || !len(trim(params.email))) {
flashInsert(error="Please provide your email address.");
renderView(action="new");
return;
}
// Find user by email
user = model("User").findOne(where="email = '#params.email#' AND deletedAt IS NULL");
// CRITICAL: Always show success message (prevents email enumeration)
if (isObject(user)) {
user.generateResetToken();
user.save();
// TODO: Send email with reset link
}
// Same message whether email exists or not
flashInsert(success="If that email address is in our system, we've sent password reset instructions.");
redirectTo(controller="sessions", action="new");
}
/**
* Show password reset form - validates token
*/
function edit() {
if (!structKeyExists(params, "token") || !len(trim(params.token))) {
flashInsert(error="Invalid password reset link.");
redirectTo(controller="sessions", action="new");
return;
}
user = model("User").findOne(where="resetToken = '#params.token#' AND deletedAt IS NULL");
if (!isObject(user)) {
flashInsert(error="Invalid or expired password reset link.");
redirectTo(controller="sessions", action="new");
return;
}
// Check token expiry (1 hour)
if (!user.isResetTokenValid()) {
flashInsert(error="This password reset link has expired. Please request a new one.");
redirectTo(controller="passwordResets", action="new");
return;
}
token = params.token;
}
/**
* Process password reset - single-use token
*/
function update() {
// Validate token and update password
user = model("User").findOne(where="resetToken = '#params.token#' AND deletedAt IS NULL");
if (!isObject(user) || !user.isResetTokenValid()) {
flashInsert(error="Invalid or expired password reset link.");
redirectTo(controller="sessions", action="new");
return;
}
user.password = params.password;
user.passwordConfirmation = params.passwordConfirmation;
user.clearResetToken(); // Single-use token
if (user.save()) {
// Auto-login after successful reset
session.userId = user.id;
session.userEmail = user.email;
flashInsert(success="Your password has been reset successfully!");
redirectTo(controller="home", action="index");
}
}
}
User Model Token Methods
// Add to User model config()
function config() {
// ... other config ...
beforeCreate("setDefaults");
}
/**
* Generate password reset token (1-hour expiry)
*/
public void function generateResetToken() {
this.resetToken = hash(createUUID() & now(), "SHA-256");
this.resetTokenExpiry = dateAdd("h", 1, now());
}
/**
* Check if reset token is valid (not expired)
*/
public boolean function isResetTokenValid() {
if (!structKeyExists(this, "resetToken") || !len(this.resetToken)) {
return false;
}
if (!structKeyExists(this, "resetTokenExpiry")) {
return false;
}
return dateCompare(now(), this.resetTokenExpiry) < 0;
}
/**
* Clear reset token after use (single-use)
*/
public void function clearResetToken() {
this.resetToken = "";
this.resetTokenExpiry = "";
}
Security Checklist for Password Reset
- ✅ Email enumeration prevention: Always show same success message
- ✅ Token expiry: Limit token validity (1 hour recommended)
- ✅ Single-use tokens: Clear token after successful reset
- ✅ Auto-login: Log user in after successful reset for better UX
- ✅ Secure token generation: Use cryptographic hash with UUID + timestamp
- ✅ HTTPS only: Password reset links should only work over HTTPS in production
Generated by: Wheels Auth Generator Skill v1.0