When user logs in using OTP:
Laravel finds the user
Laravel silently logs in to Keycloak
If Keycloak user already exists → get Keycloak ID and store in Laravel DB
If Keycloak user does NOT exist → create Keycloak user → save Keycloak ID in Laravel DB
So final result:
Step–by–Step Solution
👉 We need 3 things:
1️⃣ API to search user in Keycloak by email
Keycloak Admin API:
GET /admin/realms/{realm}/users?email={email}
2️⃣ If exists → get user ID and update Laravel user
Keycloak returns:
[
{
"id": "bd6ae936-16e4-4802-a609-369b3d942455",
"email": "abhi@gmail.com"
}
]
We save "id" into Laravel column kc_user_id.
3️⃣ If not exists → create user
POST /admin/realms/{realm}/users
🟩 FIRST: Add column in Laravel
If you DO NOT already have kc_user_id in users table, add it:
migration:
Schema::table('users', function (Blueprint $table) {
$table->string('kc_user_id')->nullable()->after('id');
});
Run:
php artisan migrate
🟩 SECOND: Add helper functions (Keycloak Admin)
Create file:
app/Services/KeycloakService.php
<?php
namespace App\Services;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class KeycloakService
{
// GET ADMIN TOKEN
public function getAdminToken()
{
$base = config('keycloak.base_url');
$realm = "master"; // admin login realm
$url = "{$base}/realms/{$realm}/protocol/openid-connect/token";
$response = Http::asForm()->post($url, [
'grant_type' => 'password',
'client_id' => 'admin-cli',
'username' => env('KEYCLOAK_ADMIN_USER'),
'password' => env('KEYCLOAK_ADMIN_PASS'),
]);
return $response->json()['access_token'];
}
// SEARCH USER BY EMAIL
public function findUserByEmail($email)
{
$token = $this->getAdminToken();
$url = config('keycloak.base_url') .
"/admin/realms/" . config('keycloak.realm') .
"/users?email=" . urlencode($email);
$response = Http::withToken($token)->get($url);
return $response->json();
}
// CREATE USER IN KEYCLOAK
public function createUser($email, $firstName, $lastName)
{
$token = $this->getAdminToken();
$url = config('keycloak.base_url') .
"/admin/realms/" . config('keycloak.realm') .
"/users";
$response = Http::withToken($token)->post($url, [
'email' => $email,
'username' => $email,
'enabled' => true,
'firstName' => $firstName,
'lastName' => $lastName,
]);
if ($response->failed()) {
Log::error("Keycloak user creation failed: " . $response->body());
return null;
}
// Extract user ID from Location header
$location = $response->header('Location');
return basename($location);
}
}
🟧 THIRD: Add env variables for admin login
In .env:
# KEYCLOAK ADMIN
KEYCLOAK_ADMIN_USER=admin
KEYCLOAK_ADMIN_PASS=YOUR_ADMIN_PASSWORD
🟦 FOURTH: Modify OTP store() Method (FINAL VERSION)
👉 This version searches Keycloak,
👉 creates user if needed,
👉 stores kc_user_id in Laravel.
Modify your store() like this:
public function store(Request $request)
{
...
// Existing OTP + Laravel login code remains same
...
// 🔵 INTEGRATE KEYCLOAK USER SYNC
$kc = new \App\Services\KeycloakService();
// 1️⃣ Search user in Keycloak
$kcUser = $kc->findUserByEmail($user->email);
if (!empty($kcUser)) {
// User exists in Keycloak
$kcUserId = $kcUser[0]['id'];
Log::info("Keycloak user found, ID: ".$kcUserId);
} else {
// 2️⃣ Create user in Keycloak
$kcUserId = $kc->createUser(
$user->email,
$user->first_name ?? '',
$user->last_name ?? ''
);
Log::info("Keycloak user created, ID: ".$kcUserId);
}
// 3️⃣ Save in Laravel users table if null
if ($user->kc_user_id == null && $kcUserId) {
$user->kc_user_id = $kcUserId;
$user->save();
Log::info("kc_user_id saved in Laravel: ".$kcUserId);
}
// 🔵 Continue: silent Keycloak login (Direct Grant)
...
}
Top comments (0)