Olympus Docs
TroubleshootingAuth issues

TOTP codes rejected as wrong

User insists code is correct, Kratos says invalid

User enters a 6-digit code from their authenticator app. Kratos returns "invalid code." User confirms the code they're entering. Yet rejected.

Cause 1: Clock skew

TOTP is time-based. Codes are valid for 30 seconds, with a 1-window grace each side (so 90s total tolerance).

If the user's device clock is more than ~30s off from server clock, codes generated by the device don't match.

Check user clock

Ask user to verify time settings on their phone:

  • iOS: Settings → General → Date & Time → Set automatically.
  • Android: Settings → System → Date & time → Automatic.

Check server clock

podman exec ciam-kratos date
# Should be very close to current UTC.

# If drifted:
ssh prod 'sudo systemctl restart systemd-timesyncd'
ssh prod 'timedatectl status'

See troubleshooting/clock-skew.mdx.

Cause 2: User entered code from wrong account

Authenticator apps store many TOTP secrets. User accidentally entered the code from another service.

Verify: the entry in the authenticator app shows "[Your App Name]"? Yes → correct. Otherwise, they're using the wrong one.

Cause 3: Old/replaced secret

User unenrolled MFA and re-enrolled. The authenticator app still has the OLD entry, the new entry was added but they may be tapping the wrong one.

Fix: in their authenticator, delete the old "[Your App]" entry, keep only the new one.

Cause 4: Wrong issuer / label in authenticator

If your service rebranded, the entry label might be stale. Visually confusing. Tell user to delete and re-enroll.

Cause 5: TOTP secret corruption

Rare. If the QR code wasn't scanned cleanly, the secret might be slightly off. Re-enroll.

Cause 6: Algorithm mismatch

TOTP supports SHA1, SHA256, SHA512. Most apps default to SHA1.

Kratos uses SHA1 by default. Verify:

# kratos.yml
selfservice:
  methods:
    totp:
      config:
        issuer: Your App
        # algorithm defaults to SHA1

If you customized to SHA256 but users enrolled before that change, their app is generating SHA1 codes which Kratos compares against SHA256 → mismatch.

Cause 7: Server-side time drift recovery

If clock skew persists beyond TOTP tolerance, fix the clock and have users re-test. Don't rely on increasing tolerance, that weakens security.

Recovery codes as escape hatch

If MFA codes are broken, user should be able to use a recovery (backup) code.

selfservice:
  methods:
    lookup_secret:
      enabled: true

User enrolls 10 backup codes at MFA-setup time. Login UI shows "Use a backup code" link.

If they're also missing → admin recovery (see locked-account-unlock).

Debugging server-side

podman logs ciam-kratos | grep totp | tail -50

If you see totp code does not match:

  • Check what time Kratos thinks it is (date in container).
  • Compare to what time the user thinks it is.
  • Typical: server is 60s behind real time → user codes are "from the future."

On this page