Dalam pengembangan aplikasi berbasis web, fitur otentikasi dan otorisasi merupakan salah satu yang cukup rumit untuk diwujudkan. Beberapa fungsionalitas yang harus diimplementasikan dalam scope tersebut, misalkan:
- Form untuk registrasi
- Login
- Reset password
- Email verification
Tentu mengimplementasikan fitur ini akan memakan waktu. Namun syukurnya, dengan Laravel, fitur tersebut bisa kita peroleh secara gratis dengan menggunakan Laravel Breeze.
Laravel Breeze adalah starter kit untuk membuat fungsionalitas yang meliputi otentikasi dan otorisasi dengan mudah, dan tentu saja gratis. Tersedia berbagai preset stacks frontend, mulai dari Blade dengan Alpine, hingga Inertia dengan React atau Vue.
Namun demikian, Laravel Breeze tidak untuk sistem otentikasi dan otorisasi terpusat seperti SSO. Untuk SSO, kita bisa menggunakan Laravel Passport, yakni package untuk mempermudah membangun sistem SSO dengan OAuth 2.0.
Tutorial berikut akan memandu Anda mengimplementasikan SSO dengan menggunakan Laravel dan Laravel Passport. Jika Anda memiliki serangkaian aplikasi yang membutuhkan fitur berbagi session atau otentikasi, maka konsep dalam tulisan ini akan membantu Anda untuk memulai.
Preresquite
Sebelum dapat mengikuti tutorial ini dengan baik, saya sarankan Anda sudah menguasai web framework Laravel beserta dengan ekosistemnya. Anda juga diasumsikan sudah mengenal berbagai macam jargon umum dalam web development seperti JWT, otentikasi, otorisasi, validasi, request, dan session.
Apa itu SSO?
Single Sign On adalah metode yang memungkinkan aplikasi-aplikasi yang berbeda bisa menggunakan 1 sistem otentikasi dan otorisasi secara terpusat.
Anda mungkin sudah pernah melihat implementasinya pada ekosistem Google dan Meta, di mana Anda bisa menggunakan 1 akun saja untuk login ke berbagai macam platform.
Misalkan dengan menggunakan akun Facebook, Anda bisa login ke Instagram dan Threads. Di lain sisi, dengan akun Gmail, Anda bisa login ke Google Drive, YouTube, dan Meet.
Komponen dalam SSO
Berikut adalah komponen-komponen SSO.
User
User bisa berupa orang atau sistem yang perlu melakukan proses authentication ke beberapa sistem dengan credentials yang sama.
Identity Provider
Identity provider dalam hal ini adalah sebuah sistem yang bisa melakukan otentikasi akses user ke suatu sistem. Setelah proses otentikasi berhasil, identity provider akan memberikan token akses, sebagai kunci otorisasi.
Service Provider
Service provider adalah system yang bergantung kepada identity provider untuk proses otentikasi. Dengan kata lain, sistem yang proses otentikasinya diproses oleh identity provider.
Authentication Protocol
Authentication Protocol adalah protocol yang digunakan dalam mengeluarkan token saat proses otentikasi berhasil dilakukan. Ada beberapa standar yang biasanya digunakan yaitu:
- OAuth 2.0
- SAML
- OpenID
Dalam tutorial ini kita akan menggunakan Oauth 2.0 yang disediakan oleh package Laravel Passsport.
Identity Directory
Identity Directory adalah system yang berisikan basis data dari user. Identity directory adalah sistem yang bertugas untuk menyimpan credentials dari user seperti id, email, username, dan password yang akan digunakan untuk melakukan pengecekan saat proses otentikasi.
Token
Token yang digunakan untuk proses otorisasi saat user akan melakukan akses pada sumber daya yang ada dalam service provider. Tentu saja token ini didapatkan ketika proses otentikasi pada identity provider berhasil. Standarnya, token bisa berupa:
- JSON Web Token atau JWT
- SAML assertion
- OAuth access Token
Arsitektur SSO dengan Laravel
Jika digambarkan, arsitektur dari aplikasi yang akan kita bangun adalah seperti ini:

