Skip to main content

Two-factor authentication (OTP)

Contember supports TOTP-based two-factor authentication. Once enabled for a person, every sign-in (signIn and signInPasswordless) requires the current OTP code in addition to the regular credentials.

All three OTP mutations are self-service — they operate on the calling person. An administrator cannot enable or disable 2FA on someone else's behalf; the only admin lever is disablePerson + forceSignOutPerson to lock the account out entirely.

Enabling 2FA

The setup is a two-step exchange:

1. Prepare

mutation {
prepareOtp(label: "Contember Admin") {
ok
result {
otpUri
otpSecret
}
}
}
  • label is the human-readable name the authenticator app will show. Defaults to "Contember".
  • otpUri is a otpauth:// URI — render it as a QR code for the user.
  • otpSecret is the raw shared secret, for users who prefer to type it manually.

prepareOtp is idempotent — calling it again rotates the secret. If the person already had 2FA enabled, an audit 2fa_disable entry is written before the new pair is issued (because the previous secret is now invalid).

2. Confirm

mutation {
confirmOtp(otpToken: "123456") {
ok
error { code developerMessage }
}
}

The client asks the user to read the current code from their authenticator app and submits it. The token is validated against the secret created by prepareOtp. On success, 2FA is activated for the person.

Errors:

CodeCause
NOT_PREPAREDprepareOtp was not called for this person.
INVALID_OTP_TOKENThe submitted code does not match.

A successful confirm records 2fa_enable in the audit log; a rejected one records the failure too.

Disabling 2FA

mutation {
disableOtp {
ok
error { code }
}
}

Errors:

CodeCause
OTP_NOT_ACTIVE2FA is not enabled for this person.

Records 2fa_disable in the audit log.

Sign-in with 2FA enabled

signIn and signInPasswordless accept an otpToken argument. When 2FA is enabled and the token is missing or wrong:

MutationMissing tokenWrong token
signInOTP_REQUIREDINVALID_OTP_TOKEN
signInPasswordlessOTP_REQUIREDINVALID_OTP_TOKEN

Clients should react to OTP_REQUIRED by prompting for the code and retrying the same mutation with otpToken populated.

Where 2FA does not apply

  • signInIDP — the identity provider is responsible for second-factor enforcement. Contember does not re-check OTP after a successful IDP callback.
  • createSessionToken — admin impersonation bypasses 2FA by design.
  • Permanent API keys — 2FA only protects person logins; permanent keys are credentials in their own right.