Login & Account Security
Complete sign-up, email verification, password recovery, TOTP, and SSO login and configure login protection
Clouisle's authentication flow is driven by site settings: password, email verification, administrator approval, CAPTCHA, forced password change, TOTP, and SSO can all apply at once. This page walks through each step in order of use, with its defaults, failure results, and recovery paths. The full list of site switches is in the Site Settings reference.
Sign-up & Email Verification
Self-Registration
-
Navigate to the login page at
https://your-domain.comand click Sign up. -
Fill in the registration form:
Field Constraint Username Required, globally unique, max 50charactersEmail Required, globally unique, must be a valid address Password Validated against the site password policy (at least 8characters by default — see Password Requirements)Confirm Password Must match the password -
If human verification is enabled, click Click to verify you are human.
-
If terms acceptance is required, tick the user agreement and privacy policy.
-
Click Register to submit.
What happens next depends on site settings:
| Site setting | Default | Behavior after submit |
|---|---|---|
allow_registration | true | When off, registration returns 5000 (registration_disabled) |
require_approval | true | When on, the account is pending approval (approval_status=pending, is_active=false); administrators get a global notification and the user cannot log in until approved |
email_verification | true | When on, the email must be verified before login |
enable_captcha | false | When on, the sign-up form shows the click-style CAPTCHA |
require_terms_acceptance_on_register | false | When on, terms acceptance is mandatory |
force_password_change_first_login | false | When on, new users must change their password at first login |
The first user registered on a site bypasses every restriction: it is activated immediately, marked email-verified, and granted the Super Admin role. Register that first administrator before opening registration up.
There is no invitation-based registration flow. Accounts come from self-registration, administrator creation (User Management), or SSO auto-creation (SSO).

