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

  1. Ensure login() (first stage) succeeded and set twoFactorChallenge before rendering the OTP input or calling verifyTwoFactor
  2. Guard the UI: only show/submit the code form when twoFactorChallenge is non-null; otherwise route the user back to the credentials screen
  3. If the challenge was consumed or lost (e.g. after navigation/reload), restart the flow from the credentials step to obtain a new challengeToken
  4. 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

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


AI-assisted analysis of NginxProxyManager/nginx-proxy-manager@934a3fafe5 (2026-08-27). Data as JSON: /api/errors/07107a465bdafa22. Report an issue: GitHub.