I already created a registration authenticator by implementing the authenticator interface. I want to verify the user email, and then I want to store the user in Keycloak.

I don't know where I can store my OTP. Currently, I am storing my opt in AuthenticationSession. It works fine, but if my keycloak server restarts, my session is cleared, and I'm getting a session timeout. I don't want that. Can someone shine a light on this?

package fileuploader.authenticators;
import fileuploader.authenticators.messages.Messages;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;

import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
@JBossLog
public class RegistrationAuthenticator implements  Authenticator {

    public  static  final String PROVIDED_ID = "file-uploader-registration-form";
    public  static  final String VERIFY_USERNAME_FORM = "verify-username.ftl";

    @Override
    public void authenticate(AuthenticationFlowContext context) {
        Response challenge = context.form()
                .createForm(VERIFY_USERNAME_FORM);
        context.challenge(challenge);
    }




    public  Response createForm(AuthenticationFlowContext context,String message){
        LoginFormsProvider formsProvider = context.form().addError(new FormMessage(message));
        return formsProvider.createForm(VERIFY_USERNAME_FORM);
    }

    public Response validateForm(AuthenticationFlowContext context,String email){
         UserModel user  = context.getSession().users().getUserByEmail(context.getRealm(),email);
         if(user != null ){
               return createForm(context, Messages.Error.USER_ALREADY_EXISTS);
         }
         return null;
    }

    @Override
    public void action(AuthenticationFlowContext context) {

        MultivaluedMap<String,String> formData = context.getHttpRequest().getDecodedFormParameters();
        String email = formData.get("email").get(0);
        String password =formData.get("password").get(0);


       Response formError =  validateForm(context,email);
       log.info("this is error : "+formError);

       if(formError!=null){
           context.challenge(formError);
           return;
       }

        context.getAuthenticationSession().setAuthNote("email", email);
        context.getAuthenticationSession().setAuthNote("password", password);

        context.success();

    }


    @Override
    public boolean requiresUser() {
        return false;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return false;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
     log.info("Set Required is called");

    }


    @Override
    public void close() {
        log.info("Close Called...");
    }
}


package fileuploader.authenticators;

import fileuploader.authenticators.messages.Messages;
import lombok.extern.jbosslog.JBossLog;
import org.keycloak.authentication.AuthenticationFlowContext;
import org.keycloak.authentication.Authenticator;
import org.keycloak.email.EmailSenderProvider;
import org.keycloak.email.EmailTemplateProvider;
import org.keycloak.email.EmailTemplateSpi;
import org.keycloak.forms.login.LoginFormsProvider;
import org.keycloak.models.KeycloakSession;
import org.keycloak.models.RealmModel;
import org.keycloak.models.UserCredentialModel;
import org.keycloak.models.UserModel;
import org.keycloak.models.utils.FormMessage;

import javax.mail.*;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
import javax.ws.rs.core.MultivaluedMap;
import javax.ws.rs.core.Response;
import java.util.Collections;
import java.util.Properties;
import java.util.Random;
import java.util.ResourceBundle;

@JBossLog
public class OtpAuthenticator implements Authenticator {
    public  static  final String PROVIDED_ID = "file-uploader-otp-form";
   private static final String OTP_FORM = "otp-form.ftl";

    ResourceBundle bundle = ResourceBundle.getBundle("application");




    public String generateRandomOTP()
    {

        Random random = new Random();
        int min = 100000;
        int max = 999999;
        int randomNumber = random.nextInt(max - min + 1) + min;
        return String.valueOf(randomNumber);
    }







