NginxProxyManager/nginx-proxy-manager · warning · Error
No 2FA challenge pending
Error message
No 2FA challenge pending
What it means
The AuthContext in the NPM frontend manages a two-step login flow: after credentials are accepted with a 2FA-enabled account, the server returns a challenge token which is stored in state as twoFactorChallenge. verifyTwoFactor is the second step and refuses to run when no challenge is currently held in state, because it has no challengeToken to send to the verify2FA API.
Source
Thrown at frontend/src/context/AuthContext.tsx:61
const handleTokenUpdate = (response: TokenResponse) => {
AuthStore.set(response);
setAuthenticated(true);
setTwoFactorChallenge(null);
};
const login = async (identity: string, secret: string) => {
const response = await getToken(identity, secret);
if (isTwoFactorChallenge(response)) {
setTwoFactorChallenge({ challengeToken: response.challengeToken });
return;
}
handleTokenUpdate(response);
};
const verifyTwoFactor = async (code: string) => {
if (!twoFactorChallenge) {
throw new Error("No 2FA challenge pending");
}
const response = await verify2FA(twoFactorChallenge.challengeToken, code);
handleTokenUpdate(response);
};
const cancelTwoFactor = () => {
setTwoFactorChallenge(null);
};
const loginAs = async (id: number) => {
const response = await loginAsUser(id);
AuthStore.add(response);
queryClient.clear();
window.location.reload();
};
const logout = () => {
if (AuthStore.count() >= 2) {View on GitHub (pinned to 934a3fafe5)
Solutions
- Ensure login() (first stage) succeeded and set twoFactorChallenge before rendering the OTP input or calling verifyTwoFactor
- Guard the UI: only show/submit the code form when twoFactorChallenge is non-null; otherwise route the user back to the credentials screen
- If the challenge was consumed or lost (e.g. after navigation/reload), restart the flow from the credentials step to obtain a new challengeToken
- Check that handleTokenUpdate or an earlier verify attempt didn't clear the challenge before a retry is attempted
Example fix
// before
const verifyTwoFactor = async (code: string) => {
if (!twoFactorChallenge) {
throw new Error("No 2FA challenge pending");
}
...
};
// after: caller checks state first
if (!twoFactorChallenge) {
setShowOtpInput(false);
await login(credentials); // restart flow to get a new challenge
} else {
await verifyTwoFactor(code);
} Defensive patterns
Strategy: type-guard
Validate before calling
const canVerify = () => twoFactorChallenge !== null && !!twoFactorChallenge.challengeToken;
if (!canVerify()) {
// restart the login flow instead of verifying
setShowOtp(false);
return;
}
await verifyTwoFactor(code); Type guard
interface TwoFactorChallenge { challengeToken: string; /* ... */ }
const isChallengePending = (
c: TwoFactorChallenge | null,
): c is TwoFactorChallenge => c !== null && typeof c.challengeToken === 'string'; Try / catch
try {
await verifyTwoFactor(code);
} catch (e) {
if (e instanceof Error && e.message === 'No 2FA challenge pending') {
resetToCredentialsStep(); // re-authenticate to get a new challenge
return;
}
throw e;
} Prevention
- Model login as an explicit state machine: 'credentials' -> 'otp' -> 'done'; only render OTP UI in the otp state
- Clear and re-acquire the challenge on any navigation, reload, or cancel action
- Disable the submit button until the OTP form is visible with a live challenge
When it happens
Trigger: Calling verifyTwoFactor(code) before a successful first-stage login that returned a challenge, calling it twice (the challenge is cleared after the first attempt or after cancelTwoFactor), or after a page reload/state reset where the in-memory twoFactorChallenge variable is null again.
Common situations: UI state bugs where the 2FA code input is shown on the wrong screen, stale rendered forms after the challenge expired or was consumed, direct programmatic calls to the verify function out of order, or a first-factor login failure that the UI ignored before showing the OTP input.
Related errors
- Database config does not exist! Please read the instructions
- Plugin not found
- No files were uploaded
- Unknown DNS provider '${certificate.meta.dns_provider}'
AI-assisted analysis of NginxProxyManager/nginx-proxy-manager@934a3fafe5 (2026-08-27).
Data as JSON: /api/errors/07107a465bdafa22.
Report an issue: GitHub.