Keycloak - Reset email to another (second) address
16:51 27 Oct 2025

I'm trying to modify Keycloak 26, in this case the "forgot password" function.

Every user in my environment has an extra attribute with the private mail address of the user.

I want to modify the "forgot password" flow that KC will send the mail to both mail addresses (or just to the private).

Is this possible?

Until now I created a custom SPI for the event "SEND_RESET_PASSWORD". The build of KC is working without problems, I can activate the Event Listener in the Admin GUI of KC. The mail is also sending, but not the the attribute "secMail", the SMTP defined mail address is used.

And also I'm struggling to get the KC generated link with the token to fill in the body of the second mail.

The new attribute "secMail" is set in the Realms settings and the user has filled out the attribute.

Here are my files:

pom.xml


  4.0.0
  com.example.keycloak
  kc-secMailResetEmail
  1.0.0
  jar

  
    26.4.2
    17
    17
  

  
    
      org.keycloak
      keycloak-core
      ${keycloak.version}
    
    
      org.keycloak
      keycloak-server-spi
      ${keycloak.version}
    
    
      org.keycloak
      keycloak-server-spi-private
      ${keycloak.version}
    
  

SecMailResetListenerProvider.java

package com.example.keycloak;

import org.keycloak.email.EmailException;
import org.keycloak.email.EmailSenderProvider;
import org.keycloak.events.Event;
import org.keycloak.events.EventType;
import org.keycloak.events.admin.AdminEvent;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;

public class SecMailResetListenerProvider implements EventListenerProvider {

    private final KeycloakSession session;

    public SecMailResetListenerProvider(KeycloakSession session) {
        this.session = session;
    }

    @Override
    public void onEvent(Event event) {

        // 1️⃣ Nur auf SEND_RESET_PASSWORD reagieren
        if (!EventType.SEND_RESET_PASSWORD.equals(event.getType())) {
            return;
        }

        System.out.println("[DEBUG] RESET_PASSWORD Event für UserId: " + event.getUserId());

        RealmModel realm = session.realms().getRealm(event.getRealmId());
        if (realm == null) {
            System.out.println("[DEBUG] Realm nicht gefunden: " + event.getRealmId());
            return;
        }

        UserModel user = session.users().getUserById(realm, event.getUserId());
        if (user == null) {
            System.out.println("[DEBUG] User nicht gefunden: " + event.getUserId());
            return;
        }

        // 2️⃣ Benutzerattribut secMail prüfen
        String secMail = user.getFirstAttribute("secMail");
        if (secMail == null || secMail.isBlank()) {
            System.out.println("[DEBUG] Kein secMail-Attribut für Benutzer " + user.getUsername());
            return;
        }

        // 3️⃣ E-Mail erstellen
        String subject = "Passwort-Zurücksetzung";
        String body = String.format(
                "Hallo %s,%n%n" +
                "Es wurde eine Anfrage zum Zurücksetzen Ihres Passworts gestellt.%n" +
                "Falls Sie das nicht waren, kontaktieren Sie bitte den Support.%n%n" +
                "Ihr Sicherheitsteam",
                user.getUsername()
        );

        // Debug-Ausgabe
        System.out.println("[DEBUG] secMail: " + secMail);
        System.out.println("[DEBUG] Subject: " + subject);
        System.out.println("[DEBUG] Body: " + body);

        try {
            EmailSenderProvider sender = session.getProvider(EmailSenderProvider.class);

            // Absender aus Realm-Email-Config
            String from = realm.getSmtpConfig().get("from");
            if (from == null || from.isBlank()) {
                System.out.println("[DEBUG] Kein Absender in Realm SMTP Config gefunden.");
                from = "noreply@example.com"; // Fallback
            }

            // 4️⃣ Mail senden
            sender.send(
                    realm.getSmtpConfig(), // SMTP Config
                    from,                  // Absender
                    secMail,               // Empfänger
                    subject,               // Betreff
                    body                   // Text
            );

            System.out.println("✅ secMail-Reset-Email an " + secMail + " gesendet.");
        } catch (EmailException e) {
            System.err.println("❌ Fehler beim Senden der secMail an " + secMail + ": " + e.getMessage());
            e.printStackTrace();
        }
    }

    @Override
    public void onEvent(AdminEvent adminEvent, boolean includeRepresentation) {
        // Admin-Events ignorieren
    }

    @Override
    public void close() {
        // nichts zu schließen
    }
}

SecMailResetListenerProviderFactory.java

package com.example.keycloak;

import org.keycloak.Config.Scope;
import org.keycloak.events.EventListenerProvider;
import org.keycloak.events.EventListenerProviderFactory;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.KeycloakSessionFactory;

public class SecMailResetListenerProviderFactory implements EventListenerProviderFactory {
    public static final String PROVIDER_ID = "secMailResetListener";

    @Override
    public EventListenerProvider create(KeycloakSession session) {
        return new SecMailResetListenerProvider(session);
    }

    @Override
    public void init(Scope config) {}

    @Override
    public void postInit(KeycloakSessionFactory factory) {}

    @Override
    public void close() {}

    @Override
    public String getId() {
        return PROVIDER_ID;
    }
}
keycloak