AUTHENTICATION AUTHORIZATION SECURITY OAUTH OPENID-CONNECT WEB-SECURITY FRONTEND-SECURITY BACKEND-SECURITY API-SECURITY SYSTEM-DESIGN SPA MOBILE-APP WEB-DEVELOPMENT BEST-PRACTICES DEVELOPER-EXPERIENCE

Membangun Alur Autentikasi dan Otorisasi yang Aman dan Efisien untuk Berbagai Tipe Klien (SPA, Mobile, Web Tradisional)

⏱️ 11 menit baca
👨‍💻

Membangun Alur Autentikasi dan Otorisasi yang Aman dan Efisien untuk Berbagai Tipe Klien (SPA, Mobile, Web Tradisional)

1. Pendahuluan

Di era aplikasi modern, pengguna berinteraksi dengan layanan kita melalui berbagai platform: browser desktop (Single Page Application/SPA), aplikasi mobile native, atau bahkan aplikasi web tradisional yang dirender di sisi server. Masing-masing platform memiliki karakteristik keamanan dan batasan yang berbeda. Karena itu, merancang alur autentikasi (siapa Anda?) dan otorisasi (apa yang boleh Anda lakukan?) yang aman dan efisien adalah salah satu tantangan terbesar, sekaligus fondasi terpenting, dalam pengembangan aplikasi.

Kesalahan dalam mendesain alur ini bisa berakibat fatal: data pengguna bocor, akun disalahgunakan, atau pengalaman pengguna yang buruk. Di sinilah standar industri seperti OAuth 2.0 dan OpenID Connect (OIDC) hadir sebagai penyelamat. Keduanya menyediakan kerangka kerja yang teruji untuk mengelola identitas dan hak akses secara aman.

Artikel ini akan memandu Anda memahami perbedaan mendasar antara OAuth 2.0 dan OIDC, serta bagaimana memilih dan mengimplementasikan alur yang paling tepat dan aman untuk berbagai tipe klien aplikasi Anda. Mari kita selami!

2. Memahami Fondasi: OAuth 2.0 dan OpenID Connect

Sebelum kita membahas alur spesifik, penting untuk memahami dua protokol kunci ini. Seringkali disalahpahami, keduanya memiliki peran yang berbeda namun saling melengkapi.

OAuth 2.0: Protokol Otorisasi (Bukan Autentikasi!)

OAuth 2.0 adalah standar untuk otorisasi akses delegasi. Bayangkan Anda memberikan izin kepada aplikasi pihak ketiga (misalnya, aplikasi foto) untuk mengakses sebagian data Anda (misalnya, foto di Google Photos) tanpa perlu memberikan password Google Anda kepada aplikasi tersebut.

Konsep-konsep utama dalam OAuth 2.0:

Intinya, OAuth 2.0 menjawab pertanyaan: “Apakah aplikasi ini boleh mengakses data tertentu atas nama pengguna?”

OpenID Connect (OIDC): Lapisan Autentikasi di Atas OAuth 2.0

OIDC adalah lapisan identitas sederhana di atas protokol OAuth 2.0. Jika OAuth 2.0 berfokus pada otorisasi, OIDC berfokus pada autentikasi. OIDC memungkinkan klien untuk memverifikasi identitas pengguna berdasarkan autentikasi yang dilakukan oleh Authorization Server, dan untuk mendapatkan informasi profil dasar tentang pengguna.

OIDC memperkenalkan konsep penting:

📌 Ingat: OAuth 2.0 = Otorisasi, OIDC = Autentikasi. OIDC menggunakan OAuth 2.0 sebagai fondasinya.

3. Alur untuk Aplikasi Single Page (SPA) dan Mobile Native: Authorization Code Flow with PKCE

Untuk klien publik seperti SPA (React, Vue, Angular) atau aplikasi mobile native, di mana client_secret tidak dapat disimpan dengan aman (karena kode dapat diinspeksi), kita harus menggunakan alur yang lebih aman: Authorization Code Flow with PKCE (Proof Key for Code Exchange).

Mengapa tidak Implicit Flow? Implicit Flow dulunya populer untuk SPA, di mana access_token langsung dikembalikan di URL fragment. Namun, ini tidak aman karena token dapat terekspos dalam log browser, histori, atau direbut oleh skrip berbahaya (XSS).

Authorization Code Flow with PKCE adalah Solusi Modern PKCE menambahkan lapisan keamanan ekstra untuk memastikan bahwa code yang diterima oleh klien hanya dapat ditukar oleh klien yang sama yang memulai permintaan otorisasi.

Berikut adalah alurnya:

  1. Klien (SPA/Mobile) membuat code_verifier: String kriptografi acak yang hanya diketahui oleh klien.
  2. Klien membuat code_challenge: code_verifier di-hash (biasanya dengan SHA256) dan di-encode. Ini yang akan dikirim ke Authorization Server.
  3. Klien menyimpan code_verifier: code_verifier disimpan secara lokal (misalnya di sessionStorage untuk SPA, atau penyimpanan aman di mobile).
  4. Klien mengarahkan pengguna ke Authorization Server: Dengan menyertakan client_id, redirect_uri, scope, dan code_challenge.
  5. Pengguna login dan memberikan persetujuan: Di Authorization Server.
  6. Authorization Server mengembalikan code: Setelah pengguna memberikan persetujuan, Authorization Server mengarahkan kembali pengguna ke redirect_uri klien, membawa code.
  7. Klien menukar code dengan token: Klien mengirimkan code, client_id, redirect_uri, dan code_verifier (yang disimpan sebelumnya) ke token endpoint Authorization Server.
  8. Authorization Server memvalidasi: Server memverifikasi bahwa code_challenge yang diterima di langkah 4 cocok dengan code_verifier yang dikirim di langkah 7. Jika cocok, server mengeluarkan access_token, id_token, dan refresh_token.

