Data & Auth

Authentication

How mock authentication works, demo users and roles, and how to connect a real auth backend.

How Mock Auth Works

Haze Dashboard uses a Zustand store (src/stores/auth.ts) for mock authentication. There is no real backend or database — authentication is simulated entirely in the browser. The store maintains the current user, computes their role-based permissions, and provides login/logout methods.

On initial load, the store automatically logs in as the Admin user for development convenience. You can change this by modifying the initial state in the store.

Auth Store API

The auth store exposes the following reactive state and methods:

'use client'
import { useAuthStore } from '@/stores/auth'

const user = useAuthStore(s => s.user)            // Current user object (or null)
const isAuthenticated = useAuthStore(s => s.isAuthenticated)
const permissions = useAuthStore(s => s.permissions)

// Methods
const login = useAuthStore(s => s.login)
const logout = useAuthStore(s => s.logout)
const hasPermission = useAuthStore(s => s.hasPermission)

hasPermission('edit_orders')        // Check a specific permission
login('admin@haze.dev')             // Log in by email (matches demo users)
logout()                             // Clear the current user

Demo Users

Three demo users are defined in src/lib/permissions.ts:

NameEmailRoleCapabilities
Alex Johnsonadmin@haze.devAdminAll permissions (view, create, edit, delete; manage roles)
Sarah Cheneditor@haze.devEditorView + create + edit (no delete, no role management)
Marcus Webbviewer@haze.devViewerView only (no create, edit, or delete)

Roles & Permissions

Permissions follow the pattern action_resource (e.g., view_orders, create_products, delete_customers). The full permission matrix:

// src/lib/permissions.ts
export const rolePermissions: Record<string, string[]> = {
  admin: [
    'view_orders', 'create_orders', 'edit_orders', 'delete_orders',
    'view_products', 'create_products', 'edit_products', 'delete_products',
    'view_customers', 'create_customers', 'edit_customers', 'delete_customers',
    'view_invoices', 'create_invoices', 'edit_invoices', 'delete_invoices',
    'view_users', 'create_users', 'edit_users', 'delete_users',
    'manage_roles',
  ],
  editor: [
    'view_orders', 'create_orders', 'edit_orders',
    'view_products', 'create_products', 'edit_products',
    'view_customers', 'create_customers', 'edit_customers',
    'view_invoices', 'create_invoices', 'edit_invoices',
    'view_users',
  ],
  viewer: [
    'view_orders', 'view_products', 'view_customers',
    'view_invoices', 'view_users',
  ],
}

Role-Based UI

Use the auth store's hasPermission()method to conditionally show or hide UI elements based on the user's role:

'use client'
import { useAuthStore } from '@/stores/auth'
import { Button } from '@/components/ui/button'
import { Plus } from 'lucide-react'

export function OrdersHeader() {
  const hasPermission = useAuthStore(s => s.hasPermission)

  return (
    <div className="flex items-center justify-between">
      <h1>Orders</h1>
      {hasPermission('create_orders') && (
        <Button>
          <Plus className="size-4 mr-1" /> New Order
        </Button>
      )}
    </div>
  )
}

This pattern is used throughout the template to hide create/edit/delete buttons for users without the required permissions.

Route Protection (Middleware)

Protect routes with Next.js middleware. Create src/middleware.ts that checks for an auth token cookie and redirects unauthenticated users:

// src/middleware.ts
import { NextResponse, NextRequest } from 'next/server'

const protectedRoutes = ['/dashboard', '/orders', '/products', '/settings']

export function middleware(request: NextRequest) {
  const { pathname } = request.nextUrl
  const isProtected = protectedRoutes.some(p => pathname.startsWith(p))
  const token = request.cookies.get('auth-token')?.value

  if (isProtected && !token) {
    const url = request.nextUrl.clone()
    url.pathname = '/auth/login'
    return NextResponse.redirect(url)
  }

  return NextResponse.next()
}

export const config = {
  matcher: ['/((?!api|_next/static|_next/image|favicon.ico).*)'],
}

Connecting Real Authentication

To replace the mock auth with a real backend, you will need to modify:

  • 1. Auth store— Replace the in-memory user with API calls. The login() method should POST credentials to your auth endpoint and store the returned token/session.
  • 2. Middleware— Check for a valid token/session instead of the mock isAuthenticated flag. Redirect to login on 401 responses.
  • 3. Permissions— Fetch the user's permissions from your backend instead of using the static rolePermissions map.
  • 4. Login/Register pages — Connect the form submissions to your real auth API endpoints.

Example: Real Login Implementation

Here is how the auth store login method might look when connected to a real API:

// Modified auth store with real API
import { create } from 'zustand'

export const useAuthStore = create((set) => ({
  user: null,
  token: null,

  async login(email: string, password: string) {
    const res = await fetch('/api/auth/login', {
      method: 'POST',
      headers: { 'Content-Type': 'application/json' },
      body: JSON.stringify({ email, password }),
    })
    if (!res.ok) return false
    const data = await res.json()
    document.cookie = `auth-token=${data.token}; path=/; max-age=86400`
    set({ user: data.user, token: data.token })
    return true
  },

  async logout() {
    await fetch('/api/auth/logout', { method: 'POST' })
    document.cookie = 'auth-token=; path=/; max-age=0'
    set({ user: null, token: null })
  },

  async fetchUser() {
    const res = await fetch('/api/auth/me')
    if (res.ok) set({ user: await res.json() })
  },
}))

Tip

For SSR-compatible authentication, store the auth token in an httpOnly cookie set by your API. Cookies are sent automatically with every request, so server components and middleware can verify auth without extra fetches.

Next Steps

See the Mock API guide for the data layer, or explore Components for the shared component reference.