You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

自定义登录页集成Keycloak双因素认证:REST API需求问询

Got it, let's break down how to implement your custom login flow with Keycloak's TOTP (Google Authenticator) integration—no redirects, fully JS-based, with Keycloak acting as a backend service. Here's everything you need:

Keycloak REST API for Custom TOTP-Aware Login Flow

1. First-Time Login: Fetch and Display TOTP QR Code

When a user logs in with valid credentials but hasn't set up TOTP yet, Keycloak will reject the initial token request and prompt for TOTP configuration. We'll use this session to fetch the QR code for Google Authenticator.

Step-by-Step Workflow & APIs

  • Initial Password Login Attempt
    Send a POST request to the token endpoint. If TOTP isn't configured, you'll get a 400 Bad Request with an error about needing to set up OTP, plus session cookies needed for subsequent requests.

    const initialLogin = async (username, password) => {
      const response = await fetch('http://localhost:8080/auth/realms/Demo/protocol/openid-connect/token', {
        method: 'POST',
        headers: {'Content-Type': 'application/x-www-form-urlencoded'},
        body: new URLSearchParams({
          username,
          password,
          grant_type: 'password',
          client_id: 'your-client-id',
          client_secret: 'your-client-secret'
        })
      });
    
      if (!response.ok) {
        const error = await response.json();
        // Check if error is due to missing TOTP setup
        if (error.error === 'invalid_grant' && error.error_description.includes('configure OTP')) {
          // Extract session cookies from response headers
          const cookies = response.headers.get('Set-Cookie').split(';').map(c => c.split('=')[0]).join('; ');
          // Extract session code from WWW-Authenticate header
          const authHeader = response.headers.get('WWW-Authenticate').replace('Bearer ', '');
          const sessionCode = authHeader.match(/code="([^"]+)"/)[1];
          return {needsTotpSetup: true, cookies, sessionCode};
        }
        throw new Error(`Login failed: ${error.error_description}`);
      }
      // User already has TOTP setup, return tokens
      return await response.json();
    };
    
  • Fetch QR Code
    Use the session cookies and code to request the TOTP configuration page, then parse the HTML to extract the QR code URL:

    const getTotpQrCode = async (sessionCode, cookies) => {
      const response = await fetch(`http://localhost:8080/auth/realms/Demo/login-actions/required-action?key=CONFIGURE_TOTP&session_code=${sessionCode}&client_id=your-client-id`, {
        method: 'GET',
        headers: {'Cookie': cookies}
      });
    
      if (!response.ok) throw new Error('Failed to fetch TOTP configuration');
      
      const html = await response.text();
      const qrMatch = html.match(/<img[^>]+src="([^"]+)"[^>]+class="qr-code"/);
      if (!qrMatch) throw new Error('QR code not found in response');
      
      // Return full QR code URL
      return `http://localhost:8080${qrMatch[1]}`;
    };
    
  • Verify OTP & Complete Setup
    After the user scans the QR code and enters their OTP, submit it to finalize TOTP setup, then fetch the access token:

    const verifyTotpAndLogin = async (sessionCode, cookies, otpCode) => {
      // Submit OTP to complete TOTP setup
      await fetch(`http://localhost:8080/auth/realms/Demo/login-actions/required-action?key=CONFIGURE_TOTP&session_code=${sessionCode}&client_id=your-client-id`, {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Cookie': cookies
        },
        body: new URLSearchParams({otp: otpCode})
      });
    
      // Fetch access token now that TOTP is set up
      const tokenResponse = await fetch('http://localhost:8080/auth/realms/Demo/protocol/openid-connect/token', {
        method: 'POST',
        headers: {
          'Content-Type': 'application/x-www-form-urlencoded',
          'Cookie': cookies
        },
        body: new URLSearchParams({
          grant_type: 'password',
          client_id: 'your-client-id',
          client_secret: 'your-client-secret'
        })
      });
    
      if (!tokenResponse.ok) throw new Error('Failed to get access token after TOTP setup');
      return await tokenResponse.json();
    };
    

2. Subsequent Logins: Authenticate with Username, Password, and OTP

For users who already have TOTP set up, simply add the otp parameter to the standard token request:

const regularLoginWithOtp = async (username, password, otpCode) => {
  const response = await fetch('http://localhost:8080/auth/realms/Demo/protocol/openid-connect/token', {
    method: 'POST',
    headers: {'Content-Type': 'application/x-www-form-urlencoded'},
    body: new URLSearchParams({
      username,
      password,
      grant_type: 'password',
      client_id: 'your-client-id',
      client_secret: 'your-client-secret',
      otp: otpCode
    })
  });

  if (!response.ok) {
    const error = await response.json();
    throw new Error(`Login failed: ${error.error_description}`);
  }
  return await response.json();
};

Critical Notes

  • CORS Configuration: In your Keycloak admin console, go to Realm → Clients → Your Client → Settings and add your custom login page's domain to the Web Origins list to allow cross-domain requests.
  • Session Persistence: Keep the session cookies intact throughout the TOTP setup flow—Keycloak uses them to track the user's session state.
  • Error Handling: Expand the error checks to handle cases like invalid OTP, expired sessions, or incorrect credentials for a smoother user experience.

内容的提问来源于stack exchange,提问作者Ankur Singhal

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.05.27 09:31:25