Pertama, kita akan membuat sebuah project Laravel yang berperan sebagai service provider sekaligus identity provider. Laravel Passport berperang sebagai service provider, Laravel Breeze berperang sebagai identity provider untuk menangani proses login.
Kemudian, kita akan membuat sebuah aplikasi dengan Laravel lagi, yang berperan sebagai aplikasi client atau resource provider. Aplikasi client akan menggunakan service provider sebagai sistem otentikasi.
Alur Kerja
SSO dari workflow yang akan kita adalah sebagai berikut:
- User mengakses resource provider. Jika butuh proses otentikasi, maka dilanjutkan ke tahap 2.
- Service provider akan mengarahkan proses otentikasi ke identity provider dengan membawa identifier inisiasi (random string) untuk pengecekan selanjutnya
- Service provider meminta user untuk melakukan otentikasi. User memasukan credentials pada halaman login (disediakan oleh identity provider), dan identity provider mengecek apakah credentials yang diberikan valid dan cocok dengan yang tersimpan pada identity directory
- Jika credentials valid, identity provider mengirimkan service provider callback beserta dengan user access token dan identifier inisiasi pengenal (random string pada tahap 2)
- Service provider menerima user token dan identifier inisiasi. Jika identifier inisiasi cocok, maka service provider dapat melanjutkan dengan pembuatan session, stateful ataupun stateless dengan JWT
- Token yang diberikan oleh identity provider ke service provider adalah access token dan refresh access token. Kita akan bahas penggunaan dua token ini pada bagian selanjutnya.

