**Overview** A system where users can log in using their **email Id /Username + Email OTP** instead of username/password, by Keycloak. **Make Email OTP Java SPI ** # Run this to find the Keycloak container: docker ps | grep -i keycloak Result will look like 2550aa1a95b7 quay.io/keycloak/keycloak:26.1.0 "/opt/keycloak/bin/k…" 7 days ago Up 7 days 8443/tcp, 0.0.0.0:8080->8080/tcp, [::]:8080->8080/tcp, 9000/tcp keycloak_app fcca38958118 postgres:16 "docker-entrypoint.s…" 7 days ago Up 7 days (healthy) 5432/tcp Run this in the droplet console to find the exact JAR download URL: curl -s https://api.github.com/repos/for-keycloak/email-otp-authenticator/releases/latest | grep browser_download_url We need email-otp-authenticator JAR if it is not available **1. Download the email-otp-authenticator JAR** curl -L -o email-otp-authenticator.jar https://github.com/for-keycloak/email-otp-authenticator/releases/download/v1.3.5/email-otp-authenticator-v1.3.5-kc-26.2.5.jar **2. Copy into the running container** docker cp email-otp-authenticator.jar keycloak_app:/opt/keycloak/providers/ **3. Run build inside the container (registers the provider)** # Verify it's there docker exec keycloak_app ls /opt/keycloak/providers/ docker exec keycloak_app /opt/keycloak/bin/kc.sh build **4. Restart the container** docker restart keycloak_app # Now let's set up the Email OTP flow. Go to Keycloak Admin Console at [[https://64.227.190.56/|https://64.227.190.56/]]: **1. First configure SMTP (if not already done)** Realm Settings → Email Host: smtp.gmail.com, Port: 587 From: from email id Username: your username, Password: your app password Enable StartTLS → Save → Test connection **2. Create Email OTP Authentication Flow** Go to Authentication → Flows → Create flow Name: Browser Email OTP → Save Add step → Username Password Form → Required Add step → Email OTP → Required **3. Bind the flow** Client → account → Advance Override realm authentication flow bindings. →Browser Flow → Browser email otp **Customize email content** python3 -c "import zipfile; [print(f) for f in zipfile.ZipFile('email-otp-authenticator.jar').namelist()]" # check current email template python3 -c " import zipfile with zipfile.ZipFile('email-otp-authenticator.jar') as z: print(z.read('theme-resources/messages/messages_en.properties').decode()) " # To customize the email text, create a custom Keycloak theme. Run these commands on the droplet: Step 1: Create theme directory \structure docker exec keycloak_app mkdir -p /opt/keycloak/themes/cotrav/email/messages Step 2: Create theme.\properties docker exec keycloak_app sh -c 'cat> /opt/keycloak/themes/cotrav/email/theme.properties <<\EOF parent=\base EOF' Step 3: Create custom messages (edit the text as you like) docker exec keycloak_app sh -c 'cat> /opt/keycloak/themes/cotrav/email/messages/messages_en.properties <<\EOF emailOtpSubject=Your Cotrav OTP \Code emailOtpYourAccessCode=Your one-time login code is: emailOtpExpiration=This code will expire in {0} minutes. Do not share it with anyone. EOF' Step 4: Set realm to use this \theme TOKEN=$(curl -s -X POST http://64.227.190.56:8080/realms/master/protocol/openid-connect/token -d "client_id=admin-cli&grant_type=password&username=superadmin_username&password=superadminpassword" | grep -o '"access_token":"[^"]*' | cut -d'"' -f4) curl -s -X PUT http://64.227.190.56:8080/admin/realms/master \ -H "Authorization: Bearer $TOKEN" \ -H "Content-Type: application/json" \ -d '{"emailTheme":"cotrav"}' Then test by logging in again — you should see your custom text in the OTP email. # Invalid otp issue\\ The OTP field name sent by our server might not match what the extension expects. Let me check: python3 -c "\ import zipfile\ with zipfile.ZipFile('email-otp-authenticator.jar') as z:\ print(z.read('theme-resources/templates/login-email-otp.ftl').decode())\ " **# Browser email otp Flow order should be** \\ Username Form → Required (first)\\ Email OTP Form → Required (second) **# Dont do this** - No required user action available in user details\\ - Set Email & password