Authentication

Modern, flexible authentication system with passwordless and traditional password flows

Authentication

goilerplate includes a complete, production-ready authentication system that gives your users flexible sign-in options with strong default security.

Overview

The modern auth stack that sells:

  • Passwordless Magic Links - Modern, secure, email-based authentication
  • Traditional Password Auth - Optional password login for users who prefer it
  • Full Password Management - Set, change, or remove passwords anytime
  • Secure by Default - 12+ character passwords and common pattern detection based on NIST recommendations
  • Email Verification - Built-in email verification flow
  • Password Reset - Secure token-based password recovery
  • Email Change - Verified email change with confirmation
  • Progressive Onboarding - Streamlined user experience
  • OAuth Ready - Google and GitHub placeholders (coming soon)
  • No JavaScript Required - Progressive enhancement for core auth flows

User Flows

The primary, recommended authentication method. Zero passwords to remember, maximum security.

How it works:

  1. User enters email on /auth
  2. Receives magic link via email
  3. Clicks link → automatically logged in
  4. New users complete onboarding (name input)
  5. Free subscription created automatically

Benefits:

  • No password to remember or leak
  • Immune to password-based attacks
  • Modern UX (Slack, Notion, Linear use this)
  • Faster signup flow

Password Authentication (Optional)

Traditional email + password login for users who prefer it.

How it works:

  1. User clicks “Sign in with password instead” on /auth
  2. Navigates to /auth/password
  3. Enters email + password → logged in

Password Requirements:

  • Minimum 12 characters (NIST recommendation)
  • No special character requirements (encourages passphrases)
  • Automatic detection of common/weak passwords
  • Validates against patterns: “password”, “123456”, “qwerty”, etc.

Flexible Password Management

Users can switch between passwordless and password-based auth anytime.

Set Password (Passwordless → Password)

  • User starts with magic link account
  • Goes to Settings → Security → Password
  • Sets a password (no current password required)
  • Now can use both magic links AND password to login

Remove Password (Password → Passwordless)

  • User has password-based account
  • Goes to Settings → Security → Password
  • Clicks “Remove Password” with confirmation
  • Returns to passwordless (magic links only)

Change Password

  • Standard password change flow
  • Requires current password verification
  • Same strong password requirements

Technical Implementation

Password Validation

Located in internal/validation/password.go:

func ValidatePassword(password string) error {
    // Minimum 12 characters
    if len(password) < 12 {
        return errors.New("password must be at least 12 characters")
    }

    // Check for common/weak patterns
    lower := strings.ToLower(password)
    commonPatterns := []string{
        "password", "123456", "qwerty", "admin", "letmein",
        "welcome", "monkey", "dragon", "master", "sunshine",
    }

    for _, pattern := range commonPatterns {
        if strings.Contains(lower, pattern) {
            return errors.New("password is too common, please choose a stronger one")
        }
    }

    return nil
}

Note: All input validators (email, password, name, files) are now centralized in the internal/validation package for reusability across services and handlers.

Why this approach:

  • NIST recommends 12-15 character minimum
  • No “must have uppercase + number + symbol” rules (proven to create weaker passwords)
  • Encourages passphrases: “my cat loves pizza 2024” = secure + memorable
  • Simple implementation, no external dependencies

This means the password rules are based on NIST guidance for length-first password policies. It does not mean the product is claiming general NIST compliance.

Magic links are secure, time-limited tokens sent via email.

Token Generation:

  • 32-byte random token (hex-encoded = 64 characters)
  • Stored hashed in database
  • Configurable expiry (default: 15 minutes)
  • Single-use (marked as used after verification)

Security Features:

  • Tokens expire automatically
  • One-time use only
  • Email enumeration protection (always shows success message)
  • Rate limiting ready

Session Management

Uses JWT (JSON Web Tokens) stored in HttpOnly cookies.

JWT Claims:

{
    "user_id": "uuid",
    "email": "[email protected]",
    "exp": "expiry_timestamp",
    "iat": "issued_at_timestamp"
}

Cookie Settings:

  • HttpOnly: true (prevents XSS)
  • Secure: true (production only, HTTPS required)
  • SameSite: Lax (CSRF protection)
  • Default expiry: 7 days

Middleware

Three middleware functions handle auth requirements:

AuthMiddleware

Runs on every request. Checks JWT, loads user/profile/subscription into context.

// internal/middleware/auth.go
func AuthMiddleware(...)

RequireAuth

Protects routes that need authentication. Redirects to /auth if not logged in.

mux.HandleFunc("GET /app/dashboard", middleware.RequireAuth(dashboard.Page))

Handles:

  • Unauthenticated users → redirect to /auth
  • Incomplete onboarding → redirect to /onboarding
  • HTMX requests → HX-Redirect header

RequireGuest

Prevents authenticated users from accessing auth pages.

mux.HandleFunc("GET /auth", middleware.RequireGuest(auth.AuthPage))

Routes

Public Auth Routes

// Primary auth methods
GET  /auth                    → Magic link page
POST /auth/magic-link         → Send magic link
GET  /auth/magic-link/{token} → Verify magic link

GET  /auth/password           → Password login page
POST /auth/password           → Password authentication

// Email verification
GET  /verify/{token}          → Verify email (new accounts)

