This project is a Keycloak authentication plugin that provides multi-factor authentication (MFA) using various methods including SMS, Email, Telegram, and TOTP. The codebase follows modern design patterns, providing an extensible, maintainable, and testable architecture.
- Supports Keycloak version 26.x and above
- Built with Java 11+
- Tested with PostgreSQL as the database backend
- Clone the repository
- Build using Maven:
mvn clean package
- The build will produce a JAR file in the
target
directory
-
Copy the JAR file to the Keycloak
providers
directory:cp target/keycloak-mfa-plugin-1.0-SNAPSHOT.jar /path/to/keycloak/providers/
-
Restart Keycloak or build a new image if using Docker:
# For standalone Keycloak /path/to/keycloak/bin/kc.sh build /path/to/keycloak/bin/kc.sh start-dev # For Docker-based setup docker-compose -f docker/docker-compose.yml up --build
After deploying Keycloak, you can access the login console:
- URL: http://localhost:3220/auth/ (The port may vary based on your
docker-compose.yml
configuration) - Default credentials:
- Username:
admin
- Password:
admin
- Username:
-
Log in to the Keycloak at http://localhost:3220/auth/
-
Go to Authentication → Flows
-
Duplicate the "Browser" flow or create a new flow
-
Add the "Custom MFA Authentication" as an execution step
-
Click the gear icon on the new execution to configure it:
- Twilio Account SID, Auth Token, and Verify Service SID for SMS
- Telegram Bot Token for Telegram notifications
- Email settings (uses Keycloak's email configuration by default)
- OTP expiration time
-
Set the flow as "Required" or "Alternative" based on your needs
-
Bind the new flow to your realm's browser flow
The MFA plugin includes a Telegram bot service that allows users to receive one-time passwords via Telegram.
- Open Telegram and search for "BotFather" (@BotFather)
- Start a chat with BotFather and send the
/newbot
command - Follow the instructions to create a new bot:
- Provide a name for your bot (e.g., "MFA OTP Bot")
- Provide a username for your bot (e.g., "mfa_otp_bot")
- Once created, BotFather will provide you with a token. This token is required for the bot to function.
Example:
Use this token to access the HTTP API:
123456789:ABCDefGhIJKlmnOPQRstUVwxyz
export TELEGRAM_BOT_TOKEN=123456789:ABCDefGhIJKlmnOPQRstUVwxyz
The bot username is configured in the messages_en.properties
file. The default setting is @KeycloakMultiMFA
, but you should update this to match your actual bot username:
- Open
src/main/resources/theme/base/login/messages/messages_en.properties
- Find the line with
telegrambotusername=
and update it with your bot's username - Rebuild and redeploy the Keycloak service for the changes to take effect
When users select Telegram as their MFA method, they will:
- Be prompted to contact the Telegram bot
- The bot will respond with their Chat ID
- Users enter this Chat ID in the MFA configuration screen
- For future logins, the system will send one-time codes to the user via the Telegram bot
A Docker Compose file is included for easy deployment with support for all services including the Telegram bot:
# Start the environment
docker-compose -f docker/docker-compose.yml up --build
# View logs
docker-compose logs -f
# Stop the environment
docker-compose down
The Docker setup includes:
- Keycloak server with the MFA plugin pre-installed
- PostgreSQL database
- Telegram bot service for MFA via Telegram
- Proper configuration for all services
You can test the MFA functionality by:
- Creating a user in the realm through the Keycloak Admin Console
- Enabling MFA for the user through the user account
- Navigating to the login page at http://localhost:3220/
- Logging in with the user's credentials
- Selecting an MFA method and configuring it if necessary
- Verifying the authentication with the selected method
graph TD
User((User)) --> KC[Keycloak]
KC --> Auth[CustomMFAAuthenticator]
Auth --> Factory[MFAProviderFactory]
Factory --> Provider{MFA Provider}
Provider --> SMS[SMSProvider]
Provider --> Email[EmailProvider]
Provider --> Telegram[TelegramProvider]
Provider --> TOTP[TOTPProvider]
SMS --> Twilio[TwilioServiceAdapter]
Email --> EmailService[EmailServiceAdapter]
Telegram --> TelegramBot[TelegramServiceAdapter]
Auth --> EventMgr[AuthEventManager]
EventMgr --> Listeners[Event Listeners]
subgraph Flow[Authentication Flow]
Select[Method Selection] --> Config[Configuration]
Config --> Verify[Code Verification]
Verify --> Success[Authentication Success]
end
graph TD
subgraph Strategy[Strategy Pattern]
StrategyInterface[MFAProvider Interface]
StrategyAbstract[AbstractMFAProvider]
Strategy1[SMSProvider]
Strategy2[EmailProvider]
Strategy3[TelegramProvider]
Strategy4[TOTPProvider]
StrategyInterface --> StrategyAbstract
StrategyAbstract --> Strategy1
StrategyAbstract --> Strategy2
StrategyAbstract --> Strategy3
StrategyAbstract --> Strategy4
end
subgraph Factory[Factory Pattern]
FactoryClass[MFAProviderFactory]
Client[CustomMFAAuthenticator]
Product[MFAProvider]
Client --> FactoryClass
FactoryClass --> Product
end
graph TD
subgraph Adapter[Adapter Pattern]
AdapterInterface[ExternalServiceAdapter]
Adapter1[TwilioServiceAdapter]
Adapter2[EmailServiceAdapter]
Adapter3[TelegramServiceAdapter]
AdapterInterface --> Adapter1
AdapterInterface --> Adapter2
AdapterInterface --> Adapter3
end
subgraph Observer[Observer Pattern]
Subject[AuthEventManager]
Observer1[LoggingEventListener]
ObserverInterface[AuthEventListener]
EventClass[AuthEvent]
Subject --> ObserverInterface
ObserverInterface --> Observer1
Subject --> EventClass
end
graph TD
subgraph Template[Template Method]
TemplateClass[AbstractMFAProvider]
ConcreteClass[Concrete Providers]
TemplateClass -- "defines flow" --> ConcreteClass
ConcreteClass -- "override specific steps" --> TemplateClass
end
subgraph Singleton[Singleton Pattern]
Single1[OTPGenerator]
Single2[MFAProviderFactory]
Single3[AuthEventManager]
Single4[Service Adapters]
end
classDiagram
MFAProvider <|.. AbstractMFAProvider
AbstractMFAProvider <|-- SMSProvider
AbstractMFAProvider <|-- EmailProvider
AbstractMFAProvider <|-- TelegramProvider
AbstractMFAProvider <|-- TOTPProvider
ExternalServiceAdapter <|.. TwilioServiceAdapter
ExternalServiceAdapter <|.. EmailServiceAdapter
ExternalServiceAdapter <|.. TelegramServiceAdapter
SMSProvider --> TwilioServiceAdapter
EmailProvider --> EmailServiceAdapter
TelegramProvider --> TelegramServiceAdapter
CustomMFAAuthenticator --> MFAProviderFactory
MFAProviderFactory --> MFAProvider
CustomMFAAuthenticator --> AuthEventManager
AuthEventManager --> AuthEventListener
AuthEventListener <|.. LoggingEventListener
class MFAProvider {
<<interface>>
+isConfiguredFor(user)
+sendVerificationCode(context, user)
+verifyCode(context, user, code)
+configure(context, user, configValue)
+getType()
+getDisplayName()
}
class AbstractMFAProvider {
<<abstract>>
#config: MFAConfig
#otpGenerator: OTPGenerator
+sendVerificationCode(context, user)
+verifyCode(context, user, code)
#sendCode(context, user, code)
}
class ExternalServiceAdapter {
<<interface>>
+isConfigured()
+sendVerificationCode(recipient, code)
+verifyCode(recipient, code)
}
class MFAProviderFactory {
-instance: MFAProviderFactory
+getInstance()
+createProvider(type, config)
}
class CustomMFAAuthenticator {
-providerFactory: MFAProviderFactory
-eventManager: AuthEventManager
+authenticate(context)
+action(context)
}
The Strategy Pattern defines a family of algorithms, encapsulates each one, and makes them interchangeable. In this project, it's used to handle different MFA methods:
- Interface:
MFAProvider
- Abstract Class:
AbstractMFAProvider
- Concrete Implementations:
SMSProvider
EmailProvider
TelegramProvider
TOTPProvider
This allows each provider to implement method-specific logic while sharing common functionality through the abstract class.
The Factory Pattern provides an interface for creating objects without specifying their concrete classes.
- Factory:
MFAProviderFactory
This factory creates the appropriate MFA provider based on the requested type, hiding the instantiation logic from the client code.
The Template Method Pattern defines the skeleton of an algorithm, deferring some steps to subclasses.
- Abstract Class:
AbstractMFAProvider
- Template Methods:
sendVerificationCode()
- Defines the flow for sending codesverifyCode()
- Defines the flow for verifying codes
This allows consistent handling while letting subclasses implement method-specific steps.
The Singleton Pattern ensures a class has only one instance and provides a global point to access it.
- Singleton Classes:
MFAProviderFactory
AuthEventManager
OTPGenerator
- Service adapters (TwilioServiceAdapter, TelegramServiceAdapter, EmailServiceAdapter)
This prevents unnecessary creation of objects that should be shared.
The Adapter Pattern converts the interface of a class into another interface clients expect.
- Interface:
ExternalServiceAdapter
- Implementations:
TwilioServiceAdapter
TelegramServiceAdapter
EmailServiceAdapter
This provides a consistent interface for working with external services.
The Observer Pattern defines a one-to-many dependency between objects so that when one object changes state, all its dependents are notified.
- Event Class:
AuthEvent
- Event Manager:
AuthEventManager
- Event Listener Interface:
AuthEventListener
- Example Listener:
LoggingEventListener
This allows for logging, metrics, and other cross-cutting concerns without cluttering the core code.
The Telegram bot service is organized as follows:
docker/telegram-bot/
├── Dockerfile # Container configuration for the bot
├── package.json # Node.js dependencies
└── bot.js # Bot implementation
- Extensibility: Adding new MFA methods is easy - just implement a new provider.
- Testability: The modular design makes unit testing much simpler.
- Separation of Concerns: Each class has a single responsibility.
- Code Reuse: Common logic is shared through abstract classes and utilities.
- Maintainability: The clean architecture makes the code easier to understand and modify.
- Error Handling: Consistent exception handling across components.
- Logging: Centralized logging through events.
com.example.mfa/
├── authenticator/
│ ├── CustomMFAAuthenticator.java # Main authenticator
│ └── CustomMFAAuthenticatorFactory.java
├── config/
│ └── MFAConfig.java # Configuration
├── provider/
│ ├── MFAProvider.java # Interface for all providers
│ ├── AbstractMFAProvider.java # Abstract base class
│ ├── MFAException.java # Custom exception
│ ├── SMSProvider.java # Implementation for SMS
│ ├── EmailProvider.java # Implementation for Email
│ ├── TelegramProvider.java # Implementation for Telegram
│ └── TOTPProvider.java # Implementation for TOTP
├── factory/
│ └── MFAProviderFactory.java # Factory for creating providers
├── service/
│ ├── ExternalServiceAdapter.java # Interface for external services
│ ├── TwilioServiceAdapter.java # Implementation for Twilio
│ ├── TelegramServiceAdapter.java # Implementation for Telegram
│ └── EmailServiceAdapter.java # Implementation for Email
├── event/
│ ├── AuthEvent.java # Event class
│ ├── AuthEventListener.java # Listener interface
│ ├── AuthEventManager.java # Event manager
│ └── LoggingEventListener.java # Example listener
└── util/
├── OTPGenerator.java # Utility for OTP generation
└── ValidationUtil.java # Validation utilities
To add a new MFA method:
- Create a new implementation of
MFAProvider
- Add the new method type to the
MFAProviderFactory
- Update the UI templates if necessary
The main authenticator doesn't need to be modified when adding new methods, as it delegates to the appropriate provider through the factory.
- Twilio account with Account SID and Auth Token
- Twilio Verify Service set up with SMS capability
- Twilio Verify Service SID
- Telegram Bot created through BotFather
- Telegram Bot Token
- Users will need to message the bot to get their Chat ID
- Working SMTP configuration in Keycloak
- Or custom SMTP configuration provided to the plugin
- Uses Keycloak's built-in TOTP implementation
- Users will need an authenticator app like Google Authenticator, Microsoft Authenticator, or Authy
- If MFA methods stay in "development mode" despite configuration:
- Check that the exact key names match between the factory and adapters
- Verify authentication flow configuration in Keycloak
- Enable DEBUG logging for detailed configuration tracing
- If users aren't redirected to TOTP setup:
- Ensure the CONFIGURE_TOTP required action is enabled in your realm
- Check that TOTP is properly set up in the authentication flow
- Verify Keycloak's email configuration is working
- Test email sending through Keycloak's test feature
- Check spam folders for MFA verification emails
- Bot doesn't respond: Check that the
TELEGRAM_BOT_TOKEN
is correctly set in the.env
file - Connection errors: Ensure that your server can reach the Telegram API (api.telegram.org)
- Users can't find the bot: Verify that the username in
messages_en.properties
matches the actual bot username
- If you can't access the Keycloak UI at http://localhost:3220/:
- Verify that the Keycloak container is running (
docker ps
) - Check container logs for startup errors (
docker logs keycloak
) - Confirm port mapping in your docker-compose.yml matches the URL you're trying to access
- Verify that the Keycloak container is running (
- Enable DEBUG level logging for
com.example.mfa
package - Examine
AuthEvent
logs for authentication flow issues - Service adapters log connection and delivery attempts
- "Configuration error occurred": Check service credentials
- "Verification session has expired": Increase OTP timeout
- "Failed to send verification code": Check service connectivity
- "Invalid verification code": Ensure clock synchronization for TOTP
- The Telegram bot token and Twilio credentials are sensitive. Protect them as you would any API key.
- In production environments, consider using a secrets management solution rather than environment variables in a
.env
file. - The Telegram bot is public and can be messaged by anyone. It only returns Chat IDs and does not expose any sensitive information.
- OTP codes expire after a configurable timeout period (default 5 minutes)
- Failed verification attempts are limited to prevent brute force attacks