authentication:send_sms
This is an old revision of the document!
Table of Contents
Overview
A system where users can log in using their phone number + SMS OTP instead of username/password, powered by Keycloak + Exotel.
Architecture
User (Browser/App) ↓ enter phone number Node.js Server (64.227.190.56:3000) ↓ phone → find username by Keycloak admin API ↓ Keycloak login form proxy Keycloak SPI (Java) ↓ OTP generate + store ↓ call webhook Node.js Webhook ↓ call Exotel API Exotel → SMS → User phone ↓ enter User OTP Keycloak verify OTP→ return JWT Token</font>
Step 1: Created Custom Java SPI (Keycloak Plugin)
Step 1: Connect to DigitalOcean Server
1. Create Java SPI Project
Create folder structure
mkdir -p /root/sms-otp-spi/src/main/java/com/cotrav/keycloak\ mkdir -p /root/sms-otp-spi/src/main/resources/META-INF/services
==== File 1: pom.xml ====
<code>
''cat> /root/sms-otp-spi/pom.xml <<'EOF'
<?xml version="1.0" encoding="UTF-8"?>
<project xmlns="http://maven.apache.org/POM/4.0.0"
xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>com.cotrav.keycloak</groupId>
<artifactId>keycloak-sms-otp</artifactId>
<version>1.0.0</version>
<packaging>jar</packaging>
<properties>
<maven.compiler.source>17</maven.compiler.source>
<maven.compiler.target>17</maven.compiler.target>
<keycloak.version>26.1.0</keycloak.version>
</properties>
<dependencies>
<dependency><groupId>org.keycloak</groupId><artifactId>keycloak-server-spi</artifactId><version>${keycloak.version}</version><scope>provided</scope></dependency>
<dependency><groupId>org.keycloak</groupId><artifactId>keycloak-server-spi-private</artifactId><version>${keycloak.version}</version><scope>provided</scope></dependency>
<dependency><groupId>org.keycloak</groupId><artifactId>keycloak-services</artifactId><version>${keycloak.version}</version><scope>provided</scope></dependency>
</dependencies>
<build>
<plugins>
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-jar-plugin</artifactId>
<configuration>
<archive><manifest><addClasspath>false</addClasspath></manifest></archive>
</configuration>
</plugin>
</plugins>
</build>
</project>
EOF
File 2: SmsOtpAuthenticator.java
cat> /root/sms-otp-spi/src/main/java/com/cotrav/keycloak/SmsOtpAuthenticatorFactory.java <<'EOF'\
package com.cotrav.keycloak;\
import org.keycloak.Config;\
import org.keycloak.authentication.Authenticator;\
import org.keycloak.authentication.AuthenticatorFactory;\
import org.keycloak.models.AuthenticationExecutionModel;\
import org.keycloak.models.KeycloakSession;\
import org.keycloak.models.KeycloakSessionFactory;\
import org.keycloak.provider.ProviderConfigProperty;\
import org.keycloak.provider.ProviderConfigurationBuilder;\
import java.util.List;
public class SmsOtpAuthenticatorFactory implements AuthenticatorFactory {\
public static final String PROVIDER_ID = "sms-otp-authenticator";\
private static final SmsOtpAuthenticator SINGLETON = new SmsOtpAuthenticator();
@Override public String getId() { return PROVIDER_ID; }\
@Override public String getDisplayType() { return "SMS OTP Form"; }\
@Override public String getHelpText() { return "Sends OTP via SMS webhook and validates it."; }\
@Override public String getReferenceCategory() { return "otp"; }\
@Override public boolean isConfigurable() { return true; }\
@Override public boolean isUserSetupAllowed() { return false; }
@Override\
public AuthenticationExecutionModel.Requirement[] getRequirementChoices() {\
return new AuthenticationExecutionModel.Requirement[]{\
AuthenticationExecutionModel.Requirement.REQUIRED,\
AuthenticationExecutionModel.Requirement.ALTERNATIVE,\
AuthenticationExecutionModel.Requirement.DISABLED\
};\
}
@Override\
public List<ProviderConfigProperty> getConfigProperties() {\
return ProviderConfigurationBuilder.create()\
.property()\
.name("smsWebhookUrl")\
.label("SMS Webhook URL")\
.helpText("URL of the webhook that sends SMS via Exotel")\
.type(ProviderConfigProperty.STRING_TYPE)\
.defaultValue("http://localhost:3000/webhook/send-sms")\
.add()\
.build();\
}
@Override public Authenticator create(KeycloakSession session) { return SINGLETON; }\
@Override public void init(Config.Scope config) {}\
@Override public void postInit(KeycloakSessionFactory factory) {}\
@Override public void close() {}\
}\
EOF\
File 4: Service registration file
cat> /root/sms-otp-spi/src/main/resources/META-INF/services/org.keycloak.authentication.AuthenticatorFactory <<'EOF'\ com.cotrav.keycloak.SmsOtpAuthenticatorFactory\ EOF
Step 4: Build JAR
''cd /root/sms-otp-spi docker run --rm -v "$PWD":/app -w /app maven:3.9-eclipse-temurin-17 mvn clean package -q
Step 5: Deploy JAR to Keycloak
''docker cp /root/sms-otp-spi/target/keycloak-sms-otp-1.0.0.jar keycloak_app:/opt/keycloak/providers/ docker restart keycloak_app # Verify JAR is loaded docker exec keycloak_app ls /opt/keycloak/providers/
Useful Commands
# Check Node.js is running ps aux | grep node # View server logs tail -f /root/keycloak-test/app.log # Restart Node.js pkill -f "node server.js" cd /root/keycloak-test && nohup node server.js>> app.log 2>&1 & # Restart Keycloak docker restart keycloak_app # Check Keycloak logs for errors docker logs keycloak_app --tail=50 2>&1 | grep -i "error\|sms" # Reload Nginx nginx -s reload
Configured Keycloak Admin Console
1 — Created Authentication Flow
- Admin Console → cotrav-OPS realm → Authentication → Create Flow
- Flow name:
Browser SMS OTP - Added steps:
Username Form→ REQUIREDSMS OTP Form(custom SPI) → REQUIRED
2 — Set Webhook URL in Flow
- Clicked gear icon ⚙️ on “SMS OTP Form” step
- Set
smsWebhookUrl=http://172.17.0.1:3000/webhook/send-sms 172.17.0.1= Docker host gateway IP (Keycloak container → host Node.js)
3 — Created sms-login-client
- Admin Console → Clients → Create
Client ID=sms-login-client- Authentication Flow Override → Browser =
Browser SMS OTP - Valid Redirect URIs =
*
4 — Added phone to User Profile ⬅️ (This step was done manually)
- Admin Console → cotrav-OPS realm → Realm Settings
- Click User Profile tab
- Click Add attribute
- Attribute name:
phone - Save
5 — Set Phone Number on User ⬅️ (Done manually)
- Admin Console → Users →
sonali.magar - Click Attributes tab
- Add:
phone=8999463315 - Save
6 — Set Login Theme
- Realm Settings → Themes → Login Theme =
cotrav - Required so Keycloak finds
sms-otp-form.ftltemplate
Steps on digital Ocean
1. Install Node.js
curl -fsSL https://rpm.nodesource.com/setup_20.x | bash - dnf install -y nodejs node -v # verify installation
2 : Create Node.js Project
mkdir -p /root/keycloak-test cd /root/keycloak-test npm init -y npm install express cors axios
3. Start Server
nohup node server.js>> app.log 2>&1 &
4. Configured Nginx as Reverse Proxy
DigitalOcean firewall blocked port 3000. Added Node.js routes to existing Nginx config at /etc/nginx/conf.d/keycloak.conf:
location /api/ {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-Proto $scheme;
}
location /webhook/ {
proxy_pass http://localhost:3000;
proxy_set_header X-Forwarded-Proto $scheme;
}
nginx -s reload
Upload server.js from local machine (run on Windows):
scp d:/keycloak-test/server.js root@64.227.190.56:/root/keycloak-test/server.js
authentication/send_sms.1773039625.txt.gz · Last modified: by sonali
