Java

Add 2FA to Your Java Application

A complete guide to integrating Google Authenticator using AuthenticatorAPI.com

Also available in: PHP Python C# JavaScript

This guide shows how to add Google Authenticator-compatible two-factor authentication to Java applications. We cover both a plain Java utility class and a full Spring Boot integration with Spring Security. No third-party TOTP libraries are required — just Java's standard HTTP client.

👉 Prerequisites: Java 11+ (for the built-in HttpClient). For Spring Boot, version 3.x is used in examples. A JPA-compatible database for storing user secrets.
Core Utility Class (Plain Java)
1

TotpService.java

A self-contained service class that wraps all calls to the AuthenticatorAPI. This works in any Java application — Spring, Jakarta EE, or plain Java.

import java.net.URI;
import java.net.URLEncoder;
import java.net.http.HttpClient;
import java.net.http.HttpRequest;
import java.net.http.HttpResponse;
import java.nio.charset.StandardCharsets;
import java.security.SecureRandom;
import java.time.Duration;

public class TotpService {

    private static final String BASE32_ALPHABET = "ABCDEFGHIJKLMNOPQRSTUVWXYZ234567";
    private static final String API_BASE = "https://www.authenticatorApi.com";
    private static final SecureRandom RNG = new SecureRandom();

    private final HttpClient httpClient;

    public TotpService() {
        this.httpClient = HttpClient.newBuilder()
            .connectTimeout(Duration.ofSeconds(10))
            .build();
    }

    /** Generates a cryptographically random 32-character Base32 secret. */
    public String generateSecret() {
        return generateSecret(32);
    }

    public String generateSecret(int length) {
        StringBuilder sb = new StringBuilder(length);
        byte[] bytes = new byte[length];
        RNG.nextBytes(bytes);
        for (byte b : bytes) {
            sb.append(BASE32_ALPHABET.charAt((b & 0xFF) % 32));
        }
        return sb.toString();
    }

    /** Returns the HTML img tag for the QR code setup page. */
    public String getQrCodeHtml(String appName, String userInfo, String secret)
            throws Exception {
        String url = API_BASE + "/pair.aspx"
            + "?AppName=" + encode(appName)
            + "&AppInfo=" + encode(userInfo)
            + "&SecretCode=" + encode(secret);

        return get(url);
    }

    /** Returns true if the 6-digit pin is valid for the given secret. */
    public boolean validatePin(String pin, String secret) throws Exception {
        if (!pin.matches("\\d{6}")) return false;

        String url = API_BASE + "/Validate.aspx"
            + "?Pin=" + pin
            + "&SecretCode=" + encode(secret);

        String response = get(url).trim();
        return response.equalsIgnoreCase("true");
    }

    private String get(String url) throws Exception {
        HttpRequest request = HttpRequest.newBuilder()
            .uri(URI.create(url))
            .timeout(Duration.ofSeconds(10))
            .GET()
            .build();
        return httpClient
            .send(request, HttpResponse.BodyHandlers.ofString())
            .body();
    }

    private String encode(String value) {
        return URLEncoder.encode(value, StandardCharsets.UTF_8);
    }
}
Spring Boot Integration
2

Register as a Spring Bean

// TotpConfig.java
import org.springframework.context.annotation.Bean;
import org.springframework.context.annotation.Configuration;

@Configuration
public class TotpConfig {

    @Bean
    public TotpService totpService() {
        return new TotpService();
    }
}
3

Spring MVC Controller

// TwoFactorController.java
import org.springframework.stereotype.Controller;
import org.springframework.ui.Model;
import org.springframework.web.bind.annotation.*;
import jakarta.servlet.http.HttpSession;

@Controller
@RequestMapping("/2fa")
public class TwoFactorController {

    private final TotpService totpService;
    private final UserService userService;

    public TwoFactorController(TotpService totpService, UserService userService) {
        this.totpService = totpService;
        this.userService  = userService;
    }

    // GET /2fa/setup — show the QR code
    @GetMapping("/setup")
    public String setup(HttpSession session, Model model) throws Exception {
        String secret = totpService.generateSecret();
        session.setAttribute("pending2faSecret", secret);

        String username = /* current principal */ "";
        model.addAttribute("qrHtml", totpService.getQrCodeHtml("MyApp", username, secret));
        model.addAttribute("secret", secret);
        return "2fa/setup";
    }

    // POST /2fa/setup — confirm the user scanned correctly
    @PostMapping("/setup")
    public String confirmSetup(@RequestParam String pin, HttpSession session, Model model) throws Exception {
        String secret = (String) session.getAttribute("pending2faSecret");
        if (totpService.validatePin(pin, secret)) {
            userService.saveTotpSecret(/* userId */, secret);
            session.removeAttribute("pending2faSecret");
            return "redirect:/dashboard";
        }
        model.addAttribute("error", "Invalid code — please try again.");
        return "2fa/setup";
    }

    // POST /2fa/verify — called during login
    @PostMapping("/verify")
    public String verify(@RequestParam String pin, HttpSession session, Model model) throws Exception {
        String secret = userService.getDecryptedTotpSecret(/* userId */);
        if (totpService.validatePin(pin, secret)) {
            session.setAttribute("2faVerified", true);
            return "redirect:/dashboard";
        }
        model.addAttribute("error", "Invalid code — please try again.");
        return "2fa/verify";
    }
}
4

Spring Security Filter

Add a filter that intercepts requests after password authentication and redirects users who haven't yet completed their TOTP check.

// TwoFactorFilter.java
import jakarta.servlet.*;
import jakarta.servlet.http.*;
import org.springframework.web.filter.OncePerRequestFilter;

public class TwoFactorFilter extends OncePerRequestFilter {

    private static final String[] EXEMPT = {"/2fa/", "/login", "/logout", "/css/", "/js/"};

    @Override
    protected void doFilterInternal(HttpServletRequest req, HttpServletResponse res, FilterChain chain)
            throws ServletException, IOException {

        HttpSession session = req.getSession(false);
        String path = req.getServletPath();
        boolean isExempt = false;
        for (String e : EXEMPT) if (path.startsWith(e)) { isExempt = true; break; }

        if (!isExempt && session != null
                && session.getAttribute("userId") != null
                && !Boolean.TRUE.equals(session.getAttribute("2faVerified"))) {
            res.sendRedirect("/2fa/verify");
            return;
        }
        chain.doFilter(req, res);
    }
}
Security Checklist for Java