Membuat Identity Provider sekaligus Identity Directory
Pertama buat sebuah project Laravel dengan composer. Kita akan menginstall package Laravel Breeze dan Laravel Passport.
composer create-project laravel/laravel laravel-sso-identity
Masuk ke dalam directory laravel-sso-identity-service dan install Laravel Breeze:
cd laravel-sso-identity-service && \ composer require laravel/breeze --dev
Untuk frontend Anda bisa memilih tech stack yang Anda suka. Saya akan memilih Blade dengan Alpine (Blade with Alpine). Untuk pilihan test framework dan dark mode saya serahkan pada preferensi masing-masing:
php artisan breeze:install
Anda bebas menggunakan frontend stack yang Anda inginkan saat melakukan instalasi Breeze. Passport akan menggunakan route
/login
sebagai halaman otentikasi, yang mana sudah secara default terkonfigurasi via Breeze.
Selanjutnya install Laravel Passport:
php artisan install:api --passport
Saat proses installasi Passport, akan muncul prompt mengkonfirmasi apakah ingin meggunakan UUID sebagai ID dari client yang terdaftar. Kembali, ini tergantung dari preferensi Anda, saya tidak akan menggunakan ID dengan UUID
Selanjutnya kita harus mendaftarkan HasApiToken
traits pada User model seperti berikut:
<?php namespace App\Models; use Illuminate\Database\Eloquent\Factories\HasFactory; use Illuminate\Foundation\Auth\User as Authenticatable; use Illuminate\Notifications\Notifiable; use Laravel\Passport\HasApiTokens; class User extends Authenticatable { use HasApiTokens, HasFactory, Notifiable; }
Pada config/auth.php tambahkan api authentication guard, gunakan passport seperti berikut:
return [ // ... 'guards' => [ 'web' => [ 'driver' => 'session', 'provider' => 'users', ], 'api' => [ 'driver' => 'passport', 'provider' => 'users', ], ], // ... ];
Next, sesuaikan beberapa variabel pada .env
APP_NAME="SSO Identity Provider" APP_URL=http://localhost:8000
Terakhir jalankan server identitiy provider dengan php artisan serve --port=8000
. Saya menggunakan port 8000 secara eksplisit untuk memastikan bahwa identity provider, tetap jalan di port tersebut, karena harus sama dengan yang direferensikan oleh client.
php artisan serve --port=8000
Mendaftarkan client (service provider)
Sebelum dapat meminta akses otentikasi, buat credential untuk aplikasi client. .
php artisan passport:client
Sebagai contoh, credential client dengan data berikut:
Client 1:
name: service-provider-1
URL: http://localhost:8001/sso/callback
Catat Client ID dan Client Secreet yang telah dibuat. Informasi tersebut akan kita gunakan sebagai request parameter pada tahap otentikasi. URL adalah endpoint pada sisi client yang akan menerima hasil otentikasi (token) yang akan dikirimkan oleh identity provider ketika otentikasi (login) berhasil.
Membuat user
Untuk menguji proses otentikasi, kita perlu membuat user. Kita bisa menggunakan database seeder atau langsung mengeksekusi query dengan Tinker. Pilih metode yang menurut Anda paling mudah. Saya akan menggunakan database seeder:
php artisan make:seed CreateDummyUser
Kemudian, kita akan buat beberapa 2 user untuk pengujian:
use App\Models\User; public function run(): void { $users = [ [ 'name' => 'Putu Saka', 'email' => '[email protected]', 'password' => Hash::make('password') ], [ 'name' => 'Budi Santooso', 'email' => '[email protected]', 'password' => Hash::make('password') ], ]; User::query()->insert($users); $this->command->info('Dummy users created successfully.'); }
Terakhir, jalankan seeder untuk class CreateDummyUser
php artisan db:seed --class=CreateDummyUser
Mekanisme Otentikasi pada Aplikasi Client
Aplikasi Client (service provider) adalah aplikasi yang meminta proses otentikasi pada identity provider.
Pada Larvel Passport, proses otentikasi dapat diakses melalui path /oauth/authorize
. Aplikasi client mesti melakukan GET request ke path tersebut dengan parameter client_id
, redirect_uri
, response_type
, scope
, dan state
.
Menyiapkan Aplikasi client
Aplikasi ini bisa Anda buat dengan tech stack apapun yang Anda inginkan. Baik dengan stateful ataupun stateless session. Saya akan mencontohkan proses integrasi client dengan menggunakan Laravel.
composer create-project laravel/laravel
Pada .env pastikan
berbeda dengan identity provider. Set APP_NAME
APP_URL
ke http://localhost:8001
APP_NAME="Client 1" APP_URL=http://localhost:8001
Jika service provider dan client berada dalam 1 server yang sama, pastikan
APP_NAME
di masing-masing .env berbeda.
Kemudian kita akan membuat route untuk melakukan otentikasi:
// routes/web.php use App\Http\Controllers\AuthController; Route::get('/auth/', [AuthController::class, 'index'])->name('auth.index');
Siapkan AuthController untuk menangani request:
php artisan make:controller AuthController
Tambahkan index function untuk meredirect ke halaman otentikasi:
<?php namespace App\Http\Controllers; use Illuminate\Http\Request; use Illuminate\Support\Str; class AuthController extends Controller { function index(Request $request) { $request->session()->put('state', $state = Str::random(40)); $query = http_build_query([ 'client_id' => env('SSO_CLIENT_ID'), 'redirect_uri' => 'http://localhost:8001/sso/callback', 'response_type' => 'code', 'scope' => '', 'state' => $state, ]); return redirect('http://localhost:8000/oauth/authorize?' . $query); } }
Tambahkan SSO_CLIENT_SECRET
dan SSO_CLIENT_ID
, dan SSO_CLIENT_CALLBACK_PATH
ke .env. Isi dengan informasi dari Client ID dan Client Secret dari Client 1:
SSO_CLIENT_ID=3 SSO_CLIENT_SECRET="ok6bVVDi2ybwaIWjI0J43t258yl4CYuNwI0hJbmh" SSO_CLIENT_CALLBACK_PATH="${APP_URL}/sso/callback"
Jalankan Client 1 pada port 8001:
php artisan serve --port=8001
Sekarang, akses http://localhost:8001/auth via browser. Anda akan diredirect ke `http://localhost:8000/login`. Login dengan menggunakan email dan password salah satu user yang sebelumnya dibuat.
Jika username dan password valid, maka akan muncul halaman permintaan otorisasi seperti berikut:

Anda bisa mengubah tampilan halaman konfirmasi atau membuatnya exceptional dengan mengikuti instruksi di sini.
Jika Anda mengklik Authorize, maka Anda akan diarahkan kembali ke path http://localhost:8001/sso/callback
. Sampai tahap ini, halaman tersebut akan menampilkan error 404 karena kita belum menambahkan handler untuk route /sso/callback. Mari kita tambahkan:
routes/web.php
<?php use App\Http\Controllers\AuthController; use Illuminate\Support\Facades\Route; Route::get('/', function () { return view('welcome'); }); Route::get('/auth/', [AuthController::class, 'index'])->name('auth.index'); Route::get('/sso/callback', [AuthController::class, 'ssoCallback'])->name('auth.sso'); Route::get('/home', function () { return 'Authenticated!'; })->name('home');
app/Http/Controllers/AuthController.php
use Illuminate\Support\Facades\Http; class AuthController extends Controller { // ... function ssoCallback(Request $request) { $state = $request->session()->pull('state'); throw_unless( strlen($state) > 0 && $state === $request->state, InvalidArgumentException::class ); $response = Http::asForm()->post('http://localhost:8000/oauth/token', [ 'grant_type' => 'authorization_code', 'client_id' => env('SSO_CLIENT_ID'), 'client_secret' => env('SSO_CLIENT_SECRET'), 'redirect_uri' => 'http://localhost:8001/sso/callback', 'code' => $request->code, ]); $request->session()->put('token', $token = $response->json()); return redirect('/home'); } }
Ulangi kembali akses http://localhost:8001/auth
dan lakukan otentikasi kembali. Jika berhasil, Anda akan diarahkan ke path home
dan akan muncul Authenticated! pada browser
Melakukan Otorisasi
Anda bisa menyesuaikan logic dari AuthController::ssoCallback()
sesuai dengan kebutuhan aplikasi pada client. Saat callback dilakukan, identity provider akan mengirimkan token yang bisa Anda gunakan untuk proses otorisasi.
Sebagai contoh, akses ke route home pada client hanya bisa dilkaukan apbila terdapat token pada session dan token tersebut valid pada identity provider.
Untuk membuat mekanisme di atas, kita akan membuat sebuah endpoint pada identity provider untuk mengecek apakah sebuah token valid. Misalkan endpointnya adalah /api/token/check
. Jika token valid, JSON response dengan property message dan object user akan dikembalikan.
Kemudian, dari sisi client, kita akan membuat sebuah middleware untuk mengecek apakah token yang ada pada session client valid, dengan melakukan request ke /api/token/check pada identity provider.
Pada identity provider, tambahkan code berikut pada routes/api.php
Route::get('/token/check', function (Request $request) { return response()->json([ 'message' => 'Token is valid', 'user' => $request->user() ]); })->middleware('auth:api');
Selanjutnya pada client, buat sebuah middleware baru:
php artisan make:middleware SsoAuthMiddleware
Kemudian tambahkan code berikut untuk mengecek apakah token valid atau tidak. Jika valid, lanjutkan request, jika tidak redirect kembali ke auth:
<?php namespace App\Http\Middleware; use Closure; use Illuminate\Http\Request; use Symfony\Component\HttpFoundation\Response; use Illuminate\Support\Facades\Http; use Illuminate\Support\Facades\Session; class SsoAuthMiddleware { /** * Handle an incoming request. * * @param \Closure(\Illuminate\Http\Request): (\Symfony\Component\HttpFoundation\Response) $next */ public function handle(Request $request, Closure $next): Response { // Jika tidak ada token di session, maka redirect ke auth if (!Session::has('token')) { return redirect()->route('auth.index'); } // jika ada token di session, maka lanjutkan request ke endpoint identity provider $token = Session::get('token'); // pastikan tidak ada error pada response token if (isset($token['error'])) { return redirect()->route('auth.index'); } // melakukan pengecekan token ke identity provider // dengan menyertakan access token yang didapat dari session $response = Http::withToken($token['access_token']) ->get('http://localhost:8000/api/token/check'); // jika status response bukan 200, maka redirect ke auth if ($response->status() !== 200) { // Simpan info user ke dalam session return redirect()->route('auth.index'); } // Simpan user info ke dalam session Session::put('user', $response->json()['user']); return $next($request); } }
Tambahkan middleware pada route yang membutuhkan otorisasi. Misalkan pada home:
Route::get('/home', function () { $user = Session::get('user'); return 'Authenticated as ' . $user['name']; })->name('home')->middleware(SsoAuthMiddleware::class);
Tentu saja implementasi SsoAuthMiddleware::handle()
bisa kita sesuaikan dengan kebutuhan aplikasi client. Apabila kita tidak ingin melakukan request ke endpoint identity provider setiap request yang memerlukan otorisasi, kita bisa membuat session pada Client. Sebagai contoh, saat callback kita bisa lakukan seperti ini:
// ... use App\Models\User; use Illuminate\Support\Facades\Auth; class AuthController extends Controller { // ... function ssoCallback(Request $request) { // ... $request->session()->put('token', $token = $response->json()); // Check user detail berdasarkan access_token $response = Http::withToken($token['access_token']) ->get('http://localhost:8000/token/check'); // mendapatkan detail dari User dari identity provider $ssoUser = $response->json()['user']; // Buat atau ambil user dengan email yang sama dari table users. $user = User::firstOrCreate([ 'email' => $ssoUser['email'], ], [ 'name' => $ssoUser['name'], 'password' => bcrypt('password'), ]); // buat session sesuai dengan id user Auth::loginUsingId($user->id); return redirect('/home'); } }
Dengan demikian, kita bisa menggunakan Illuminate\Support\Facades\Auth
class untuk melakukan otorisasi secara lokal pada aplikasi client.
Kesimpulan
Laravel Passport adalah adalah package yang dapat menyederhanakan pembuatan SSO pada framework Laravel. Passport juga bekerja dengan baik dengan Breeze sehingga bisa dikombinasikan menjadi solusi otentikasi dan otorisasi yang handal.