OAuth 2.0 Flow — Auth

OAuth 2.0 adalah framework otorisasi yang memungkinkan aplikasi pihak ketiga mendapat akses terbatas ke resource pengguna tanpa mengetahui password mereka…

OAuth 2.0 adalah cara sebuah aplikasi mendapat izin terbatas untuk memakai akunmu di layanan lain, tanpa pernah tahu password-mu. Bayangin kunci valet mobil: kamu gak menyerahkan kunci utama ke petugas parkir, tapi kunci khusus yang cuma bisa menyalakan mesin dan membuka pintu, gak bisa membuka bagasi. Di OAuth, kunci khusus itu namanya access token, dan batas "cuma mesin dan pintu" itu namanya scope.

🧪 Tebakan cepat: di sepanjang alur yang dibahas di sini, apakah access token pernah lewat address bar browser? Tebak dulu, jawabannya ada di bagian "Kenapa code dulu, baru token".

Contohnya: Portal Sekolah ke Google Calendar

Pak Andi, guru di Portal Sekolah, ingin jadwal ujian otomatis masuk ke Google Calendar-nya. Portal Sekolah butuh izin menulis acara di kalender itu, tapi gak boleh tahu password Google Pak Andi. Empat pihak terlibat:

Peran di OAuth Di contoh ini
Resource owner: pemilik data yang memberi izin Pak Andi
Client: aplikasi yang minta izin server Portal Sekolah
Authorization server: yang menanyakan izin dan memberi token Google (accounts.google.com, oauth2.googleapis.com)
Resource server: yang menyimpan datanya Google Calendar API

Sebelum mulai, Portal Sekolah mendaftar di Google Cloud Console dan mendapat client_id (identitas aplikasi, boleh terlihat publik) dan client_secret (rahasia, cuma disimpan di server). Saat mendaftar, Portal Sekolah juga mencatat redirect_uri, alamat tempat Google mengembalikan Pak Andi: https://portal.sekolah.test/oauth/google/callback. Scope yang diminta: https://www.googleapis.com/auth/calendar.events, artinya cuma acara kalender, bukan email atau Drive.

Alur yang dipakai bernama Authorization Code + PKCE, delapan langkah, persis seperti di Lab. Kodenya ditulis dalam JavaScript supaya bisa dijalankan di Lab dan di Node. Di Laravel, Socialite mengerjakan langkah yang sama (lesson Social Login).

Langkah 1-2: state, PKCE, lalu URL authorize

Pak Andi menekan "Hubungkan Google Calendar". Server Portal menyiapkan dua nilai acak dan menyimpannya di session Pak Andi:

