Skip to main content

Authenticator Login

Overviewโ€‹

Login using Time-based One-Time Password (TOTP) codes from authenticator apps. This guide assumes authenticator setup is already complete.

Prerequisitesโ€‹

  • User has completed authenticator setup
  • User has authenticator app installed and configured
  • Secret key is stored in user's account

Login Flowโ€‹

Step 1: Initial Login Requestโ€‹

POST /api/BPMSelfService/commands/SelfAdminLoginCommand
{
"username": "admin@example.com",
"password": "SecurePassword123!",
"verificationMethodType": 3
}

Step 2: Server Responseโ€‹

Since authenticator is already set up:

{
"status": "success",
"message": "Please enter authenticator code",
"data": {
"requiresSetup": false,
"requiresOtp": true
}
}

Step 3: User Opens Authenticator Appโ€‹

User opens their authenticator app and finds the BankLingo entry, which displays a 6-digit code that refreshes every 30 seconds.

Step 4: Validate TOTP Codeโ€‹

POST /api/BPMSelfService/commands/ValidateOtpCommand
{
"userId": 123,
"otpCode": "123456",
"verificationMethodType": 3
}

Step 5: Successful Authenticationโ€‹

{
"status": "success",
"message": "Login successful",
"data": {
"token": "eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9...",
"refreshToken": "...",
"expiresIn": 3600,
"user": {
"id": 123,
"username": "admin",
"email": "admin@example.com"
}
}
}

Complete Frontend Exampleโ€‹

React Exampleโ€‹

function AuthenticatorLogin() {
const [step, setStep] = useState('credentials'); // 'credentials' | 'totp'
const [credentials, setCredentials] = useState({ username: '', password: '' });
const [totpCode, setTotpCode] = useState('');
const [error, setError] = useState('');
const [userId, setUserId] = useState(null);

// Step 1: Submit credentials
const handleCredentialsSubmit = async (e) => {
e.preventDefault();

try {
const response = await fetch('/api/BPMSelfService/commands/SelfAdminLoginCommand', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
...credentials,
verificationMethodType: 3
})
});

const result = await response.json();

if (result.data.requiresSetup) {
// Redirect to setup flow
window.location.href = '/authenticator-setup';
} else if (result.data.requiresOtp) {
setUserId(result.data.userId);
setStep('totp');
}
} catch (err) {
setError('Login failed. Please try again.');
}
};

// Step 2: Validate TOTP code
const handleTotpSubmit = async (e) => {
e.preventDefault();

try {
const response = await fetch('/api/BPMSelfService/commands/ValidateOtpCommand', {
method: 'POST',
headers: { 'Content-Type': 'application/json' },
body: JSON.stringify({
userId: userId,
otpCode: totpCode,
verificationMethodType: 3
})
});

const result = await response.json();

if (result.status === 'success') {
// Store token and redirect
localStorage.setItem('jwt_token', result.data.token);
window.location.href = '/dashboard';
} else {
setError('Invalid code. Please try again.');
setTotpCode('');
}
} catch (err) {
setError('Validation failed. Please try again.');
}
};

if (step === 'credentials') {
return (
<form onSubmit={handleCredentialsSubmit}>
<h2>Admin Login</h2>
<input
type="text"
placeholder="Username"
value={credentials.username}
onChange={(e) => setCredentials({...credentials, username: e.target.value})}
/>
<input
type="password"
placeholder="Password"
value={credentials.password}
onChange={(e) => setCredentials({...credentials, password: e.target.value})}
/>
<button type="submit">Continue</button>
{error && <div className="error">{error}</div>}
</form>
);
}

return (
<form onSubmit={handleTotpSubmit}>
<h2>Enter Authenticator Code</h2>
<p>Open your authenticator app and enter the 6-digit code for BankLingo</p>
<input
type="text"
placeholder="000000"
value={totpCode}
onChange={(e) => setTotpCode(e.target.value.replace(/\D/g, ''))}
maxLength={6}
autoFocus
/>
<button type="submit" disabled={totpCode.length !== 6}>
Verify
</button>
{error && <div className="error">{error}</div>}
<a href="#" onClick={() => setStep('credentials')}>รขโ€ ย Back to login</a>
</form>
);
}

C# Desktop Exampleโ€‹

Code Removed

Implementation details removed for security.

Contact support for implementation guidance.

Code Validationโ€‹

Time Windowโ€‹

  • TOTP codes are valid for 30 seconds
  • System accepts codes within ร‚ยฑ30 second window
  • Total acceptance window: 90 seconds

Example Timelineโ€‹

Time:    00:00    00:30    01:00    01:30
Code: 123456 789012 456789 ...
[-------- Valid --------]
[-30s to +30s window]

Error Handlingโ€‹

Invalid Codeโ€‹

{
"status": "error",
"message": "Invalid authenticator code",
"errorCode": "AUTH_002"
}

Common causes:

  • User entered wrong code
  • Code expired (past 30-second window)
  • Time synchronization issue

Account Lockedโ€‹

{
"status": "error",
"message": "Account locked due to multiple failed attempts",
"errorCode": "AUTH_004",
"lockedUntil": "2025-01-15T14:30:00Z"
}

After 5 failed attempts, account is temporarily locked (15 minutes).

Best Practicesโ€‹

User Experienceโ€‹

  1. Auto-submit on 6 digits: Submit form automatically when 6 digits entered
  2. Show countdown: Display time remaining for current code
  3. Clear on failure: Clear input field after failed attempt
  4. Helpful errors: Show specific error messages
  5. Back button: Allow user to return to credentials step

Securityโ€‹

  1. Rate limiting: Limit validation attempts (5 per 15 minutes)
  2. Account lockout: Temporary lockout after failed attempts
  3. Audit logging: Log all authentication attempts
  4. HTTPS only: Never transmit codes over HTTP
  5. Token security: Store JWT securely (HttpOnly cookies)

Time Synchronizationโ€‹

  • Server and user device clocks must be reasonably synchronized
  • System tolerates ร‚ยฑ30 seconds drift
  • Recommend enabling automatic time sync on devices

Troubleshootingโ€‹

ProblemSolution
Code always rejectedCheck device time synchronization
"Account locked" errorWait 15 minutes or contact admin
Code expired messageEnter new code immediately after it appears
Lost authenticatorContact admin for account recovery

See Alsoโ€‹