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
Solution 1: Update Realm Configuration (Recommended for Development)
I've already updated the config/keycloak/realm-export.json file to include a custom Direct Grant flow without OTP. To apply this:
- Stop Keycloak and all services:
bash
docker-compose -f docker-compose.dev.yml down
- Remove Keycloak volumes to force reimport:
bash
docker volume rm football-booking-backend-boilerplate_postgres-keycloak-data
- Restart services:
bash
docker-compose -f docker-compose.dev.yml up -d
- 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:
- Access Keycloak Admin Console:
- URL: http://localhost:8090/admin
- Username:
admin -
Password:
admin123 -
Navigate to Authentication:
- Select realm:
football-booking -
Click "Authentication" in the left sidebar
-
Copy the Direct Grant flow:
- Find "direct grant" flow
- Click the three-dot menu → "Duplicate"
-
Name it:
direct grant no otp -
Modify the copied flow:
- Click on the new flow
direct grant no otp - Find "Direct Grant - Conditional OTP" execution
- Click the three-dot menu → "Delete"
-
Or change requirement to "DISABLED"
-
Set as default:
- Go to "Bindings" tab at the top
- For "Direct Grant Flow", select
direct grant no otp -
Click "Save"
-
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:
- Reset password ensuring it's non-temporary:
bash
# Already done - password was reset with temporary:false
- Ensure no required actions:
bash
# Already done - requiredActions set to []
- Clear user sessions:
- Go to Keycloak Admin Console
- Users → Find user → Sessions tab
- Click "Sign out all sessions"
Why This Happens
Keycloak's Direct Grant flow (OAuth2 Resource Owner Password Credentials) by default includes:
- Username validation
- Password validation
- Conditional OTP - This checks if user has OTP configured
- If configured: requires OTP code
- 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.
Recommended Approach
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
- Test with curl:
bash
curl -X POST "http://localhost:3000/auth/login" \
-H "Content-Type: application/json" \
-d '{
"email": "nhatsang0101@gmail.com",
"password": "Trannhatsang10**"
}'
- 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
}
- Test with Swagger UI:
- Go to http://localhost:3000/api/docs
- Find POST
/auth/login - Execute with your credentials
- 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 inuser-servicesets: emailVerified: true(in development)requiredActions: []temporary: falsefor 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.