    public void sendOtpToEmail(AuthenticationFlowContext context)
    {
        UserModel user = context.getUser();

        String smtpHost = bundle.getString("email.host");
        String smtpPort = bundle.getString("email.port");
        String smtpUsername = bundle.getString("email.username");
        String smtpPassword = bundle.getString("email.password");
        String recipientEmail =context.getAuthenticationSession().getAuthNote("email");
        String subject = "OTP conformation";

        String otp = generateRandomOTP();
        user.setAttribute("otp", Collections.singletonList(otp));
                context.getAuthenticationSession().setAuthNote("otp",otp);
        long otpGeneratedTimestamp = System.currentTimeMillis();
        context.getAuthenticationSession().setAuthNote("otpGeneratedTimestamp", String.valueOf(otpGeneratedTimestamp));


        Properties props = new Properties();
        props.put("mail.smtp.host", smtpHost);
        props.put("mail.smtp.port", smtpPort);
        props.put("mail.smtp.auth", "true");
        props.put("mail.smtp.starttls.enable", "true");


        Session session = Session.getInstance(props, new javax.mail.Authenticator() {
            @Override
            protected PasswordAuthentication getPasswordAuthentication() {
                return new PasswordAuthentication(smtpUsername, smtpPassword);
            }
        });


        try
        {
            Message emailMessage = new MimeMessage(session);
            emailMessage.setFrom(new InternetAddress(bundle.getString("email.username")));
            emailMessage.setRecipients(Message.RecipientType.TO, InternetAddress.parse(recipientEmail));
            emailMessage.setSubject(subject);
            emailMessage.setText("We're excited to have you on board as a valued member of our platform. To ensure the security of your account, we have generated a one-time password (OTP) for your registration ." +
                    "\nYour OTP Code: "+otp+
                    "\nPlease use this OTP to complete your registration on our website. After entering your username and password, you'll be prompted to enter this OTP to verify your identity. Remember, this OTP is unique and should be kept confidential. Do not share it with anyone, including our support team." +
                    "\nIf you did not request this OTP or have any concerns, please contact our support team immediately at [email protected].");

            Transport.send(emailMessage);
            context.success();
        }
        catch (MessagingException e)
        {

            e.printStackTrace();
        }
    }



    @Override
    public void authenticate(AuthenticationFlowContext context) {
            sendOtpToEmail(context);
            Response challenge = context.form()
                    .createForm(OTP_FORM);
            context.challenge(challenge);
    }

    private boolean validateOTP(AuthenticationFlowContext context,String otpFromUser, long timestamp){
        long currentTime = System.currentTimeMillis();

        log.info("time : "+ (currentTime - timestamp));
        String otp= context.getAuthenticationSession().getAuthNote("otp");
//        String test = user.getFirstAttribute("otp");
//        log.info("otp from attribute : "+ test);
        log.info("original opt : " +otp);
        log.info("from user opt : " +otpFromUser);

        return otpFromUser.equals(otp) && (currentTime - timestamp) <= 60000;
    }
    @Override
    public void action(AuthenticationFlowContext context) {
        MultivaluedMap<String,String> formData = context.getHttpRequest().getDecodedFormParameters();
        KeycloakSession  session = context.getSession();
        RealmModel realm = context.getRealm();
        String otpFromUser = formData.get("otp").get(0);


        if (formData.containsKey("resend")) {
            sendOtpToEmail(context);
            LoginFormsProvider formsProvider = context.form().addSuccess(new FormMessage(Messages.Success.RESENT_OTP_SUCCESS));
            context.challenge(formsProvider.createForm(OTP_FORM));
            return;
        }
long otpGeneratedTime = Long.parseLong(context.getAuthenticationSession().getAuthNote("otpGeneratedTimestamp"));
        boolean isValid = validateOTP(context,otpFromUser , otpGeneratedTime);
        log.info("valid state : "+isValid);

     if(!isValid) {
    LoginFormsProvider formsProvider = context
            .form()
            .addError(new FormMessage(Messages.Error.INVALID_OTP));
    context.challenge(formsProvider.createForm(OTP_FORM));
    return ;
     }

    String email =   context.getAuthenticationSession().getAuthNote("email");
    String password =   context.getAuthenticationSession().getAuthNote("password");

    UserModel userModel = session.users().addUser(realm,email);
    userModel.setEnabled(true);
    userModel.setEmail(email);
    userModel.setEmailVerified(true);

    session.userCredentialManager()
            .updateCredential(realm, userModel, UserCredentialModel.password(password));

    context.setUser(userModel);
    context.success();

    }

    @Override
    public boolean requiresUser() {
        return false;
    }

    @Override
    public boolean configuredFor(KeycloakSession session, RealmModel realm, UserModel user) {
        return true;
    }

    @Override
    public void setRequiredActions(KeycloakSession session, RealmModel realm, UserModel user) {
     }

    @Override
    public void close() {
        log.info("Otp Authenticator closed ");
    }
}

0

There are 0 best solutions below