Part A – Keycloak setup (UI mein kya fill karna hai)
Part B – Laravel setup (env, config, routes, controllers, logout, sab)
So that:
Login UI = tumhara Laravel OTP form (screenshot wala)
Internal auth provider = Keycloak (silent, no UI)
SSO across all domains = motoshare.in, .jp, .ca, .us, …
Logout on any one domain = logout from ALL domains, but UI mein sirf return redirect('/') dikhna chahiye.
Part A – Keycloak setup (UI mein kya fill karna hai)
PART A – Keycloak Setup (Once for all domains)
0️⃣ Realm
Realm name: motoshare
All MotoShare domains will use this single realm.
1️⃣ Create Client: motoshare-web
In Keycloak Admin:
Go to: Realm → Clients → Create client
Fill:
Client type: OpenID Connect
Client ID: motoshare-web
Click Next
Capability config:
Client authentication: ON (Confidential client – hum client_secret use karenge)
Standard flow: ON
Direct access grants: ON ✅ (important for grant_type=password)
Service accounts: OFF (optional)
Click Save.
Now you are on the Settings tab of client motoshare-web.
2️⃣ Settings tab – Multi-domain values
General
Client ID: motoshare-web
Name: MotoShare Websites
Root URL: (optional, you can leave empty or set https://motoshare.in)
Valid redirect URIs
Even though abhi hum direct-grant use karenge, future ke liye callback rakhna safe hai:
Add one per domain:
https://motoshare.in/auth/keycloak/callback
https://motoshare.jp/auth/keycloak/callback
https://motoshare.ca/auth/keycloak/callback
https://motoshare.us/auth/keycloak/callback
https://motoshare.sg/auth/keycloak/callback
... (jitne domains hain)
Valid post logout redirect URIs
https://motoshare.in/
https://motoshare.jp/
https://motoshare.ca/
https://motoshare.us/
https://motoshare.sg/
...
Web origins
https://motoshare.in
https://motoshare.jp
https://motoshare.ca
https://motoshare.us
https://motoshare.sg
...
Tip: You can also use + in Web Origins as wildcard (if allowed in your version), but listing exact domains is safer.
3️⃣ Credentials tab
Go to Clients → motoshare-web → Credentials
Copy Client Secret – use in .env as KEYCLOAK_CLIENT_SECRET.
4️⃣ Direct Access Grant (important)
Make sure under Settings of motoshare-web:
Direct Access Grants Enabled: ✅ ON
Ye hi allow karta hai humko:
grant_type = password
PART B – Laravel Setup (common for all MotoShare domains)
Same Laravel code, bas .env mein APP_URL har domain ka alag hoga.
1️⃣ .env variables
Har MotoShare Laravel app (IN, JP, CA, …) mein:
# APP URL per domain
APP_URL=https://motoshare.in # (JP app mein https://motoshare.jp, etc.)
# Keycloak config (same for all domains)
KEYCLOAK_BASE_URL=https://auth.myhospitalnow.com
KEYCLOAK_REALM=motoshare
KEYCLOAK_CLIENT_ID=motoshare-web
KEYCLOAK_CLIENT_SECRET=YOUR_CLIENT_SECRET_HERE
2️⃣ config/keycloak.php
Create file: config/keycloak.php
<?php
return [
'base_url' => env('KEYCLOAK_BASE_URL', 'https://auth.myhospitalnow.com'),
'realm' => env('KEYCLOAK_REALM', 'motoshare'),
'client_id' => env('KEYCLOAK_CLIENT_ID', 'motoshare-web'),
'client_secret' => env('KEYCLOAK_CLIENT_SECRET', null),
];
Then:
php artisan config:clear
php artisan cache:clear
3️⃣ Routes (routes/web.php)
We’ll assume:
Tumhara login screen = OtpLoginController@showForm
AJAX OTP verify + login = OtpLoginController@store
Logout = Auth\LogoutController@destroy
use App\Http\Controllers\Auth\OtpLoginController;
use App\Http\Controllers\Auth\LogoutController;
// Show your custom OTP login form
Route::get('/login', [OtpLoginController::class, 'showForm'])->name('login');
// AJAX endpoint: verify OTP + Laravel login + Keycloak silent login
Route::post('/login/verify-otp', [OtpLoginController::class, 'store'])->name('login.verify');
// Logout (global SSO logout, silent)
Route::post('/logout', [LogoutController::class, 'destroy'])->name('logout');
// (Optional) keep for future if you ever use Keycloak redirect login
Route::get('/auth/keycloak/callback', function () {
abort(404); // or a future implementation
})->name('keycloak.callback');
4️⃣ OtpLoginController – Full Implementation
app/Http/Controllers/Auth/OtpLoginController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Http;
use App\Models\User;
class OtpLoginController extends Controller
{
// Show your custom login form (phone + OTP)
public function showForm()
{
return view('auth.login-otp'); // tumhara Blade file
}
// Verify OTP + Laravel login + Keycloak silent login
public function store(Request $request)
{
Log::info('Request data:', $request->all());
Log::info("coming store here");
// 1️⃣ OTP VALIDATION
$inputOtp = $request->input('otp');
$storedOtp = $request->session()->get('otp');
if ($inputOtp != $storedOtp) {
return response()->json([
'success' => false,
'message' => 'The OTP you entered is incorrect.'
], 400);
}
Log::info("OTP matches");
// 2️⃣ USER FIND (by phone or email)
$phone = $request->input('phone');
$user = User::where('number', $phone)->first();
if (!$user) {
$user = User::where('email', $phone)->first();
}
if (!$user) {
Log::info("User not found with phone/email: " . $phone);
return response()->json([
'success' => false,
'message' => 'Authentication failed. User not found.'
], 404);
}
// 3️⃣ LARAVEL LOGIN
Auth::login($user);
$request->session()->regenerate();
Log::info("Laravel login successful. Now syncing with Keycloak...");
// 4️⃣ SILENT KEYCLOAK LOGIN (Direct Access Grant)
$keycloakBase = config('keycloak.base_url');
$realm = config('keycloak.realm');
$clientId = config('keycloak.client_id');
$clientSecret = config('keycloak.client_secret');
$tokenUrl = "{$keycloakBase}/realms/{$realm}/protocol/openid-connect/token";
try {
$response = Http::asForm()->post($tokenUrl, [
'grant_type' => 'password',
'client_id' => $clientId,
'client_secret' => $clientSecret,
'username' => $user->email, // MUST exist in Keycloak
'password' => 'dummy', // password policy ko relax karo / dummy
]);
if ($response->failed()) {
Log::error("Keycloak Direct Grant failed: " . $response->body());
} else {
$tokens = $response->json();
$request->session()->put('kc_tokens', $tokens);
Log::info("Keycloak tokens stored successfully");
}
} catch (\Exception $e) {
Log::error("Error calling Keycloak token endpoint: " . $e->getMessage());
}
// 5️⃣ ROLE-BASED REDIRECT
$role = $user->role;
$inputrouteName = $request->input('route_name');
return response()->json([
'success' => true,
'message' => 'Logged in successfully!',
'role' => $role,
'redirect_url' => $this->getRedirectUrlForRole($role, $inputrouteName),
]);
}
// Your existing method – adjust as needed
protected function getRedirectUrlForRole($role, $inputrouteName = null)
{
// Example logic – isko tum apne existing code se replace kar sakte ho
if ($inputrouteName) {
return route($inputrouteName);
}
switch ($role) {
case 'admin':
return route('admin.dashboard');
case 'partner':
return route('partner.dashboard');
default:
return url('/'); // homepage
}
}
}
Imports ka dhyan:
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
use Illuminate\Support\Facades\Auth;
5️⃣ LogoutController – Silent Global Logout (All Domains)
app/Http/Controllers/Auth/LogoutController.php
<?php
namespace App\Http\Controllers\Auth;
use App\Http\Controllers\Controller;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Auth;
use Illuminate\Support\Facades\Http;
use Illuminate\Support\Facades\Log;
class LogoutController extends Controller
{
public function destroy(Request $request)
{
$baseUrl = config('keycloak.base_url');
$realm = config('keycloak.realm');
$clientId = config('keycloak.client_id');
$clientSecret = config('keycloak.client_secret');
// get tokens from session
$tokens = $request->session()->get('kc_tokens', []);
$idToken = $tokens['id_token'] ?? null;
// 1️⃣ Laravel local logout
Auth::logout();
$request->session()->invalidate();
$request->session()->regenerateToken();
// 2️⃣ Silent global logout in Keycloak (NO redirect to Keycloak)
if ($idToken) {
$logoutUrl = "{$baseUrl}/realms/{$realm}/protocol/openid-connect/logout";
try {
Http::asForm()->post($logoutUrl, [
'client_id' => $clientId,
'client_secret' => $clientSecret,
'id_token_hint' => $idToken,
]);
Log::info("Keycloak global logout success");
} catch (\Exception $e) {
Log::error("Keycloak silent logout error: " . $e->getMessage());
}
} else {
Log::info("No id_token found in session for Keycloak logout");
}
// 3️⃣ UI requirement: always redirect to '/' on same domain
return redirect('/');
}
}
Ye hi code har domain pe same rahega (.in, .jp, .ca, …) →
Because Keycloak realm + client same hai, global SSO session nikal jayega → sab domain se logout.
6️⃣ Blade: Login & Logout Buttons
Navbar mein:
{{-- Show Login button if guest --}}
@guest
<a href="{{ route('login') }}" class="nav-link">
Login / Register
</a>
@endguest
{{-- Show Logout if logged in --}}
@auth
<form action="{{ route('logout') }}" method="POST" style="display:inline;">
@csrf
<button type="submit" class="nav-link btn btn-link p-0 m-0 align-baseline">
Logout
</button>
</form>
@endauth
Top comments (0)