Skip to main content
Local users authenticate with a username and password stored in Dockhand’s database. This is the simplest authentication method and works without external dependencies.

Creating Users

Via Web UI

  1. Navigate to Settings > Users
  2. Click Add User
  3. Fill in user details:
    • Username - Unique identifier (required)
    • Password - Minimum 8 characters (required)
    • Email - For notifications and password recovery
    • Display Name - Full name or alias
  4. Click Create User

Via API

curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN" \
  -d '{
    "username": "alice",
    "password": "SecureP@ssw0rd123",
    "email": "[email protected]",
    "displayName": "Alice Johnson"
  }'
{
  "id": 2,
  "username": "alice",
  "email": "[email protected]",
  "displayName": "Alice Johnson",
  "isAdmin": false,
  "isActive": true,
  "createdAt": "2026-03-04T10:30:00Z"
}

Password Requirements

Dockhand enforces these password requirements:
  • Minimum Length: 8 characters
  • Recommended: 12+ characters with mixed case, numbers, and symbols
  • Hashing Algorithm: Argon2id with these parameters:
    • Memory cost: 64 MB (65536 KiB)
    • Time cost: 3 iterations
    • Parallelism: 1 thread
    • Hash length: 32 bytes (256 bits)

Password Storage Format

Passwords are stored in PHC format:
$argon2id$v=19$m=65536,t=3,p=1$[salt]$[hash]
This format is compatible with standard Argon2 implementations.

User Management

List Users

curl http://localhost:8000/api/users \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN"
[
  {
    "id": 1,
    "username": "admin",
    "email": "[email protected]",
    "displayName": "System Administrator",
    "mfaEnabled": true,
    "isAdmin": true,
    "isActive": true,
    "isSso": false,
    "authProvider": "local",
    "lastLogin": "2026-03-04T09:15:22Z",
    "createdAt": "2026-01-15T08:00:00Z"
  },
  {
    "id": 2,
    "username": "alice",
    "email": "[email protected]",
    "displayName": "Alice Johnson",
    "mfaEnabled": false,
    "isAdmin": false,
    "isActive": true,
    "isSso": false,
    "authProvider": "local",
    "lastLogin": "2026-03-04T08:45:10Z",
    "createdAt": "2026-02-10T14:20:00Z"
  }
]

Update User

curl -X PATCH http://localhost:8000/api/users/2 \
  -H "Content-Type: application/json" \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN" \
  -d '{
    "email": "[email protected]",
    "displayName": "Alice M. Johnson"
  }'

Change Password

Users can change their own password:
curl -X PATCH http://localhost:8000/api/users/2 \
  -H "Content-Type: application/json" \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN" \
  -d '{
    "currentPassword": "OldP@ssw0rd",
    "password": "NewSecureP@ssw0rd456"
  }'
Admins can reset passwords without knowing the current one:
curl -X PATCH http://localhost:8000/api/users/2 \
  -H "Content-Type: application/json" \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN" \
  -d '{
    "password": "NewP@ssw0rd789"
  }'

Disable User

Disable a user account (preserves data):
curl -X PATCH http://localhost:8000/api/users/2 \
  -H "Content-Type: application/json" \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN" \
  -d '{"isActive": false}'
Disabled users cannot log in but their sessions remain active until they expire.

Delete User

Permanently delete a user account:
curl -X DELETE http://localhost:8000/api/users/2 \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN"
Deleting a user removes all their data including audit logs, preferences, and role assignments. This action cannot be undone.

Login Flow

Basic Login

curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "SecureP@ssw0rd123"
  }'
{
  "success": true,
  "user": {
    "id": 2,
    "username": "alice",
    "email": "[email protected]",
    "displayName": "Alice Johnson",
    "isAdmin": false
  }
}

Login with 2FA

If the user has 2FA enabled, the initial login returns:
{
  "requiresMfa": true
}
Then submit the TOTP code:
curl -X POST http://localhost:8000/api/auth/login \
  -H "Content-Type: application/json" \
  -d '{
    "username": "alice",
    "password": "SecureP@ssw0rd123",
    "mfaToken": "123456"
  }'
See Two-Factor Authentication for details.

Rate Limiting

Dockhand protects against brute force attacks with rate limiting:
  • Threshold: 5 failed attempts per IP + username combination
  • Window: 15 minutes
  • Lockout: 15 minutes after threshold reached
  • Response: HTTP 429 with Retry-After header

Rate Limit Response

{
  "error": "Too many login attempts. Please try again in 847 seconds."
}
Rate limits are stored in-memory and reset on server restart.

Session Management

Session Token

