Real World Example

Where You See This

  • Flipkart, Swiggy, Zepto: login with mobile number + SMS OTP — a custom provider exactly like ours.
  • Zerodha Kite, Groww: password + TOTP (or app-based PIN) as required by SEBI two-factor rules.
  • Gmail, GitHub, AWS console: password + TOTP or passkeys, and remember-me "Trust this device for 30 days".

ShopEase Logins

UserLogin methodSecond factorSession
Customer (app)Mobile OTP (custom provider) or Googlenone for browsing; OTP again for changing phone/email15-min JWT + rotating refresh token
SellerEmail + password (form login)TOTP mandatorysession + remember-me 14 days; payouts need full login
AdminEmail + passwordTOTP mandatory, office VPN onlysession, 30-min idle timeout, no remember-me
// Seller enrols in TOTP: generate a secret and show it as a QR code
@PostMapping("/api/seller/mfa/setup")
public MfaSetupResponse setup(@AuthenticationPrincipal UserDetails seller) {
    byte[] secret = new byte[20];
    secureRandom.nextBytes(secret);
    String base32 = Base32.encode(secret);                         // authenticator apps use Base32
    sellers.savePendingMfaSecret(seller.getUsername(), encryptor.encrypt(base32));  // encrypted at rest
    String otpauth = "otpauth://totp/ShopEase:" + seller.getUsername()
            + "?secret=" + base32 + "&issuer=ShopEase&digits=6&period=30";
    return new MfaSetupResponse(otpauth);                          // frontend renders this as a QR code
}

Note: The MFA secret is like a password the server must be able to read back, so it is encrypted (not hashed) at rest. Only after the seller types one valid code is the pending secret activated. Also give 10 one-time recovery codes (stored hashed) for a lost phone.