Email Verification
- Verification email is sent over SMTP; with no SMTP configured (
smtp_enabled=false) sending fails with5007(smtp_not_configured). - The email contains a 6-digit code and a link, valid for 10 minutes (
600seconds in Redis). - Resending has a 60-second cooldown (per email address + purpose); hitting it returns
5008withremaining_seconds. - A wrong code returns
5005; an expired link/code returns5006. - Logging in with an unverified email returns
5004; the login page then switches straight to the verification step and resends a code automatically.
Login Methods
Password Login
- Navigate to the login page and enter your username or email (a value containing
@is matched as a case-insensitive email; otherwise it is matched as an exact username). - Enter your password.
- If human verification is enabled, click Click to verify you are human.
- Click Log In.
The request passes through these gates in order; any failure stops the rest of the flow:
- SSO-only mode: when
sso_enabled=trueandsso_allow_password_login=false, password login returns6306(password_login_disabled). - CAPTCHA (when enabled).
- Account existence: an unknown account and a wrong password both return
2003(incorrect_email_or_password) so account existence is not leaked. - Lock state: while locked, the response is
5300(account_locked) withremaining_seconds. - Local password presence: accounts created through SSO with no local password cannot use password login and also get
2003. - Password check: a mismatch returns
2003withremaining_attempts; reaching the threshold turns into a lockout (see Account Lockout). - Account status: a deactivated account returns
2004(inactive_user); a pending-approval account returns2004(pending_approval_user). - TOTP: with TOTP enrolled, the response is
requires_totp=trueplus a 5-minute temporary token, moving the user to the second verification step. - Administrator-enforced TOTP: when
require_totp=trueand the user has not enrolled, the response isrequires_totp_setup=trueplus a 30-minute temporary token, and the frontend routes to/totp-setup. - Email verification (when enabled; Super Admins are exempt from this check).
- Password expiration / forced change: when a change is required the response still carries a token but sets
force_password_change=trueandreason(expiredfor an expired password,forcefor an administrator request); the frontend routes to/change-password?reason=....
After a successful login the failure counter and lock state are cleared and last_login is refreshed.
SSO Single Sign-On
If your organization has configured SSO, you can log in with corporate credentials.
- Navigate to the login page and click the SSO provider button (the label comes from the provider's
button_text, e.g. "Sign in with Google"). - Authenticate with the identity provider.
- You are redirected back to Clouisle and logged in automatically.
First-time SSO login:
- With email matching on (
sso_match_by_email, enabled by default) and an existing account for that email, the SSO connection is linked to that account. - Otherwise, when auto-creation is allowed, a new account is created. New accounts have no local password, count as email-verified, and enter pending-approval state according to configuration.
OAuth2/OIDC, SAML 2.0, and CAS are supported. See SSO Configuration for setup and troubleshooting.
Turning off sso_allow_password_login switches the site to SSO-only. Before doing so, make sure at least one Super Admin has an SSO connection bound — otherwise nobody may be able to reach the admin console.
First-Time Login
After your first successful login:
- Welcome Screen: a welcome message is displayed.
- Profile Setup: complete your profile information (optional).
- Team Assignment: you may be added to the default team (
default_team_id, with roledefault_team_role,memberby default). - Dashboard Access: regular users see the platform interface; administrators also see administration entries.
Login Protection
Human Verification
Controlled by enable_captcha (off by default) and applied to the login and sign-up forms. It verifies a genuine click plus pointer trajectory rather than typing text.
- The challenge and proof live in Redis for 300 seconds (5 minutes) and are single-use.
- A failed check returns
5303(captcha_invalid); a missing proof returns5302(captcha_required). After a failure the frontend fetches a fresh challenge automatically. - The login page only loads a CAPTCHA once both username and password are filled, so not seeing one on page load is normal.
Account Lockout
The failure counter lives on the user record (failed_login_attempts + locked_until) and is per account, with no sliding window:
max_login_attemptsdefaults to5: the lock triggers as soon as the count reaches 5.lockout_duration_minutesdefaults to15: during the lock, even a correct password is rejected (5300).- Below the threshold, the error response carries
remaining_attemptsso the UI can show how many tries are left. - The counter does not decay over time: it resets either on a successful login or on the first login attempt after the lock expires.
- Triggering a lock sends the user a high-priority
security.account_lockednotification including the lock duration in minutes.
The per-IP failure counter (Redis key login:attempts:ip:<ip>, TTL 3600 seconds) has helper functions but no callers anywhere, so this version does not rate-limit by IP. Locking only happens at the account level.
Login Anomaly Detection
The system records the IPs and User-Agents each account has recently logged in from (up to 10 each, kept for 30 days):
- Once history exists, logging in from a new IP or new device raises a high-priority
security.login_anomalynotification containing the IP, time, and User-Agent. - The very first login (no history yet) never alerts, so brand-new accounts do not produce false positives.
- If Redis is unavailable the check is skipped and login proceeds normally.
Password Management
Password Requirements
| Requirement | Default | Setting key |
|---|---|---|
| Minimum length | 8 characters | min_password_length (the settings UI accepts 6–32) |
| Uppercase | At least 1 (A-Z) | require_uppercase |
| Number | At least 1 (0-9) | require_number |
| Special character | Not required | require_special_char |
| Lowercase | No such switch | — |
| No reuse | Cannot match the last 5 passwords | password_history_count |
| Minimum age | 0 days (changeable immediately) | password_min_age_days |
There is no dictionary/weak-password check and no password strength meter — validation results come back as field errors. One bcrypt constraint also applies: anything beyond 72 bytes is ignored, so trailing characters of very long passwords do not participate in verification.
Example of a valid password (satisfies the default policy):
MySecure#Pass2026!Examples of invalid passwords:
❌ 12345678 (no uppercase)
❌ abcdefgh (no digit)
❌ Pass123 (only 7 characters, below the default 8)Changing Your Password
- Open Profile Settings (avatar menu) → Account tab.
- Find the Change Password section and fill in Current Password, New Password, and Confirm New Password.
- Click Update password.
Backend behavior (POST /api/v1/users/me/change-password):
- A wrong current password returns
2003(current_password_incorrect). - Violating the minimum age (
password_min_age_days) returns5306with the remaining days. - Policy violations or a reused password return
1001with per-item reasons undererrors.new_password(such aspassword_recently_used). - Success refreshes
password_changed_at, recalculates the expiration from the policy, clears the forced-change flag, and sends a high-prioritysecurity.password_changednotification.
Tips for strong passwords: mix character types, make it at least 12 characters, avoid personal information, and use a password manager.
Resetting a Forgotten Password
Prerequisite: SMTP must be configured (smtp_enabled=true). Without it, Forgot Password? returns 5007 (smtp_not_configured) directly.
- Go to the login page and click Forgot Password?.
- Enter your email address and click Send Reset Email. To prevent enumeration this returns success whether or not the address exists.
- Check your inbox and use either path:
- click the reset link in the email (opens
/reset-password?token=...), or - expand or enter the code manually on the page and type the 6-digit code from the email.
- click the reset link in the email (opens
- Enter your new password, confirm it, and click Reset Password.
- Log in with the new password.
- Codes/links are valid for 10 minutes; resending has a 60-second cooldown (
5008). - A wrong code returns
5005; an invalid or expired link returns5006. - The reset does not ask for the current password (the code itself is the authorization), but it still enforces the password policy and history. On success the account's failure counter and lock state are cleared.
Password Expiration
If an administrator enables the expiration policy (off by default):
| Setting key | Default | Meaning |
|---|---|---|
password_expiration_enabled | false | Master switch |
password_expiration_days | 90 | Password lifetime in days |
password_expiration_warning_days | 7 | Warning window before expiration |
password_history_count | 5 | Number of past passwords retained |
password_min_age_days | 0 | Minimum interval between changes |
Who is exempt: SSO accounts (auth_source != "local"), users individually flagged as exempt (password_expiration_exempt), and Super Admins — none of them participate in expiration or forced changes.
Timeline:
- A daily 08:00 check sends a
password.expiringreminder when exactly 7 / 3 / 1 days remain, and apassword.expirednotification plus the forced-change flag once expired (at most one reminder per user per day). - If the password is already expired at login, the response carries
force_password_change=truewithreason=expiredand the frontend routes to/change-password?reason=expired. - The Account tab in profile settings shows password expiration. An expired password shows a red alert; fewer than 7 days left shows a yellow warning (that threshold is hardcoded to 7 days in the frontend).
TOTP Two-Factor Authentication
Setting Up TOTP
- Open Profile Settings → Account tab and click Enable Two-Factor Authentication.
- The wizard explains what 2FA adds, then shows a QR code (issuer
Clouisle) to scan with an authenticator app (Google Authenticator, Authy, Microsoft Authenticator, 1Password, …) or a secret to enter manually. - Enter the current 6-digit code to confirm (the verification window is ±1 period, i.e. ±30 seconds).
- Save the one-time backup codes: the wizard generates 10 of them in
XXXX-XXXXformat, downloadable (clouisle-backup-codes.txt) or copyable. - Click Finish.
The API calls POST /api/v1/totp/setup first to generate the secret, QR code, and backup codes — TOTP is not yet enabled at that point — and POST /api/v1/totp/enable verifies a code before enabling it. Calling setup/enable when already enabled returns 5314 (totp_already_enabled); enabling without a pending secret returns 5315 (totp_setup_expired).

TOTP secrets are stored encrypted with a key derived from SECRET_KEY. Changing SECRET_KEY makes every enrolled TOTP secret undecryptable, so users can only be unbound by an administrator and re-enrol.
Logging In with TOTP
- Enter your username and password; the backend answers
requires_totp=truewith a 5-minute temporary token. - Enter the 6-digit code from your authenticator (or click Use a backup code and type a backup code instead).
- Click Verify. On success the temporary token is exchanged for a full session token.
A failed verification returns 5311 (totp_invalid); rate limiting returns 5312 (totp_rate_limited) with seconds, which the frontend shows as the remaining wait. Rate limit: starting from the first failure, 5 failures within a 300-second window lock verification for 900 seconds (15 minutes); a successful verification resets the counter.
Backup Codes
- 10 are generated per batch in
XXXX-XXXXformat, each usable once, and only hashes are stored server-side. - The Account tab shows the remaining backup code count (the UI string is
{count} backup codes remaining). - Click Regenerate backup codes and enter the current 6-digit code to invalidate the old batch and issue a new one (old codes stop working immediately).
Disabling TOTP and Admin Actions
- Self-service disable: click Disable Two-Factor Authentication and provide both the account password and a current code (or a backup code) in the confirmation dialog — both must verify. A wrong password returns
2003, a wrong code5311. - Admin unbind:
POST /api/v1/admin/totp/users/{user_id}/disableturns TOTP off for a user directly, for when the authenticator and backup codes are both lost (requiresadmin:dashboard:access). - Force TOTP site-wide: with
require_totp=true, users who have not enrolled are routed to/totp-setupat login and cannot get in without enrolling.
Session Management
Session Duration
- Sessions are stateless JWTs valid for
session_timeout_days(default30days), with no idle timeout and no "Remember Me" option. - The token lives in browser
localStorageunderaccess_tokenand is sent asAuthorization: Bearer <token>.
Single Session Mode
With single_session enabled, each new login writes the previous token to a Redis blacklist and overwrites the session record.
Known limitation
Authentication only checks the Redis blacklist, and the blacklist entry written at login has a TTL of just 5 seconds; the get_user_session helper that would read the session record has no callers. In practice, therefore, single-session mode only rejects the old token for about 5 seconds — after that the old token keeps working until it expires. Do not rely on it as a session-kick security control.
Logging Out
- Click your profile icon in the top-right corner.
- Select Log out.
- You are redirected to the login page.
On the backend the current token is written to the Redis blacklist (TTL 5 seconds) and the session record is cleared. Because JWTs are stateless, a copy of the same token is accepted again after those 5 seconds until it expires on its own. What actually blocks access is changing the password (together with single-session), deactivating the account, or shortening session_timeout_days.
Account Security
Security Notifications
These events produce security.* / password.* in-app notifications:
| Event type | Trigger | Level |
|---|---|---|
security.password_changed | Password changed successfully | High |
security.account_locked | Failure threshold reached and the account was locked | High |
security.login_anomaly | Login from a new IP / device | High |
password.force_change | Administrator requires a change at next login | High |
user.password_reset | Administrator set a new password for the user | Medium |
password.expiring / password.expired | 7/3/1 days before expiry / already expired | Medium |
Reviewing the notification center regularly is the most direct way to spot unauthorized access.
If Your Account Is Compromised
If you suspect unauthorized access:
- Change your password now. Note that this does not revoke already-issued JWTs (see Logging Out) and does not affect existing API keys.
- Enable TOTP or regenerate backup codes.
- Check your login notifications for unfamiliar IPs/devices and deactivate suspicious keys in API Keys.
- Ask an administrator to deactivate the account —
is_active=falserejects every request of that account at the authentication stage and is the only immediate stop. RotatingSECRET_KEYalso invalidates all JWTs, but it makes every enrolled TOTP secret undecryptable, so use it with care.
An Administrator Setting a New Password
Administrators can set a new password for a user directly in User Management (PUT /api/v1/admin/users/{id} with password):
- The administrator sets the new password (validated against the password policy, but without the history check).
- The user receives a
user.password_resetnotification — no email with a temporary password is sent, so the administrator must pass the new password on through a secure channel. - The user should change it themselves after logging in.
Related admin actions: force password change (POST /api/v1/admin/users/{id}/force-password-change, requiring a change at next login), reset expiration clock (resets password_changed_at), and exempt from expiration (password_expiration_exempt).
Troubleshooting
Cannot Access Login Page
- Check your network connection and the URL.
- Try another browser and clear cache and cookies.
- Confirm
site_url/ the frontend address is correct (SSO callbacks depend on it too). - Contact your administrator.
Login Says the Username or Password Is Wrong
An unknown account, a wrong password, and an SSO account attempting password login all return the same 2003 — that is anti-enumeration by design, not a malfunction.
- Confirm you are using a username (exact) or email (case-insensitive).
- If the account was created through SSO and never had a local password, use SSO login instead.
- If the message includes
remaining_attempts, mind the remaining tries. - Use Forgot Password to reset.
Account Already Locked
- Wait for the lock to expire (15 minutes by default) and log in again; the failure counter resets at that point.
- Administrator activate/edit actions do not clear
locked_until— there is no "unlock now" button. For urgent access, run the forgot password reset: a successful reset clears both the failure counter and the lock. - If locking recurs, check for an old script retrying a stale password.
Verification / Reset Email Not Received
- Confirm
smtp_enabled=trueand correct SMTP settings — without them the endpoints return5007. - Check the spam folder and wait a few minutes.
- Mind the 60-second resend cooldown (
5008reports the remaining seconds). - Confirm the address matches the one on the account.
Email Still Unverified Because No Code Arrived
Returning 5004 at login moves you straight into the verification step and resends a code automatically; the Resend button is limited by the 60-second cooldown.
Because the admin console has no "mark email verified" action, ask an administrator to verify the SMTP configuration first (sending returns 5007 when it is missing). If the mail path is still unavailable, temporarily turn off email_verification so the user can log in, and re-enable it once delivery works.
TOTP Will Not Verify
- Check that device time is synced automatically (±30-second window).
- When rate-limited, wait as prompted — up to 15 minutes.
- Log in with a one-time backup code.
- If backup codes are exhausted, ask an administrator to unbind TOTP so you can re-enrol.
You Are Being Asked to Change Your Password
That means force_password_change is set (the password expired, or an administrator required it). Enter your current and new password on the /change-password page; the flag clears automatically on success.
SSO Login Fails
- Confirm you are using the right provider and that the account is active (deactivated or pending-approval accounts land on
/sso-callbackwith the matching message). - Check the provider configuration and callback address (see SSO Configuration).
- Clear browser cookies and retry, or fall back to password login if the site allows it.
Best Practices
✅ Do
- Use a strong, unique password and enable TOTP.
- Use a password manager instead of reusing passwords elsewhere.
- Keep your email address valid — it is the only password-recovery channel.
- Act on security notifications instead of clearing the list.
- Prefer SSO login to reduce password exposure.
❌ Don't
- Share accounts or passwords.
- Stay signed in on public devices (logging out only affects the local token, and see the logging-out limitation).
- Disable TOTP on shared devices.
- Ignore
security.login_anomalyalerts.
Related Documentation
- Profile Settings — where you actually change the password, manage TOTP, and unlink accounts
- SSO Configuration — provider setup and user lifecycle
- User Management — approval, deactivation, forced changes, and expiration management
- Site Settings reference — defaults for every
security/ssokey used above
How is this guide?