After successful login, Dockhand sets a session cookie:
Set-Cookie: dockhand_session=BASE64URL_TOKEN; 
  Path=/; 
  HttpOnly; 
  Secure; 
  SameSite=Strict; 
  Max-Age=86400
  • Name: dockhand_session
  • Token Format: 32-byte random value, base64url encoded (256 bits entropy)
  • HttpOnly: Prevents JavaScript access (XSS protection)
  • Secure: Only sent over HTTPS (production)
  • SameSite=Strict: Prevents CSRF attacks
  • Max-Age: Configurable (default 24 hours)

Check Session

curl http://localhost:8000/api/auth/session \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN"
{
  "authenticated": true,
  "user": {
    "id": 2,
    "username": "alice",
    "email": "[email protected]",
    "displayName": "Alice Johnson",
    "isAdmin": false
  },
  "expiresAt": "2026-03-05T09:15:22Z"
}

Logout

curl -X POST http://localhost:8000/api/auth/logout \
  -H "Cookie: dockhand_session=YOUR_SESSION_TOKEN"
This deletes the session from the database and clears the cookie.

Security Considerations

Timing Attack Protection

Dockhand prevents username enumeration via timing attacks:
// Always hash a dummy password even if user doesn't exist
if (!user) {
  await hashPassword('dummy');
  return { error: 'Invalid username or password' };
}
This ensures authentication failures take the same time whether the username exists or not.

Password Hash Migration

If you change the Argon2 parameters, existing hashes remain valid. Users are not required to reset passwords.

Session Token Security

Session tokens are:
  • Generated using crypto.randomBytes() (CSPRNG)
  • 32 bytes = 256 bits of entropy
  • Base64url encoded for cookie safety
  • Stored as plain text in database (lookup key)
  • Not encrypted (entropy makes guessing infeasible)

First User Setup

When authentication is enabled but no admin exists, Dockhand allows creating the first user without authentication:
# This works only when no admin users exist
curl -X POST http://localhost:8000/api/users \
  -H "Content-Type: application/json" \
  -d '{
    "username": "admin",
    "password": "ChangeMe123!",
    "email": "[email protected]"
  }'
The first user automatically:
  • Receives the Admin role
  • Can create additional users
  • Is logged in automatically (if authEnabled: true)
This special case is implemented in hooks.server.ts:270:
const noAdminSetupMode = !(await hasAdminUser());
if (noAdminSetupMode && event.url.pathname === '/api/users' && 
    event.request.method === 'POST') {
  return compressResponse(event.request, await resolve(event));
}

Disabling Local Login

For SSO-only deployments, disable local username/password authentication:
docker run -e DISABLE_LOCAL_LOGIN=true dockhand/dockhand:latest
This:
  • Removes “Local” from the login provider list
  • Rejects POST requests to /api/auth/login with provider: local
  • Forces all users to authenticate via OIDC or LDAP
  • Prevents password-based attacks
Keep at least one admin account with a known password as a backup before enabling this setting.

Database Schema

Local users are stored in the users table:
CREATE TABLE users (
  id INTEGER PRIMARY KEY,
  username TEXT UNIQUE NOT NULL,
  email TEXT,
  password_hash TEXT NOT NULL,
  display_name TEXT,
  avatar TEXT,
  auth_provider TEXT DEFAULT 'local',
  mfa_enabled BOOLEAN DEFAULT FALSE,
  mfa_secret TEXT,  -- JSON: {secret, backupCodes}
  is_active BOOLEAN DEFAULT TRUE,
  last_login TEXT,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP,
  updated_at TEXT DEFAULT CURRENT_TIMESTAMP
);
Sessions are stored in the sessions table:
CREATE TABLE sessions (
  id TEXT PRIMARY KEY,
  user_id INTEGER NOT NULL REFERENCES users(id) ON DELETE CASCADE,
  provider TEXT NOT NULL,
  expires_at TEXT NOT NULL,
  created_at TEXT DEFAULT CURRENT_TIMESTAMP
);

Source Code Reference

Key implementation files:
  • src/lib/server/auth.ts - Core authentication logic
  • src/routes/api/auth/login/+server.ts - Login endpoint
  • src/routes/api/users/+server.ts - User management CRUD
  • src/hooks.server.ts - Session validation middleware
  • src/lib/server/authorize.ts - Permission checks

Next Steps

Two-Factor Auth

Add TOTP-based 2FA to user accounts

OIDC/SSO

Integrate with your Identity Provider

RBAC

Configure role-based access control (Enterprise)

LDAP

Connect to Active Directory (Enterprise)

Build docs developers (and LLMs) love