Read this when changing authentication, sessions, CSRF, roles, users, or user-management endpoints. # Identity and User Management ## Purpose This subsystem authenticates database-backed users into PHP sessions and lets administrators manage accounts and roles without exposing credentials. ## Data Model | Entity | Field | Type / storage | Notes | |---|---|---|---| | `users` | `id` | auto-increment integer | Account identifier. | | `users` | `username` | `varchar(255)`, unique | Login identity and password-hash input. | | `users` | `saltedpassword` | `varchar(255)` | Bcrypt hash of `username|password`. | | `users` | `roles` | `varchar(4096)` | PHP-serialized array of persistent numeric permission IDs; unserialized with classes disabled. | | PHP session | `SESSION.user` | integer | Current authenticated user ID. | | PHP session | `SESSION.csrf_token` | 64-character hex string | Random 32-byte token used for state-changing API requests. | ## Business Rules - Authentication loads by username and verifies `username|password` against the stored bcrypt hash. - Login starts a session when required, regenerates its ID, then stores only the user ID. Session responses include a CSRF token for anonymous and authenticated callers. - Server-side state-changing protected operations require a CSRF token using `hash_equals`; client route guards never replace this check. - Permissions assigned through user administration must be existing persistent numeric permission IDs and are deduplicated. - Usernames are trimmed and must be non-empty. The unique database constraint is authoritative under concurrency. - If a username changes, a non-empty replacement password is required because the username is part of the password-hash input. - An administrator cannot delete their own account. The final account with permission ID `1` cannot lose that permission or be deleted. - Update/delete serialize final-admin checks with a transaction and `SELECT id FROM users FOR UPDATE`; failures roll back the transaction. - Public DTOs return only `id`, `username`, and, for administrator management, roles. Password hashes and serialized persistence fields never leave the API. ## Constants | Constant | Value | Location | |---|---|---| | `User::PASSWORD_SEPARATOR` | `|` | `src/Core/System/User.php` | | `Controller::CSRF_SESSION_KEY` | `SESSION.csrf_token` | `src/Core/Api/Controller.php` | | `Controller::CSRF_HEADER` | `HTTP_X_CSRF_TOKEN` | `src/Core/Api/Controller.php` | | Administrator authorization permission ID | `1` | Authorization contract used by administrator-only operations. | | `UserController::USERNAME_CONSTRAINT` | `uq_users_username` | `src/Core/Api/UserController.php` | ## Cross-links See [API routes](../api-routes.md), [MySQL integration](../integrations/mysql.md), [browser API integration](../integrations/browser-api.md), and the [API endpoint rule](../rules/add-api-endpoint.md).