Debug School

rakesh kumar
rakesh kumar

Posted on

How to Sync Laravel Users with Keycloak Automatically Using OTP Login

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"
  }
]
Enter fullscreen mode Exit fullscreen mode

We save "id" into Laravel column kc_user_id.

3️⃣ If not exists → create user

POST /admin/realms/{realm}/users
Enter fullscreen mode Exit fullscreen mode

🟩 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');
});
Enter fullscreen mode Exit fullscreen mode

Run:

php artisan migrate
Enter fullscreen mode Exit fullscreen mode

🟩 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);
    }
}
Enter fullscreen mode Exit fullscreen mode

🟧 THIRD: Add env variables for admin login

In .env:


# KEYCLOAK ADMIN
KEYCLOAK_ADMIN_USER=admin
KEYCLOAK_ADMIN_PASS=YOUR_ADMIN_PASSWORD
Enter fullscreen mode Exit fullscreen mode

🟦 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)
    ...
}
Enter fullscreen mode Exit fullscreen mode

Top comments (0)