Bỏ qua

Keycloak Login Issue - "Account is not fully set up" Fix

Problem Summary

When attempting to login via /auth/login endpoint, you're receiving:

{
  "message": "Account is not fully set up in Keycloak (required actions pending)...",
  "code": "UNAUTHORIZED",
  "statusCode": 401
}

Root Cause

The issue is caused by Keycloak's default "Direct Grant" authentication flow which includes a Conditional OTP requirement. Even though:

  • User has emailVerified: true
  • User has requiredActions: [] (empty)
  • Password is set as non-temporary

Keycloak still returns "Account is not fully set up" because the built-in Direct Grant flow has OTP (One-Time Password) configured as CONDITIONAL with "user configured" requirement. This means if the user hasn't explicitly configured OTP, Keycloak considers the account setup incomplete.

Investigation Results

From the Keycloak Admin API check:

# User details - looks correct
{
  "email": "nhatsang0101@gmail.com",
  "emailVerified": true,
  "enabled": true,
  "requiredActions": []  # Empty - no pending actions
}

# Direct Grant Flow - THE PROBLEM
{
  "displayName": "Direct Grant - Conditional OTP",
  "requirement": "CONDITIONAL",  # This causes the issue
  "providerId": "conditional-user-configured"
}

Solutions

I've already updated the config/keycloak/realm-export.json file to include a custom Direct Grant flow without OTP. To apply this:

  1. Stop Keycloak and all services:

bash docker-compose -f docker-compose.dev.yml down

  1. Remove Keycloak volumes to force reimport:

bash docker volume rm football-booking-backend-boilerplate_postgres-keycloak-data

  1. Restart services:

bash docker-compose -f docker-compose.dev.yml up -d

  1. Verify the fix: bash curl -X POST "http://localhost:8090/realms/football-booking/protocol/openid-connect/token" \ -H "Content-Type: application/x-www-form-urlencoded" \ -d "grant_type=password&client_id=api-gateway&client_secret=api-gateway-secret-key-12345&username=nhatsang0101@gmail.com&password=Trannhatsang10**"

Solution 2: Manual Fix via Keycloak Admin Console (Without Restart)

Since built-in flows cannot be modified, we need to create a custom flow:

  1. Access Keycloak Admin Console:
  2. URL: http://localhost:8090/admin
  3. Username: admin
  4. Password: admin123

  5. Navigate to Authentication:

  6. Select realm: football-booking
  7. Click "Authentication" in the left sidebar

  8. Copy the Direct Grant flow:

  9. Find "direct grant" flow
  10. Click the three-dot menu → "Duplicate"
  11. Name it: direct grant no otp

  12. Modify the copied flow:

  13. Click on the new flow direct grant no otp
  14. Find "Direct Grant - Conditional OTP" execution
  15. Click the three-dot menu → "Delete"
  16. Or change requirement to "DISABLED"

  17. Set as default:

  18. Go to "Bindings" tab at the top
  19. For "Direct Grant Flow", select direct grant no otp
  20. Click "Save"

  21. Test the login: bash curl -X POST "http://localhost:3000/auth/login" \ -H "Content-Type: application/json" \ -d '{"email":"nhatsang0101@gmail.com","password":"Trannhatsang10**"}'

Solution 3: Quick Fix - Manual User Configuration (Temporary)

If you want to keep OTP enabled but fix the current user:

  1. Reset password ensuring it's non-temporary:

bash # Already done - password was reset with temporary:false

  1. Ensure no required actions:

bash # Already done - requiredActions set to []

  1. Clear user sessions:
  2. Go to Keycloak Admin Console
  3. Users → Find user → Sessions tab
  4. Click "Sign out all sessions"

Why This Happens

Keycloak's Direct Grant flow (OAuth2 Resource Owner Password Credentials) by default includes:

  1. Username validation
  2. Password validation
  3. Conditional OTP - This checks if user has OTP configured
  4. If configured: requires OTP code
  5. If NOT configured: returns "Account is not fully set up"

This is a security feature to encourage OTP usage, but for development/testing it can be disabled.

For Development:

  • Use Solution 1 (Realm Configuration) - Already implemented in the updated realm-export.json

For Production:

  • Keep OTP enabled for security
  • Either:
  • Require all users to set up OTP
  • Use Solution 2 to create optional OTP flow
  • Implement TOTP setup flow in your application

Testing After Fix

  1. Test with curl:

bash curl -X POST "http://localhost:3000/auth/login" \ -H "Content-Type: application/json" \ -d '{ "email": "nhatsang0101@gmail.com", "password": "Trannhatsang10**" }'

  1. Expected successful response:

json { "data": { "accessToken": "eyJhbGc...", "refreshToken": "eyJhbGc...", "expiresIn": 300, "user": { "userId": "...", "email": "nhatsang0101@gmail.com", "name": "Tran Nhat Sang", "role": "customer" } }, "message": "Success", "success": true }

  1. Test with Swagger UI:
  2. Go to http://localhost:3000/api/docs
  3. Find POST /auth/login
  4. Execute with your credentials
  5. Should return 201 with token

Additional Notes

  • The issue affects ALL users created via the registration endpoint if Keycloak's default Direct Grant flow is used
  • The KeycloakService.adminCreateUser() method in user-service sets:
  • emailVerified: true (in development)
  • requiredActions: []
  • temporary: false for password
  • But this doesn't matter if the authentication flow itself requires OTP configuration

Files Modified

  • config/keycloak/realm-export.json - Added custom authentication flow without OTP

Next Steps

Choose one of the solutions above and apply it. Solution 1 is already prepared and just requires a Keycloak restart with volume removal.