Wheels Auth Generator

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.

$ Installieren

git clone https://github.com/wheels-dev/wheels /tmp/wheels && cp -r /tmp/wheels/.claude/skills/wheels-auth-generator ~/.claude/skills/wheels

// tip: Run this command in your terminal to install the skill


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