Two-Factor Authentication via OXAPI
As of V. 4.0, accounts with two-factor authentication (2FA) enabled can also authenticate via OXAPI. Previously, 2FA and OXAPI were mutually exclusive. This page is aimed at developers connecting a headless frontend via OXAPI.
The functional basics of 2FA (shop-wide activation, activation by the customer, code validity, failed attempts) are described under Two-Factor Authentication and apply to OXAPI unchanged.
Scope
OXAPI 2FA applies to every account that authenticates via OXAPI — regardless of
user rights. Even an admin user account with 2FA enabled goes through the
challenge/OTP flow on OXAPI login, just like a customer account. The
“admin backend” exception mentioned under
Two-Factor Authentication concerns only the classic
shop administration (/admin), not OXAPI.
Background
For an account with 2FA enabled, the standard OXAPI login (token / login
queries from graphql-base) does not yet return a full access token, but
first a challenge token. This token identifies the ongoing 2FA process
(claims mfa_pending and mfa_exp) and is not issued together with a
refresh token. Only after the one-time code (OTP) sent by email has been entered
successfully does the client exchange the challenge token for a regular access
token.
Without 2FA enabled, login behaves unchanged and returns a regular access token directly.
Login flow
Procedure
Log in. The client calls the standard login (
token/login) with username and password. For an account with 2FA enabled, the shop sends a 6-digit code to the stored email address and returns a challenge token.Use the challenge token. The client sends the challenge token as
Authorization: Bearer <token>on all subsequent 2FA calls.Verify the code. The client passes the entered code to
verifyTwoFactorTokenorverifyTwoFactorLoginand, on success, receives a regular access token. The challenge token is consumed and cannot be reused.Optional: resend the code. If the code did not arrive, the client requests a new one with
resendTwoFactorOtp.
Mutations
verifyTwoFactorToken
Verifies the code against the ongoing 2FA process and, on success, returns a regular access token (without a refresh token). Requires the challenge token as the bearer token.
mutation {
verifyTwoFactorToken(otp: "123456")
}
verifyTwoFactorLogin
Like verifyTwoFactorToken, but additionally returns a refresh token — i.e.
the same result as a regular login. Requires the challenge token as the bearer
token.
mutation {
verifyTwoFactorLogin(otp: "123456") {
accessToken
refreshToken
}
}
resendTwoFactorOtp
Sends a new one-time code for the ongoing process. Requires the challenge token as the bearer token. The waiting period between two dispatches (60 seconds) applies here too; if it is not met, the mutation responds with an error.
mutation {
resendTwoFactorOtp
}
setTwoFactorAuth
Enables or disables the 2FA preference of the logged-in customer. This mutation requires a regular access token (not the challenge token) and always acts on the logged-in user’s own account. The return value is the actually stored state.
mutation {
setTwoFactorAuth(enabled: true)
}
Challenge token lifetime
The challenge token is valid for the smaller of the two values “API 2FA challenge lifetime” and “OTP code lifetime”. This ensures a code can never be redeemed against an already expired challenge token. Both values are maintained in the module settings.
Note
A code resent via resendTwoFactorOtp does not extend the challenge token’s
validity. A code requested late can therefore expire before it can be redeemed
— in that case the login must be restarted.
Error behavior
Situation |
Message |
|---|---|
Invalid or expired code |
|
Code resend requested too early (waiting period) |
|
No valid challenge token (missing, expired, or no 2FA process) |
|
Prerequisites
graphql-baseis installed and activated. Withoutgraphql-base, the token-issuing 2FA mutations are not available.