Authentication
Authentication & Permissions
Minnow provides session-based authentication, role-based access control (RBAC), CSRF protection, and application passwords for API access. The system is fully compatible with WordPress user data.
Quick Start
Helper Functions
The most common auth operations are available as simple functions:
use function Minnow\Core\Auth\{
auth, user, userId, authenticated, guest,
attempt, logout, can, hasRole, isAdmin,
session, hashPassword, verifyPassword,
createNonce, verifyNonce, nonceField
};
// Login
if (attempt('user@example.com', 'password123')) {
// Logged in
}
// Check auth state
authenticated(); // true if logged in
guest(); // true if not logged in
user(); // User object or null
userId(); // User ID or null
// Check permissions
can('edit_posts'); // true/false
hasRole('administrator'); // true/false
isAdmin(); // true/false
// Logout
logout();
Authentication
Authenticator
The Authenticator class manages login, logout, and session state:
use Minnow\Core\Auth\Authenticator;
$auth = Authenticator::getInstance();
// Login with credentials (email or username)
$auth->attempt('user@example.com', 'password');
// Login a user directly (skip password check)
$auth->login($user);
// Act as user without session (for CLI/testing)
$auth->actAs($user);
// Check state
$auth->check(); // Is authenticated?
$auth->guest(); // Is guest?
$auth->user(); // Get User object
$auth->id(); // Get user ID
// Check permissions
$auth->can('edit_posts');
$auth->hasRole('editor');
$auth->isAdmin();
// Logout
$auth->logout();
Login Flow
attempt()looks up the user by email or username- Verifies the password against the stored hash
- On success, regenerates the session ID (prevents session fixation)
- Stores the user ID and a random auth token in the session
- If the password uses a legacy WordPress hash, automatically rehashes to bcrypt
Password Formats
Minnow supports three password hash formats for WordPress compatibility:
| Format | Pattern | Description |
|---|---|---|
| WordPress phpass | $P$... or $H$... |
Legacy format, auto-upgraded on login |
| WordPress bcrypt | $wp$2y$... |
WordPress 6.8+ format with HMAC-SHA384 |
| Modern bcrypt | $2y$... |
Default for new passwords |
use function Minnow\Core\Auth\{hashPassword, verifyPassword};
$hash = hashPassword('my-password'); // Modern bcrypt
$valid = verifyPassword('my-password', $hash); // Works with all formats
Sessions
Sessions use secure defaults with PHP's native session handling:
use function Minnow\Core\Auth\session;
$session = session();
$session->set('key', 'value');
$session->get('key'); // 'value'
$session->has('key'); // true
$session->remove('key');
// Flash data (persists for one request)
$session->flash('message', 'Saved!');
$session->getFlash('message'); // 'Saved!' (gone after this request)
Session Configuration
Configured during bootstrap in admin/boot.php:
| Option | Default | Description |
|---|---|---|
name |
minnow_session |
Cookie name |
lifetime |
120 (minutes) |
Session lifetime |
secure |
false |
HTTPS-only cookies |
httponly |
true |
No JavaScript access |
samesite |
Lax |
CSRF protection |
Roles & Capabilities
Minnow uses WordPress-compatible RBAC. Users are assigned roles, and roles grant capabilities.
Built-in Roles
| Role | Capabilities | Description |
|---|---|---|
| Administrator | All 46 capabilities | Full access |
| Editor | 27 capabilities | Manage all content |
| Author | 7 capabilities | Publish own posts |
| Contributor | 3 capabilities | Write drafts |
| Subscriber | 1 capability (read) |
Read-only |
Checking Capabilities
// Via helper functions
can('edit_posts');
can('manage_options');
hasRole('administrator');
isAdmin();
// Via User object
$user = user();
$user->can('edit_posts');
$user->hasRole('editor');
$user->getAllCapabilities();
$user->getRoles();
Managing User Roles
$user = \Minnow\Core\Entity\User::find(1);
$user->assignRole('editor');
$user->removeRole('subscriber');
$user->syncRoles(['editor', 'author']); // Replace all roles
$user->getRoles(); // ['editor', 'author']
Managing Roles
use Minnow\Core\Auth\{Role, RoleManager};
$manager = RoleManager::getInstance();
// List all roles
$roles = $manager->all(); // ['administrator' => Role, 'editor' => Role, ...]
// Create a custom role
$role = new Role('moderator', 'Moderator', [
'read' => true,
'edit_posts' => true,
'moderate_comments' => true,
]);
$manager->create($role);
// Update a role
$role = $manager->get('moderator');
$updated = $role->withCapability('upload_files');
$manager->update($updated);
// Delete a role (cannot delete 'administrator')
$manager->delete('moderator');
// User counts
$manager->getUserCount('editor'); // Number of editors
$manager->getAllUserCounts(); // ['administrator' => 2, 'editor' => 5, ...]
Direct Capability Overrides
Grant or revoke individual capabilities for a specific user, independent of their roles:
$user->setCapability('upload_files', true); // Grant
$user->setCapability('delete_posts', false); // Revoke
$user->removeCapability('upload_files'); // Remove override
$user->getDirectCapabilities(); // Only overrides
Capability Groups
Capabilities are organized into groups for the admin UI:
| Group | Capabilities |
|---|---|
| Users | manage_roles, list_users, create_users, edit_users, delete_users, promote_users |
| Posts | edit_posts, edit_others_posts, publish_posts, delete_posts, delete_others_posts, delete_published_posts, edit_published_posts, read_private_posts |
| Pages | edit_pages, edit_others_pages, publish_pages, delete_pages, delete_others_pages, delete_published_pages, edit_published_pages, read_private_pages |
| Media | upload_files, edit_files, delete_files |
| Comments | moderate_comments, edit_comment |
| Taxonomies | manage_categories, manage_tags, edit_categories, delete_categories, assign_categories, assign_tags |
| Appearance | switch_themes, edit_themes, edit_theme_options |
| Plugins | activate_plugins, edit_plugins, install_plugins, delete_plugins, update_plugins |
| System | manage_options, import, export, unfiltered_html, update_core, view_site_health_checks |
| General | read |
Plugins can register additional capabilities:
use Minnow\Core\Auth\CapabilityRegistry;
CapabilityRegistry::getInstance()
->register('manage_bookmarks', 'Manage Bookmarks', 'Bookmarks', 'Full bookmark management');
CSRF Protection (Nonces)
Nonces are time-limited tokens that prevent cross-site request forgery. Each nonce is valid for 24 hours and is tied to the current user.
In HTML Forms
use function Minnow\Core\Auth\{createNonce, verifyNonce, nonceField};
// Generate a hidden input field
echo nonceField('save_settings');
// <input type="hidden" name="_nonce" value="a1b2c3d4e5" />
// Verify on submission
if (verifyNonce($_POST['_nonce'], 'save_settings')) {
// Valid request
}
In API Requests
// Create token
$token = createNonce('api_action');
// Verify token
$valid = verifyNonce($token, 'api_action');
Nonces use HMAC-SHA256 with a 12-hour tick system. A nonce created in the current tick remains valid through the next tick, giving an effective 12-24 hour window.
Application Passwords
Application passwords provide secure API authentication without exposing the user's main password. Each application password is tracked independently.
use Minnow\Core\Auth\ApplicationPasswords;
// Create a new application password
$result = ApplicationPasswords::create($userId, [
'name' => 'My Mobile App',
'app_id' => 'mobile-app-v2',
]);
// $result['password'] = 'abcd efgh ijkl mnop qrst uvwx' (plaintext, shown once)
// $result['item'] = stored password object
// List all passwords for a user
$passwords = ApplicationPasswords::getForUser($userId);
// Authenticate with application password
$user = ApplicationPasswords::authenticate('username', 'abcdefghijklmnopqrstuvwx');
// Delete a password
ApplicationPasswords::delete($userId, $uuid);
// Delete all passwords
ApplicationPasswords::deleteAll($userId);
Application passwords are stored as bcrypt hashes in user meta. Usage (last used time, IP address) is tracked automatically and throttled to one update per day.
Security Features
| Feature | Implementation |
|---|---|
| Password hashing | bcrypt (default), with WordPress phpass/bcrypt migration |
| Session fixation | Session ID regenerated on login |
| Session cookies | HttpOnly, SameSite=Lax, configurable Secure flag |
| CSRF protection | HMAC-SHA256 nonces with 24-hour expiry |
| Password comparison | Constant-time via hash_equals() |
| Session destruction | Full cleanup on logout (session data + cookie) |
| API auth | Separate application passwords with usage tracking |
| Auto-upgrade | Legacy password hashes upgraded to bcrypt on login |