Single Sign On: Model Token Akses Global

Implementasi di Aplikasi/Sistem Informasi [memanfaatkan username SSO]

Interceptor

Alur ini dijalankan pada waktu klien membuka sembarang halaman. Intinya hanya 2, yaitu:

  1. apabila belum ada sesi login SSO, maka klien diminta untuk login
  2. 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()]);
        }
    }
}

Pages: 1 2 3 4


Comments

Leave a Reply

Your email address will not be published. Required fields are marked *

This site uses Akismet to reduce spam. Learn how your comment data is processed.