Implementasi di Aplikasi/Sistem Informasi [memanfaatkan username SSO]
Interceptor
Alur ini dijalankan pada waktu klien membuka sembarang halaman. Intinya hanya 2, yaitu:
- apabila belum ada sesi login SSO, maka klien diminta untuk login
- apabila sudah ada sesi login SSO, maka klien bisa langsung membuka halaman yang dituju, sesuai dengan hak aksesnya
Java
import jakarta.servlet.*;
import jakarta.servlet.annotation.WebFilter;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;
import jakarta.servlet.http.HttpSession;
import java.io.IOException;
// Menjaga semua halaman di dalam folder /dashboard/*
@WebFilter("/dashboard/*")
public class SsoAuthorizationFilter implements Filter {
@Override
public void doFilter(ServletRequest request, ServletResponse response, FilterChain chain)
throws IOException, ServletException {
HttpServletRequest httpRequest = (HttpServletRequest) request;
HttpServletResponse httpResponse = (HttpServletResponse) response;
HttpSession session = httpRequest.getSession(false);
// Cek apakah session null atau atribut login tidak valid
if (session == null || session.getAttribute("user_logged_in") == null) {
String authorizeUrl = "https://sso.utama.id/authorize?client_id=java_app&response_type=code";
/* =================================================================
* OPSI KEAMANAN TINGKAT TINGGI: PKCE CHALLENGE (BUKA JIKA DIBUTUHKAN)
* =================================================================
* // 1. Buat string acak (code_verifier) dan simpan di session untuk tahap callback nanti
* String codeVerifier = "RAHASIA_KUNCI_ASLI_PKCE_YANG_BELUM_DI_HASH_CONTOH_ABC123";
* httpRequest.getSession(true).setAttribute("sso_code_verifier", codeVerifier);
* * // 2. Buat hash SHA-256 dari code_verifier, lalu di-encode ke Base64URL (code_challenge)
* String codeChallenge = "HASIL_HASH_SHA256_DARI_CODE_VERIFIER_DI_ATAS_BASE64_URL";
* * // 3. Tempelkan tantangan ke URL gerbang SSO
* authorizeUrl += "&code_challenge=" + codeChallenge + "&code_challenge_method=S256";
* ================================================================= */
// Alihkan ke gerbang SSO
httpResponse.sendRedirect(authorizeUrl);
return; // Hentikan eksekusi script
}
// Jika lolos, izinkan request melanjutkan perjalanan ke halaman tujuan
chain.doFilter(request, response);
}
}Laravel
namespace App\Http\Middleware;
use Closure;
use Illuminate\Http\Request;
use Illuminate\Support\Str;
class SsoInterceptor
{
public function handle(Request $request, Closure $next)
{
// Cek apakah session lokal 'user_logged_in' tidak ada
if (!$request->session()->has('user_logged_in')) {
$ssoUrl = 'https://sso.utama.id/authorize?client_id=laravel_app&response_type=code';
/* =================================================================
* OPSI KEAMANAN TINGKAT TINGGI: PKCE CHALLENGE (BUKA JIKA DIBUTUHKAN)
* =================================================================
* // 1. Generate code_verifier acak & simpan aman di session internal Laravel
* $codeVerifier = Str::random(64);
* session(['sso_code_verifier' => $codeVerifier]);
* * // 2. Lakukan hash SHA-256 dan ubah ke format Base64 URL Safe
* $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');
* * // 3. Titipkan tantangan ke URL authorize
* $ssoUrl .= '&code_challenge=' . $codeChallenge . '&code_challenge_method=S256';
* ================================================================= */
// Arahkan ke gerbang SSO
return redirect($ssoUrl);
}
// Jika lolos, silakan lanjut masuk ke dashboard/halaman internal
return $next($request);
}
}Code Igniter
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class SsoFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$session = session();
// Cek apakah session server kosong
if (!$session->get('user_logged_in')) {
// Redirect ke gerbang SSO
return redirect()->to('https://sso.utama.id/authorize?client_id=ci_app');
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Tidak perlu melakukan apa-apa setelah request selesai
}
}Pembuatan Token
Java
namespace App\Filters;
use CodeIgniter\Filters\FilterInterface;
use CodeIgniter\HTTP\RequestInterface;
use CodeIgniter\HTTP\ResponseInterface;
class SsoFilter implements FilterInterface
{
public function before(RequestInterface $request, $arguments = null)
{
$session = session();
// Cek apakah session server kosong
if (!$session->get('user_logged_in')) {
$ssoUrl = 'https://sso.utama.id/authorize?client_id=ci_app&response_type=code';
/* =================================================================
* OPSI KEAMANAN TINGKAT TINGGI: PKCE CHALLENGE (BUKA JIKA DIBUTUHKAN)
* =================================================================
* // 1. Generate code_verifier acak & simpan di session CI4
* helper('text');
* $codeVerifier = random_string('alnum', 64);
* $session->set('sso_code_verifier', $codeVerifier);
* * // 2. Lakukan hash SHA-256 dan bersihkan formatnya menjadi Base64URL Safe
* $codeChallenge = rtrim(strtr(base64_encode(hash('sha256', $codeVerifier, true)), '+/', '-_'), '=');
* * // 3. Masukkan tantangan ke parameter URL
* $ssoUrl .= '&code_challenge=' . $codeChallenge . '&code_challenge_method=S256';
* ================================================================= */
// Redirect ke gerbang SSO
return redirect()->to($ssoUrl);
}
}
public function after(RequestInterface $request, ResponseInterface $response, $arguments = null)
{
// Tidak perlu melakukan apa-apa setelah request selesai
}
}Laravel
namespace App\Http\Controllers;
use Illuminate\Http\Request;
use Illuminate\Support\Facades\Http;
class SsoController extends Controller
{
public function callback(Request $request)
{
// Tangkap authorization code dari query string
$code = $request->query('code');
if (!$code) {
return response()->json(['error' => 'Authorization code tidak valid'], 400);
}
// Data standar pertukaran token
$payload = [
'grant_type' => 'authorization_code',
'code' => $code,
'client_id' => 'laravel_app',
'client_secret' => 'rahasia_client_laravel',
'redirect_uri' => 'https://aplikasi.id/sso/callback',
];
/* =================================================================
* PROSES PKCE: MENYERAHKAN KUNCI ASLI (BUKA JIKA INTERCEPTOR AKTIF)
* =================================================================
* $payload['code_verifier'] = session()->get('sso_code_verifier');
* ================================================================= */
// 1. Eksekusi Tukar Token via HTTP Client bawaan Laravel
$tokenResponse = Http::asForm()->post('https://sso.utama.id/oauth/token.php', $payload);
if ($tokenResponse->failed()) {
return response()->json(['error' => 'Gagal menukarkan token di server SSO'], 500);
}
$accessToken = $tokenResponse->json('access_token');
// 2. Ambil Data Profil User dari endpoint userinfo
$userResponse = Http::withToken($accessToken)->get('https://sso.utama.id/oauth/userinfo.php');
if ($userResponse->failed()) {
return response()->json(['error' => 'Gagal mengambil data profil dari server SSO'], 500);
}
$userData = $userResponse->json();
// 3. Mengunci Sesi Lokal Laravel (Sehingga SsoInterceptor memberikan lampu hijau)
session([
'user_logged_in' => true,
'username' => $userData['username'],
'nama_lengkap' => $userData['name'],
'email' => $userData['email']
]);
// Alihkan user langsung ke halaman dashboard internal Laravel
return redirect()->to('/dashboard');
}
}Code Igniter
namespace App\Controllers;
class Sso extends BaseController
{
public function callback()
{
$request = service('request');
$session = session();
// Ambil code dari parameter URL
$code = $request->getGet('code');
if (empty($code)) {
return $this->response->setStatusCode(400)->setJSON(['error' => 'Code tidak ditemukan']);
}
// Data standar pertukaran token
$formParams = [
'grant_type' => 'authorization_code',
'code' => $code,
'client_id' => 'ci_app',
'client_secret' => 'rahasia_client_ci',
'redirect_uri' => 'https://aplikasi.id/sso/callback',
];
/* =================================================================
* PROSES PKCE: MENYERAHKAN KUNCI ASLI (BUKA JIKA INTERCEPTOR AKTIF)
* =================================================================
* $formParams['code_verifier'] = $session->get('sso_code_verifier');
* ================================================================= */
// Ambil instance HTTP Client CI4
$client = \Config\Services::curlrequest();
try {
// 1. Ambil Access Token dari token.php
$tokenResponse = $client->request('POST', 'https://sso.utama.id/oauth/token.php', [
'form_params' => $formParams
]);
$tokenData = json_decode($tokenResponse->getBody(), true);
$accessToken = $tokenData['access_token'];
// 2. Ambil data profil dari userinfo.php
$userResponse = $client->request('GET', 'https://sso.utama.id/oauth/userinfo.php', [
'headers' => [
'Authorization' => 'Bearer ' . $accessToken,
'Accept' => 'application/json'
]
]);
$userData = json_decode($userResponse->getBody(), true);
// 3. Set Session CI4 agar lolos dari jeratan SsoFilter
$session->set([
'user_logged_in' => true,
'username' => $userData['username'],
'nama' => $userData['name'],
'email' => $userData['email']
]);
// Redirect paksa masuk ke area dashboard internal aplikasi CI4
return redirect()->to('/dashboard');
} catch (\Exception $e) {
return $this->response->setStatusCode(500)->setJSON(['error' => 'Gagal jabat tangan: ' . $e->getMessage()]);
}
}
}
Leave a Reply