Two-factor authentication
owncloud.online checks the second factor in the core, but it does not provide
one itself. The factor comes from an app that registers itself under
<two-factor-providers> in its info.xml. For time-based one-time passwords
(TOTP) that app is twofactor_totp. Without such an app enabled, login does not
ask for a second factor, even when enforcement is switched on.
Install the provider app
The app does not ship with the core. It is publicly available in the Marketplace (no API key needed) and is installed like any other app through the Market app, see Apps and Marketplace. On the command line, one command installs and enables it:
sudo -u www-data php8.4 occ market:install twofactor_totp
If the app is already present and only disabled, this is enough:
sudo -u www-data php8.4 occ app:enable twofactor_totp
The app creates the QR codes with imagick and requires the extension in its
info.xml (<lib>imagick</lib>). If php8.4-imagick is missing,
occ app:enable refuses to enable the app and names the missing library ("The
library imagick is not available."). market:install does not check the
library and enables the app anyway. Without the extension, however, the secret
cannot be shown as a QR code, neither in the account settings nor on the login
page. So install php8.4-imagick before the app.
To see which apps are enabled:
sudo -u www-data php8.4 occ app:list
Set up the second factor in an account
Go to Settings → Personal → Security, section TOTP Second-factor Auth:
- Check Activate TOTP. The server then creates a new secret and shows it together with a QR code.
- Scan the QR code with the authenticator app. If you cannot scan, type in the value shown after This is your new TOTP secret: by hand.
- Enter the six-digit code from the app in the Authentication code field and click Verify. The answer is Verified or Not verified.
Step 3 is the one that matters: only a verified secret counts. If the box is checked but no code is ever verified, login stays as it was. The next time the page loads, the box is empty again, because its state follows the verified secret. Unchecking the box deletes the secret on the server. Activating it again creates a new secret and requires a new scan.
Timing: the code changes every 30 seconds, and the check accepts three time steps in both directions. That allows a difference of about one and a half minutes between the server clock and the device clock. A code that was already accepted is not accepted a second time; the server stores the last verified code for this purpose.
Enforce the second factor
Enforcement is under Settings → Admin → Security, section Two-factor Authentication. It has two controls:
- the checkbox Enforce two-factor authentication to all the users
- the field Exclude the following groups from enforcing two-factor authentication
Both controls write to the app configuration:
| Key | App | Meaning |
|---|---|---|
enforce_2fa |
core |
yes turns enforcement on, any other value leaves it off |
enforce_2fa_excluded_groups |
core |
JSON list of exempt groups, default [] |
The same settings on the command line:
sudo -u www-data php8.4 occ config:app:set core enforce_2fa --value yes
sudo -u www-data php8.4 occ config:app:set core enforce_2fa_excluded_groups --value '["Engineering","Board"]'
sudo -u www-data php8.4 occ config:app:get core enforce_2fa
sudo -u www-data php8.4 occ config:app:get core enforce_2fa_excluded_groups
To turn it off, use --value no or delete the key:
sudo -u www-data php8.4 occ config:app:delete core enforce_2fa
What enforcement per group actually does
The model only knows exceptions, not an allow list: enforcement applies to everyone, and the listed groups are exempt. To enforce the factor for a single group only, every other group must be on the exception list. Accounts that belong to no group cannot be exempted this way, because the check only compares group memberships. Members of exempt groups can still set up the factor voluntarily; the user interface says so, too.
Behavior when the second factor is enforced
- Accounts without a configured factor are not locked out. On the factor page the app creates a secret and shows the QR code, so setup happens right at login. The code entered there also counts as verification of the new secret.
- Enforcement overrides the per-account switch-off (see below).
- Without an enabled provider app, enforcement has no effect, because there is no factor to ask for.
The flow after a correct password: the Two-step verification page appears with the list of available factors. With exactly one factor, the page skips the list and asks for the code right away. Until the code is correct, the server does not let you reach any other page; only Cancel login leads out.
App passwords and client sync
When an account uses a second factor, the server rejects every client login that sends the account password. Requests with an app password (device token) keep working.
| Access path | Behavior with an active second factor |
|---|---|
| Login in the browser | unchanged, the factor is requested |
| Client with the account password (WebDAV, OCS) | rejected; the log shows Login failed: '<account>' (Remote IP: '<ip>') |
| Client with an app password | keeps working |
Token generation via index.php/token/generate |
rejected with HTTP 401 |
| Browser session that is already logged in | not asked for the factor afterwards; the prompt happens at login |
Existing app passwords stay valid. Turning on the second factor does not revoke any tokens. That is convenient for clients that are already set up, and it is also the weak spot of the measure: a leaked app password still bypasses the second factor. If you introduce the factor to shut out compromised credentials, go through the tokens and revoke them.
Create and revoke app passwords under Settings → Personal → Security, section App passwords / tokens: enter a name and click Create new app passcode. Username and Password / Token are then shown only once. On the same page, the Sessions section lists the web, desktop, and mobile clients that are currently logged in.
Independent of the second factor, you can block login with the account password
for all clients in config/config.php:
'token_auth_enforced' => true,
Login in the browser is not affected.
When an account has lost its second factor
The second factor cannot be recovered, only reset. After that, the account sets up a new device.
In the admin settings, use Settings → Admin → User Authentication, section TOTP Admin Reset: find the account with Search users, select it in the list, and click Reset selected. Reset all TOTP setups resets every setup on the instance.
The same on the command line:
sudo -u www-data php8.4 occ twofactor_totp:delete-secret alice
sudo -u www-data php8.4 occ twofactor_totp:delete-secret alice bob
sudo -u www-data php8.4 occ twofactor_totp:delete-secret --all
Without an account and without --all, the command stops with a message. The
output shows the number of deleted secrets per account.
What happens next depends on enforcement. If it applies to the account, the account sets the factor up again on the factor page at the next login. If it does not apply, the second factor is off for that account until it is switched on again in the account settings.
Two more options that keep the secret:
# Switch the factor off for one account without deleting the secret
sudo -u www-data php8.4 occ user:setting alice core two_factor_auth_disabled --value 1
# Undo the switch-off
sudo -u www-data php8.4 occ user:setting alice core two_factor_auth_disabled --delete
# Mark the secret as "not verified"; the factor is then off unless enforcement applies
sudo -u www-data php8.4 occ twofactor_totp:set-secret-verification-status false --uid alice
Both options only work while enforcement does not apply to the account, because
enforcement is checked first. With an enforced factor, a secret marked as "not
verified" only leads back to the factor page, where the same secret appears
again as a QR code. The switch-off via two_factor_auth_disabled is also
available in the Provisioning API: send a PUT to ocs/v1.php/cloud/users/<uid>
with the key two_factor_auth_enabled (value true or false). When you read
the account data, the API returns the same field.
Clean up secrets of accounts that were already deleted:
sudo -u www-data php8.4 occ twofactor_totp:delete-redundant-secret
Do not disable the app for testing
Disabling the twofactor_totp app deletes the secrets of all
accounts. If you disable it "just for a quick test", every account on the
instance has to set up TOTP again. Deleting an account also removes its
secret.
Interaction with brute-force protection
Brute-force protection counts failed password checks per combination of IP
address and login name. The first attempts carry no delay; after that the delay
grows, and the login form shows the remaining time. Behind a reverse proxy, the
count is only correct if trusted_proxies is set correctly, see
Security and setup warnings.
For the second factor, the order matters:
- Wrong password: the attempt is counted, and the factor page is never reached.
- Correct password: the counter for this pair of IP address and login name is cleared, then the factor page follows.
- Wrong code on the factor page: the attempt is not counted. The core only
writes a warning to the log:
Two factor verify failed: '<account>' (Remote IP: '<ip>').
So brute-force protection guards the password, not the second factor. Any number of wrong codes causes neither a delay nor a lockout. Guessing the code is only hindered by its six digits, the narrow time window, and the block on reusing a code. If that is not enough for you, monitor the log line above, see Server log and error messages.
When the brute_force_protection app is enabled, it takes over the protection
of login and of public link passwords. The built-in protection stays out of
exactly these two paths, so that two mechanisms do not count side by side and
report two different delays. It still covers all other actions.
Troubleshooting
| Symptom | Cause | Fix |
|---|---|---|
| Enforcement is on, but no code is requested | No app with <two-factor-providers> is enabled; without a factor, enforcement has nothing to ask for |
occ market:install twofactor_totp or occ app:enable twofactor_totp, then check occ app:list |
| Box checked in the account, but login does not change | The secret was never verified with a code; only verified secrets count | Enter a code and click Verify until Verified appears |
| Codes are rejected although the app shows them | Server or device clock is off by more than about one and a half minutes | Check time synchronization on the server and on the device |
| The same code is not accepted a second time | The last verified code is stored and blocked for reuse | Wait for the next code |
occ app:enable refuses the app, the message names imagick |
php8.4-imagick is missing; the app requires the extension |
Install the extension, reload PHP-FPM |
| No QR code, setup fails | php8.4-imagick is missing; market:install enabled the app without this check, or the extension was removed later |
Install the extension, reload PHP-FPM |
| Desktop or mobile client reports a login error, the browser works | The client sends the account password, which is blocked with an active second factor (log: Login failed: …) |
Create an app password and enter it in the client instead of the account password |
| An old client still reaches the data although the factor is active | An app password created before the change is still valid | Revoke the token under Personal → Security → App passwords / tokens |
| An exempt group is still forced to use the factor | enforce_2fa_excluded_groups does not contain a valid JSON list, or a group name is misspelled |
Check occ config:app:get core enforce_2fa_excluded_groups and set it again |
two_factor_auth_disabled has no effect for an account |
Enforcement applies to this account and is checked first | Exempt the account's group or turn enforcement off |
After disabling twofactor_totp, all setups are gone |
Disabling deletes every secret | Have the accounts set up the factor again; do not disable the app for testing |
| Several wrong codes have no consequence | Brute-force protection only counts password attempts, not factor attempts | Monitor the log for Two factor verify failed |