// Password reset
GET  /forgot-password         → Forgot password page
POST /forgot-password         → Request password reset
GET  /reset-password/{token}  → Reset password page
POST /reset-password          → Set new password

// Onboarding
GET  /onboarding              → Onboarding page (requires auth)
POST /onboarding              → Complete onboarding

// Logout
POST /logout                  → Clear session

Protected Account Routes (Settings)

// Email management
PATCH /app/account/email              → Request email change
GET   /auth/verify-email-change/{token} → Verify new email

// Password management
POST   /app/account/password      → Change password
POST   /app/account/password/set  → Set password (passwordless → password)
DELETE /app/account/password      → Remove password (password → passwordless)

// Account deletion
DELETE /app/account       → Delete account

Services

AuthService

internal/service/auth.go

Key Methods:

  • SendMagicLink(email) - Creates user if needed, sends magic link
  • VerifyMagicLink(token) - Validates token, returns user
  • Login(email, password) - Traditional password login
  • SetPassword(userID, password) - Adds password to passwordless account
  • RemovePassword(userID) - Removes password from account
  • ValidatePassword(password) - Validates password strength
  • SendForgotPasswordLink(email) - Sends magic link that removes password and logs user in
  • GenerateJWT(user) - Creates JWT token
  • VerifyJWT(token) - Validates JWT token

UserService

internal/service/user.go

Key Methods:

  • UpdatePassword(userID, current, new) - Change password (requires current password)
  • DeleteAccount(userID) - Account deletion with safety checks

Email Templates

Authentication emails are sent via Resend using HTML templates:

  • Magic Link - Clean, simple email with secure link
  • Email Verification - Welcome email with verification link
  • Password Reset - Secure reset link with expiry notice
  • Email Change - Confirmation for new email address

Located in internal/service/email.go

Security Best Practices

Password Storage

  • Bcrypt hashing with default cost (10)
  • Passwords stored as nullable field (supports passwordless)
  • Never logged or exposed in responses

Token Security

  • Cryptographically secure random generation
  • Time-limited expiry
  • Single-use enforcement
  • Hashed storage in database

Protection Against

  • Brute Force - Rate limiting ready
  • Email Enumeration - Always returns success messages
  • Password Spray - Minimum 12 character passwords
  • Common Passwords - Pattern detection
  • Session Hijacking - HttpOnly + Secure cookies
  • CSRF - SameSite cookie policy
  • XSS - HttpOnly cookies, CSP headers

Customization

Adjust Token Expiry

In .env:

TOKEN_MAGIC_LINK_EXPIRY=15m        # Magic link validity
TOKEN_EMAIL_VERIFY_EXPIRY=24h      # Email verification
TOKEN_PASSWORD_RESET_EXPIRY=1h     # Password reset
TOKEN_EMAIL_CHANGE_EXPIRY=24h      # Email change
JWT_EXPIRY=168h                    # Session duration (7 days)

Modify Password Requirements

Edit internal/validation/password.go:

func ValidatePassword(password string) error {
    // Change minimum length
    if len(password) < 16 {  // Increase to 16
        return errors.New("password must be at least 16 characters")
    }

    // Add more common patterns
    commonPatterns := append(commonPatterns, "mycompany", "changeme")

    // Add custom validation
    if !hasNumber(password) {
        return errors.New("password must contain a number")
    }

    return nil
}

OAuth Setup (Google & GitHub)

OAuth integration is built into the authentication system - you just need to configure your OAuth apps.

Google OAuth

1. Create OAuth Credentials:

  • Go to Google Cloud Console
  • Create or select a project
  • Click “Create Credentials” → “OAuth 2.0 Client ID”
  • Application type: “Web application”
  • Add authorized redirect URI: {your-app-url}/auth/google/callback
    • Development: http://localhost:8090/auth/google/callback
    • Production: https://yourdomain.com/auth/google/callback

2. Configure Environment Variables:

GOOGLE_CLIENT_ID=your_google_client_id
GOOGLE_CLIENT_SECRET=your_google_client_secret

3. Enable Google+ API:

  • In Google Cloud Console, enable “Google+ API” for your project
  • This allows your app to fetch user profile information

GitHub OAuth

1. Register OAuth App:

  • Go to GitHub Developer Settings
  • Click “New OAuth App”
  • Fill in details:
    • Application name: Your app name
    • Homepage URL: Your app URL
    • Authorization callback URL: {your-app-url}/auth/github/callback
      • Development: http://localhost:8090/auth/github/callback
      • Production: https://yourdomain.com/auth/github/callback

2. Configure Environment Variables:

GITHUB_CLIENT_ID=your_github_client_id
GITHUB_CLIENT_SECRET=your_github_client_secret

Testing OAuth

Set credentials in .env, run task dev, navigate to /auth, and click the OAuth button. For production, set credentials in your hosting environment and update callback URLs to production domain.

OAuth Security

Never commit OAuth credentials to git. Use separate apps for development and production.

Example Usage

Protect routes:

mux.HandleFunc("GET /app/dashboard", middleware.RequireAuth(handler))

Access user in handlers:

user := ctxkeys.User(r.Context())
profile := ctxkeys.Profile(r.Context())
subscription := ctxkeys.Subscription(r.Context())