wa2fa is a Keycloak 26.x provider bundle that adds WhatsApp-based authentication features using the Meta WhatsApp Cloud API.
- WhatsApp OTP as a login step (
Authenticator) - Phone verification flow (
Required Action) - Login alerts on successful sign-in (
Event Listener) - Optional QR scan verification with webhook callback (
Realm Resource Provider) - Optional HTTP SMS fallback when WhatsApp delivery fails
Supported UI/message languages: en, hi, es, fr, de, ar, pt.
Version 1.1 is Meta-only.
- Keycloak:
26.x(Quarkus distribution) - Java:
17+ - Maven:
3.9+
- Copy and edit env file:
cp .env.example .env- Start stack:
docker compose up --build- Open Keycloak:
- URL:
http://localhost:8080 - Admin user:
admin - Admin password:
admin
- Build JAR:
mvn clean package- Copy provider:
cp target/wa2fa-1.1.jar /opt/keycloak/providers/- Rebuild Keycloak and start:
/opt/keycloak/bin/kc.sh build
/opt/keycloak/bin/kc.sh startAuthenticatorFactory:WhatsApp OTP (wa2fa)RequiredActionFactory:Verify Phone Number via WhatsApp (wa2fa)EventListenerProviderFactory:wa2fa-login-notificationRealmResourceProviderFactory:wa2fa
Runtime service registrations are in:
src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactorysrc/main/resources/META-INF/services/org.keycloak.authentication.RequiredActionFactorysrc/main/resources/META-INF/services/org.keycloak.events.EventListenerProviderFactorysrc/main/resources/META-INF/services/org.keycloak.services.resource.RealmResourceProviderFactory
You need these from Meta Business / WhatsApp Manager:
- Access Token
- Phone Number ID
- Approved template for OTP (default:
otp_message) - Approved template for login alerts (default:
login_notification)
- OTP template: OTP code parameter
- Login notification template:
username,timestamp,ip,browser
- Go to
Authentication -> Flows - Duplicate
Browserflow (recommended) - Add execution:
WhatsApp OTP (wa2fa) - Set requirement to
REQUIRED - Configure authenticator settings
Recommended structure:
Browser flow
Username Password Form (REQUIRED)
OTP sub-flow (REQUIRED)
WhatsApp OTP (wa2fa) (REQUIRED)
Do not keep a Condition - user configured gate in this OTP sub-flow if you want WhatsApp OTP consistently enforced.
- Go to
Authentication -> Required Actions - Enable:
Verify Phone Number via WhatsApp (wa2fa)
This flow writes/uses these user attributes:
phoneNumberphoneNumberVerifiedphoneNumberVerifiedValue
- Go to
Realm Settings -> Events - Add listener:
wa2fa-login-notification
Configured in the execution settings for WhatsApp OTP (wa2fa):
wa2fa.accessTokenwa2fa.phoneNumberIdwa2fa.apiVersionwa2fa.otpLengthwa2fa.otpExpirywa2fa.templateOtpwa2fa.templateLoginwa2fa.templateLoginLayoutwa2fa.defaultLanguagewa2fa.defaultCountryCodewa2fa.smsFallbackUrlwa2fa.smsFallbackMethodwa2fa.qrEnabledwa2fa.otpEnabledwa2fa.businessPhonewa2fa.webhookVerifyTokenwa2fa.appSecretwa2fa.maxResendwa2fa.qrAckVerifiedwa2fa.qrAckMismatchwa2fa.qrAckExpiredwa2fa.qrAckNoMatch
Exposed under the realm resource provider wa2fa:
GET /realms/{realm}/wa2fa/webhook(Meta webhook verification)POST /realms/{realm}/wa2fa/webhook(incoming message callback)GET /realms/{realm}/wa2fa/qr-status?token=...(polling endpoint)
If WA2FA_APP_SECRET / wa2fa.appSecret is set, webhook payload signature (X-Hub-Signature-256) is verified.
Core env vars (also available in .env.example):
WA2FA_ACCESS_TOKENWA2FA_PHONE_NUMBER_IDWA2FA_API_VERSIONWA2FA_TEMPLATE_OTPWA2FA_TEMPLATE_LOGINWA2FA_DEFAULT_LANGUAGEWA2FA_DEFAULT_COUNTRY_CODEWA2FA_OTP_EXPIRYWA2FA_LOGIN_NOTIFICATION_ENABLEDWA2FA_LOGIN_NOTIFICATION_ASYNCWA2FA_SMS_FALLBACK_URLWA2FA_SMS_FALLBACK_METHODWA2FA_QR_ENABLEDWA2FA_BUSINESS_PHONEWA2FA_WEBHOOK_VERIFY_TOKENWA2FA_APP_SECRET
- Delivery is attempted via WhatsApp first.
- If configured, SMS fallback is attempted on WhatsApp failure.
- Event details include channel/failure metadata for auditing.
src/main/java/com/wa2fa/
action/ # Required action (phone verification)
authenticator/ # WhatsApp OTP authenticator
event/ # Login notification listener
qr/ # Webhook + QR status resource
...shared services/utilities
src/main/resources/
META-INF/services/ # SPI registrations
theme-resources/ # FTL templates + i18n bundles + QR JS
- Provider not visible in Keycloak:
- Ensure JAR is in
/opt/keycloak/providers/ - Run
/opt/keycloak/bin/kc.sh build - Restart Keycloak
- OTP not sent:
- Verify token/phone-id values
- Verify template exists and is approved
- Check Keycloak logs for WhatsApp API errors
- QR flow not completing:
- Ensure webhook URL points to
/realms/{realm}/wa2fa/webhook - Ensure verify token matches
- Set
WA2FA_APP_SECRETfor signed webhook validation
- Phone rejected:
- Number is validated with libphonenumber
- Use E.164 (
+...) or setWA2FA_DEFAULT_COUNTRY_CODE
Compile only:
mvn -DskipTests compilePackage:
mvn clean package