Panduan Integrasi React.js
Panduan ini menjelaskan cara mengintegrasikan DUMEG Identity (Keycloak SSO) ke dalam aplikasi React.js (SPA) menggunakan TypeScript, Vite, dan library terawat react-oidc-context bersama oidc-client-ts.
Status Kode Referensi (Starter Template)
Contoh kode dan aplikasi demo di bawah ini adalah implementasi referensi (starter kit) yang telah diverifikasi alur protokolnya secara lokal menggunakan Mock OIDC Server, namun belum diuji menggunakan server Keycloak nyata (karena registrasi client resmi pada realm Keycloak production masih menunggu alokasi tim IAM). Kode ini BUKAN implementasi siap-production secara langsung. Setiap tim wajib melakukan penyesuaian dengan kebutuhan routing, state management, dan audit keamanan internal DUMEG sebelum diaplikasikan ke lingkungan production.
1. Arsitektur Integrasi React (Public Client)
Aplikasi React SPA berjalan sepenuhnya di sisi browser pengguna tanpa backend khusus, sehingga didaftarkan sebagai Public OIDC Client.
flowchart LR
A[Browser / React App\nlocalhost:5174] -->|1. Generate PKCE & Redirect| B{DUMEG Identity\nlogin.dumeg.com}
B -->|2. Callback dengan Code & State| A
A -->|3. Tukar Code + Verifier via HTTPS| B
B -->|4. Kembalikan ID & Access Token| A
A -->|5. Simpan di SessionStorage / Memory| C[(React Auth Context)]Prinsip Keamanan Public Client:
- DILARANG MENGGUNAKAN
client_secret: Karena kode JavaScript browser dapat diinspeksi oleh siapa saja, public client tidak memiliki rahasia client. - Wajib PKCE S256: Pertukaran kode otorisasi dilindungi secara kriptografis menggunakan Proof Key for Code Exchange (RFC 7636).
- In-Memory Storage (Prioritas): Token disimpan dalam memori runtime JavaScript (
InMemoryWebStorage) untuk menghindari paparan di Web Storage (localStorageatausessionStorage).
Batasan Keamanan In-Memory Storage & XSS
Menyimpan token dalam memori JavaScript secara efektif menghilangkan persistensi token pada Web Storage (localStorage/sessionStorage), sehingga mencegah skrip jahat mencuri token via storage exfiltration.
Namun, penyimpanan dalam memori TIDAK membuat aplikasi kebal dari serangan XSS. Jika penyerang berhasil menyuntikkan skrip ke dalam DOM, penyerang tetap dapat menginspeksi memori objek JavaScript atau mengeksekusi request API atas nama pengguna (session riding). Sanitasi input, escaping output, dan penerapan Content Security Policy (CSP) tetap merupakan pertahanan utama yang wajib diimplementasikan.
2. Persyaratan & Spesifikasi
- Runtime: Node.js versi 20.x atau 22.x LTS
- Build Tool: Vite 5.x / 6.x
- Framework: React 18.x / 19.x (TypeScript)
- Port Demo Lokal:
http://localhost:5174
Dependencies yang Diperlukan:
npm install react-oidc-context oidc-client-ts react-router-dom3. Konfigurasi OIDC Client di Keycloak
Untuk menggunakan aplikasi React di lingkungan lokal, ajukan pendaftaran client publik dengan parameter berikut:
| Parameter | Nilai Konfigurasi | Keterangan |
|---|---|---|
| Client ID | dumeg-react-demo | Identifier unik aplikasi |
| Client Authentication | Off (Public Client) | Tidak memerlukan client secret |
| Authentication Flow | Standard Flow (Authorization Code) | OIDC Standard |
| PKCE Enforcement | S256 | Wajib untuk public client |
| Valid Redirect URIs | http://localhost:5174/callbackhttp://localhost:5174/ | URL tujuan setelah login |
| Valid Post Logout URIs | http://localhost:5174/ | URL tujuan setelah logout |
| Web Origins (CORS) | http://localhost:5174 | Mengizinkan request token via browser |
4. Konfigurasi Environment (.env.example)
Buat file .env pada root proyek React:
# Konfigurasi DUMEG Identity (Keycloak) untuk React SPA
VITE_DUMEG_AUTHORITY="https://login.dumeg.com/realms/dumeg-internal"
VITE_DUMEG_CLIENT_ID="dumeg-react-demo"
VITE_DUMEG_REDIRECT_URI="http://localhost:5174/callback"
VITE_DUMEG_POST_LOGOUT_URI="http://localhost:5174/"
VITE_DUMEG_SCOPE="openid profile email"Tidak Ada Client Secret
Variabel di atas berawalan VITE_ dan dapat dibaca oleh browser. Jangan pernah menambahkan client secret di file ini.
5. Implementasi Kode
A. Konfigurasi OIDC Provider (src/config/oidc.ts)
Gunakan InMemoryWebStorage untuk token (in-memory) dan sessionStorage untuk parameter state PKCE:
// src/config/oidc.ts
import { AuthProviderProps } from 'react-oidc-context'
import { WebStorageStateStore, InMemoryWebStorage } from 'oidc-client-ts'
export const oidcConfig: AuthProviderProps = {
authority: import.meta.env.VITE_DUMEG_AUTHORITY || 'https://login.dumeg.com/realms/dumeg-internal',
client_id: import.meta.env.VITE_DUMEG_CLIENT_ID || 'dumeg-react-demo',
redirect_uri: import.meta.env.VITE_DUMEG_REDIRECT_URI || 'http://localhost:5174/callback',
post_logout_redirect_uri: import.meta.env.VITE_DUMEG_POST_LOGOUT_URI || 'http://localhost:5174/',
response_type: 'code',
scope: import.meta.env.VITE_DUMEG_SCOPE || 'openid profile email',
// KEAMANAN: Simpan token dalam memori JavaScript murni (InMemoryWebStorage)
// Mencegah pencurian kredensial via script scraping localStorage/sessionStorage
userStore: new WebStorageStateStore({ store: new InMemoryWebStorage() }),
// State dan code_verifier PKCE harus bertahan selama navigasi redirect browser
stateStore: new WebStorageStateStore({ store: window.sessionStorage }),
// Otomatis memperbarui token sebelum expired
automaticSilentRenew: true,
// Tangani pembersihan URL callback
onSigninCallback: () => {
window.history.replaceState({}, document.title, window.location.pathname)
},
}B. Inisialisasi Root Provider (src/main.tsx)
// src/main.tsx
import React from 'react'
import ReactDOM from 'react-dom/client'
import { AuthProvider } from 'react-oidc-context'
import { BrowserRouter } from 'react-router-dom'
import App from './App'
import { oidcConfig } from './config/oidc'
import './styles/app.css'
ReactDOM.createRoot(document.getElementById('root')!).render(
<React.StrictMode>
<AuthProvider {...oidcConfig}>
<BrowserRouter>
<App />
</BrowserRouter>
</AuthProvider>
</React.StrictMode>
)C. Komponen Rute Terproteksi (src/components/ProtectedRoute.tsx)
// src/components/ProtectedRoute.tsx
import React from 'react'
import { useAuth } from 'react-oidc-context'
import { Navigate } from 'react-router-dom'
interface Props {
children: React.ReactNode
}
export const ProtectedRoute: React.FC<Props> = ({ children }) => {
const auth = useAuth()
if (auth.isLoading) {
return <div className="loading-state">Memeriksa status autentikasi...</div>
}
if (!auth.isAuthenticated) {
return <Navigate to="/" replace />
}
return <>{children}</>
}D. Penanganan Login & Profil (src/pages/HomePage.tsx)
// src/pages/HomePage.tsx
import React from 'react'
import { useAuth } from 'react-oidc-context'
import { Link } from 'react-router-dom'
export const HomePage: React.FC = () => {
const auth = useAuth()
if (auth.isLoading) {
return <div className="loading-state">Memuat layanan identitas...</div>
}
if (auth.error) {
return (
<div className="error-box">
<h3>Terjadi Kesalahan Autentikasi</h3>
<p>{auth.error.message}</p>
<button onClick={() => auth.signinRedirect()}>Coba Login Kembali</button>
</div>
)
}
return (
<div className="home-container">
<h1>Portal Aplikasi React</h1>
<p>Terintegrasi dengan DUMEG Identity Single Sign-On.</p>
{auth.isAuthenticated ? (
<div className="user-banner">
<p>Selamat datang, <strong>{auth.user?.profile.name || auth.user?.profile.preferred_username}</strong>!</p>
<div className="btn-group">
<Link to="/dashboard" className="btn-primary">Buka Dashboard</Link>
<button onClick={() => auth.signoutRedirect()} className="btn-secondary">Logout</button>
</div>
</div>
) : (
<div className="login-action">
<button onClick={() => auth.signinRedirect()} className="btn-login-dumeg">
Masuk dengan DUMEG Identity
</button>
</div>
)}
</div>
)
}6. Alur Logout Terpusat (RP-Initiated Logout)
Memanggil auth.signoutRedirect() akan:
- Menghapus data token dari
sessionStoragebrowser. - Mengarahkan browser ke endpoint logout Keycloak:
https://login.dumeg.com/realms/dumeg-internal/protocol/openid-connect/logout - Mengakhiri sesi SSO Keycloak di browser, lalu me-redirect kembali ke
post_logout_redirect_uri.
7. Troubleshooting Masalah Umum
| Masalah / Error | Penyebab | Solusi |
|---|---|---|
| CORS error saat menukar code | Web Origins di Keycloak belum diatur | Tambahkan http://localhost:5174 pada kolom Web Origins di konfigurasi client Keycloak. |
| Invalid redirect_uri | Redirect URI tidak cocok persis | Pastikan http://localhost:5174/callback terdaftar di Valid Redirect URIs Keycloak. |
| PKCE code_verifier mismatch | sessionStorage terhapus atau dibersihkan ekstensi | Pastikan tidak ada ekstensi browser pemblokir cookie/storage yang menghapus verifier sebelum callback selesai. |
| Token hilang saat buka tab baru | Penggunaan sessionStorage | Karakteristik normal sessionStorage. Gunakan silent renew untuk mengaktifkan sesi di tab baru tanpa login ulang. |
8. Menjalankan Aplikasi Contoh Lokal
Aplikasi contoh React lengkap tersedia di direktori examples/react-sso:
cd examples/react-sso
npm install
cp .env.example .env
npm run devBuka browser pada alamat: http://localhost:5174
Pengujian Lokal Menggunakan Mock OIDC
Proyek ini menyediakan server Mock OIDC lokal di examples/mock-oidc (port 8088) yang dapat digunakan untuk menguji alur login PKCE, callback, pembacaan claims identitas, dan in-memory storage secara deterministik sebelum client resmi didaftarkan di Keycloak production.