on
Extending Keycloak (continued): using a custom email sender
Introduction
In the last post Extending Keycloak: adding API key authentication, I took a glimpse at how to extend Keycloak with a custom authentication method. I would like to continue in this post series about Keycloak with another useful customization: a custom email sender.
As mentioned before, Keycloak offers tons of customization capabilities. Whether it relates to how the data is stored, or how your login page will look like, Keycloak can be extended from the bottom up. Keycloak notifies users by e-mail about different events that can happen while handling their account (password reset, email confimation,..etc), and uses by default javax.mail
for that which may not be ideal for some. Luckily, it is possible to plug a custom email sender. We just need to override the right provider, in this case EmailSenderProvider, and implement its corresponding factory, EmailSenderProviderFactory. For the demonstration purpose, we are going to use Amazon SES as our email client.
As a start, we are going to need the dependency for the java aws ses sdk:
<dependency>
<groupId>com.amazonaws</groupId>
<artifactId>aws-java-sdk-ses</artifactId>
<version>1.11.538</version>
</dependency>
Implementation
We can use the previous post’s project as a starting point. Let’s first implement our SESEmailSenderProvider.java
:
public class SESEmailSenderProvider implements EmailSenderProvider {
private static final Logger log = Logger.getLogger("org.keycloak.events");
private final AmazonSimpleEmailService sesClient;
public SESEmailSenderProvider(
AmazonSimpleEmailService sesClient) {
this.sesClient = sesClient;
}
@Override
public void send(Map<String, String> config, UserModel user, String subject, String textBody,
String htmlBody) {
log.info("attempting to send email using aws ses for " + user.getEmail());
Message message = new Message().withSubject(new Content().withData(subject))
.withBody(new Body().withHtml(new Content().withData(htmlBody))
.withText(new Content().withData(textBody).withCharset("UTF-8")));
//config.get("from") is set from the ui to avoid hardcoding it inside the module
SendEmailRequest sendEmailRequest = new SendEmailRequest()
.withSource("example<" + config.get("from") + ">")
.withMessage(message).withDestination(new Destination().withToAddresses(user.getEmail()));
sesClient.sendEmail(sendEmailRequest);
log.info("email sent to " + user.getEmail() + " successfully");
}
@Override
public void close() {
}
}
Then we have to implement the provider factory which we will name SESEmailSenderProviderFactory.java
public class SESEmailSenderProviderFactory implements EmailSenderProviderFactory {
private static AmazonSimpleEmailService sesClientInstance;
@Override
public EmailSenderProvider create(KeycloakSession session) {
//using the singleton pattern to avoid creating the client each time create is called
if (sesClientInstance == null) {
String awsRegion = Objects.requireNonNull(System.getenv("AWS_REGION"));
sesClientInstance =
AmazonSimpleEmailServiceClientBuilder
.standard().withCredentials(new EnvironmentVariableCredentialsProvider())
.withRegion(awsRegion)
.build();
}
return new SESEmailSenderProvider(sesClientInstance);
}
@Override
public void init(Scope config) {
}
@Override
public void postInit(KeycloakSessionFactory factory) { }
@Override
public void close() {
}
@Override
public String getId() {
return "default";
}
}
Finally, we have to create a new file named org.keycloak.email.EmailSenderProviderFactory
in META-INF/services/
with the full qualified name of our factory class:
com.gwidgets.providers.SESEmailSenderProviderFactory
In this way, Keycloak will be aware that we are overriding one of its provider factories.
Easy peasy. Keycloak will now send e-mails using AWS SES.
The added source code can be found in this git commit: https://github.com/zak905/keycloak-api-key-demo/commit/3e686cfec7d3229cf3fd7ac8d26900a866c6e64f