💡 Tips Implementasi di SPA:

// ✅ Contoh (konseptual) Alur Login di Frontend SPA dengan PKCE

// 1. Pada saat user mengklik tombol "Login":
function initiateLogin() {
    const codeVerifier = generateRandomString(128); // Fungsi helper untuk string acak
    const codeChallenge = sha256(codeVerifier); // Fungsi helper untuk SHA256 hash
    
    // Simpan code_verifier di sessionStorage agar tidak hilang saat redirect
    sessionStorage.setItem('code_verifier', codeVerifier);

    const authUrl = new URL('https://auth.example.com/oauth2/authorize');
    authUrl.searchParams.append('response_type', 'code');
    authUrl.searchParams.append('client_id', 'YOUR_SPA_CLIENT_ID');
    authUrl.searchParams.append('redirect_uri', 'https://your-spa.com/auth/callback');
    authUrl.searchParams.append('scope', 'openid profile email'); // openid wajib untuk OIDC
    authUrl.searchParams.append('code_challenge', codeChallenge);
    authUrl.searchParams.append('code_challenge_method', 'S256'); // Wajib untuk PKCE

    window.location.href = authUrl.toString();
}

// 2. Setelah Authorization Server mengarahkan kembali ke 'https://your-spa.com/auth/callback':
async function handleAuthCallback() {
    const urlParams = new URLSearchParams(window.location.search);
    const code = urlParams.get('code');
    const storedCodeVerifier = sessionStorage.getItem('code_verifier');

    if (!code || !storedCodeVerifier) {
        console.error('Code atau code_verifier tidak ditemukan.');
        // Handle error, redirect ke halaman error
        return;
    }

    sessionStorage.removeItem('code_verifier'); // Hapus setelah digunakan

    try {
        const response = await fetch('https://auth.example.com/oauth2/token', {
            method: 'POST',
            headers: { 
                'Content-Type': 'application/x-www-form-urlencoded' 
            },
            body: new URLSearchParams({
                grant_type: 'authorization_code',
                client_id: 'YOUR_SPA_CLIENT_ID',
                redirect_uri: 'https://your-spa.com/auth/callback',
                code: code,
                code_verifier: storedCodeVerifier
            })
        });

        if (!response.ok) {
            throw new Error(`Gagal menukar kode: ${response.statusText}`);
        }

        const data = await response.json();
        // data akan berisi access_token, id_token, refresh_token (jika diminta scope offline_access)

        // ✅ Simpan token dengan aman (misalnya di memori atau sessionStorage)
        // Untuk refresh_token, pertimbangkan HttpOnly cookie dari backend jika ada
        localStorage.setItem('access_token', data.access_token); // Contoh, sesuaikan dengan strategi Anda
        localStorage.setItem('id_token', data.id_token);
        // Jika backend mengeluarkan refresh_token sebagai HttpOnly cookie,
        // tidak perlu menyimpannya di localStorage/sessionStorage frontend.

        console.log('Login berhasil!', data);
        window.location.href = '/dashboard'; // Redirect ke halaman utama aplikasi
    } catch (error) {
        console.error('Error saat handle callback:', error);
        // Handle error, redirect ke halaman error
    }
}

// Helper functions (implementasi detail bisa bervariasi)
function generateRandomString(length) { /* ... */ }
function sha256(plain) { /* ... */ }

4. Alur untuk Aplikasi Web Tradisional (Server-Rendered): Authorization Code Flow (Tanpa PKCE)

Aplikasi web tradisional (misalnya, yang dibangun dengan Laravel, Spring Boot, atau ASP.NET MVC) memiliki server backend yang dapat menjaga kerahasiaan client_secret. Oleh karena itu, kita dapat menggunakan Authorization Code Flow tanpa PKCE, karena client_secret sudah cukup untuk mengamankan pertukaran kode.

Berikut adalah alurnya:

  1. Browser mengarahkan pengguna ke Authorization Server: Dengan menyertakan client_id, redirect_uri, dan scope.
  2. Pengguna login dan memberikan persetujuan: Di Authorization Server.
  3. Authorization Server mengembalikan code: Setelah pengguna memberikan persetujuan, Authorization Server mengarahkan kembali pengguna ke redirect_uri di server backend aplikasi Anda, membawa code.
  4. Server backend menukar code dengan token: Server backend aplikasi Anda mengirimkan code, client_id, client_secret, dan redirect_uri ke token endpoint Authorization Server.
  5. Authorization Server memvalidasi: Server memverifikasi client_id dan client_secret. Jika valid, server mengeluarkan access_token, id_token, dan refresh_token.
  6. Server backend membuat session: Server backend menyimpan token-token ini (biasanya di database atau memori server) dan kemudian membuat session lokal untuk pengguna di browser (misalnya, melalui HttpOnly cookie).

Keuntungan: client_secret tetap aman di server backend, dan token akses tidak pernah terekspos langsung ke browser pengguna. Interaksi pengguna dengan token diurus sepenuhnya di sisi server.

⚠️ Penting: Session cookie yang dibuat oleh server backend harus diatur dengan atribut HttpOnly (untuk mencegah akses JS), Secure (hanya dikirim melalui HTTPS), dan SameSite=Lax atau Strict (untuk perlindungan CSRF).

5. Mengelola Token dengan Aman dan Efisien

Bagaimana kita menyimpan dan menggunakan token-token ini setelah didapatkan? Strategi penyimpanan sangat bergantung pada tipe token dan tipe klien.

Access Token

Refresh Token

ID Token

6. Strategi Keamanan Tambahan dan Best Practices

Melengkapi alur autentikasi dengan praktik keamanan tambahan akan membuat aplikasi Anda lebih tangguh.