function base64url(bytes) {
  return btoa(String.fromCharCode(...bytes)).replace(/\+/g, "-").replace(/\//g, "_").replace(/=+$/, "");
}

async function startLogin(session) {
  session.state = base64url(crypto.getRandomValues(new Uint8Array(16)));
  session.verifier = base64url(crypto.getRandomValues(new Uint8Array(32))); // 43 karakter
  const digest = await crypto.subtle.digest("SHA-256", new TextEncoder().encode(session.verifier));
  const params = new URLSearchParams({
    response_type: "code",
    client_id: "1234567890-portal.apps.googleusercontent.com",
    redirect_uri: "https://portal.sekolah.test/oauth/google/callback",
    scope: "https://www.googleapis.com/auth/calendar.events",
    state: session.state,
    code_challenge: base64url(new Uint8Array(digest)),
    code_challenge_method: "S256",
  });
  return "https://accounts.google.com/o/oauth2/v2/auth?" + params; // jawab dengan 302 ke sini
}

Dengan nilai contoh dari RFC 7636 yang dipakai Lab, verifier dBjftJeZ4CVP-mB92K27uhbUJU1p1r_wW1gFWFOEjXk menghasilkan challenge E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM, dan URL-nya jadi:

https://accounts.google.com/o/oauth2/v2/auth?response_type=code&client_id=1234567890-portal.apps.googleusercontent.com&redirect_uri=https%3A%2F%2Fportal.sekolah.test%2Foauth%2Fgoogle%2Fcallback&scope=https%3A%2F%2Fwww.googleapis.com%2Fauth%2Fcalendar.events&state=af0ifjsldkj&code_challenge=E9Melhoa2OwvFrEMTJguCHaoeK1t8URWbuGJSstw-cM&code_challenge_method=S256

Langkah 3-5: persetujuan dan callback

Di Google, Pak Andi login (password-nya diketik di Google, bukan di Portal) dan melihat layar persetujuan: Portal Sekolah ingin melihat dan mengedit acara kalendernya. Dia menekan Izinkan. Google membuat authorization code dan mencatatnya bersama code_challenge, lalu mengembalikan browser ke redirect_uri:

https://portal.sekolah.test/oauth/google/callback?state=af0ifjsldkj&code=SplxlOBeZQQYbYS6WxSbIA

Kalau Pak Andi menekan Batal, callback-nya membawa ?error=access_denied&state=af0ifjsldkj. Itu jawaban yang sah, bukan crash: client harus menanganinya.

async function handleCallback(session, callbackUrl) {
  const query = new URL(callbackUrl).searchParams;
  const { state, verifier } = session;
  delete session.state; // sekali pakai
  delete session.verifier;
  if (!state || query.get("state") !== state) throw new Error("state tidak cocok");
  if (query.get("error")) return { error: query.get("error") }; // access_denied: Pak Andi menekan Batal

  const res = await fetch("https://oauth2.googleapis.com/token", {
    method: "POST",
    body: new URLSearchParams({
      grant_type: "authorization_code",
      code: query.get("code"),
      redirect_uri: "https://portal.sekolah.test/oauth/google/callback",
      client_id: "1234567890-portal.apps.googleusercontent.com",
      client_secret: process.env.GOOGLE_CLIENT_SECRET, // cuma ada di server
      code_verifier: verifier,
    }),
  });
  return res.json();
}

Langkah 6-7: tukar code jadi token

Request fetch tadi dikirim dari server Portal langsung ke Google, gak lewat browser. Body-nya membawa code, client_secret, dan code_verifier. Google menghitung SHA-256 dari verifier itu dan membandingkannya dengan challenge yang dicatat di langkah 3. Cocok, Google menjawab:

{
  "access_token": "2YotnFZFEjr1zCsicMWpAA",
  "expires_in": 3599,
  "scope": "https://www.googleapis.com/auth/calendar.events",
  "token_type": "Bearer"
}

Langkah 8: Portal memanggil POST https://www.googleapis.com/calendar/v3/calendars/primary/events dengan header Authorization: Bearer 2YotnFZFEjr1zCsicMWpAA, dan jadwal "Ujian Matematika 8A" masuk ke kalender Pak Andi. Token ini berlaku sekitar 1 jam (3599 detik), dan cuma untuk scope kalender.

Kenapa code dulu, baru token

Yang lewat browser (langkah 2 sampai 5) cuma URL: alamat authorize dan callback berisi code. URL bisa tersimpan di history, log server, atau header Referer. Karena itu yang dikirim lewat URL adalah authorization code, yang sekali pakai, berumur pendek, dan gak berguna tanpa client_secret dan code_verifier. Access token-nya sendiri datang sebagai jawaban request langkah 6, server ke server, jadi gak pernah muncul di URL atau history browser. Itu jawaban tebakannya: gak pernah.

Alur lama, Implicit Flow (response_type=token), menaruh access token langsung di URL. Alur itu sudah gak dianjurkan. Sekarang semua client, termasuk SPA dan aplikasi mobile, memakai authorization code + PKCE.

📌 Grant type lain

Grant type Dipakai untuk
Authorization Code + PKCE ada user yang memberi izin: web, SPA, mobile
Client Credentials mesin ke mesin, tanpa user, misalnya job malam yang memanggil API lain
Device Code perangkat tanpa keyboard (TV, konsol): layar menampilkan kode, user menyetujui dari HP

Login dengan Google, yang memakai alur yang sama ditambah identitas user, dibahas di lesson berikutnya, Social Login.

⚠️ Jebakan yang sering ketemu

TL;DR: OAuth 2.0 memberi aplikasi izin terbatas (scope) tanpa password. Alurnya authorization code + PKCE: client membuat state dan code_verifier, mengirim code_challenge = base64url(SHA-256(verifier)) di URL authorize yang dibangun dengan URLSearchParams, user menyetujui di provider, callback membawa code dan state (cocokkan state, tangani error=access_denied), lalu server menukar code + verifier + client_secret jadi access token, server ke server. Yang diterima client lewat browser cuma authorization code, jadi token gak pernah muncul di URL. Untuk mesin ke mesin: Client Credentials. Implicit Flow sudah usang.

Yang akan kamu pelajari