Social login adalah login memakai akun yang sudah kamu punya di layanan lain, misalnya Google, jadi gak perlu password baru. Bayangin acara antarsekolah. Panitia gak membuat kartu tanda pengenal baru untukmu. Mereka melihat kartu pelajar yang dikeluarkan sekolahmu, mengecek kartunya asli, sekolah penerbitnya benar, ditujukan untuk acara ini, dan belum kedaluwarsa, lalu mencocokkan nomor induknya dengan daftar peserta. Di social login, kartu itu namanya id_token, penerbitnya Google, dan nomor induknya klaim sub.
🧪 Tebakan cepat: Siti sudah punya akun Portal Sekolah dengan email
[email protected]. Dia menekan "Login dengan Google" untuk pertama kali, dan akun Google-nya memakai email yang sama, sudah terverifikasi. Portal sebaiknya membuat akun baru, atau menyambungkan ke akun Siti yang lama? Tebak dulu, jawabannya ada di bagian "Menyambungkan ke akun".
OAuth + OpenID Connect
Lesson OAuth 2.0 memberi Portal Sekolah izin memakai kalender Pak Andi. Itu authorization. Untuk login, Portal butuh hal lain: siapa yang baru saja login. Access token gak menjawab itu, karena dia cuma kunci untuk memanggil API.
OpenID Connect (OIDC) adalah lapisan di atas OAuth 2.0 yang menambahkan identitas. Alurnya sama persis (authorization code, state, PKCE), bedanya dua:
- Scope-nya diawali
openid:openid email profile. - Jawaban
/tokenmembawa satu token lagi,id_token: sebuah JWT yang ditandatangani Google, berisi klaim tentang user yang login.
Jadi di balik tombol "Login dengan Google" bekerja OAuth 2.0 untuk otorisasi, plus OpenID Connect untuk identitas. Ini payload id_token Siti, persis seperti di Lab (login 9 Oktober 2026 jam 07.00 WIB):
{
"iss": "https://accounts.google.com",
"aud": "1234567890-portal.apps.googleusercontent.com",
"sub": "110169484474386276334",
"email": "[email protected]",
"email_verified": true,
"name": "Siti",
"iat": 1791504000,
"exp": 1791507600
}
Cek id_token, berurutan
Membaca payload belum berarti percaya. Server memeriksanya dengan urutan ini dan berhenti di pemeriksaan pertama yang gagal:
| Urutan | Klaim | Harus | Kalau gagal |
|---|---|---|---|
| 1 | tanda tangan | cocok dengan public key Google (https://www.googleapis.com/oauth2/v3/certs) |
token palsu atau diubah |
| 2 | iss |
https://accounts.google.com (atau accounts.google.com) |
bukan dari Google |
| 3 | aud |
client_id Portal Sekolah |
token itu dibuat untuk aplikasi lain |
| 4 | exp |
lebih besar dari waktu sekarang, dalam detik Unix | sudah kedaluwarsa |
| 5 | sub |
ada | gak ada ID akun |
Tanda tangan diperiksa library dengan kunci publik Google, seperti JWT di lesson sebelumnya, cuma algoritmanya RS256. Klaim-klaimnya:
const GOOGLE_CLIENT_ID = "1234567890-portal.apps.googleusercontent.com";
function decodePayload(idToken) {
const part = idToken.split(".")[1].replace(/-/g, "+").replace(/_/g, "/");
return JSON.parse(atob(part)); // cuma membaca, belum memverifikasi
}
function checkClaims(claims, nowSeconds) {
if (claims.iss !== "https://accounts.google.com" && claims.iss !== "accounts.google.com") return "iss";
if (claims.aud !== GOOGLE_CLIENT_ID) return "aud";
if (claims.exp <= nowSeconds) return "exp";
if (!claims.sub) return "sub";
return null; // lolos
}
audpaling sering dilupakan. Token Siti untuk Kantinku, aplikasi kantin sekolah, juga asli dari Google, tapiaud-nya client_id Kantinku. Portal harus menolaknya.expdalam detik: jam 07.05 adalah1791504300, jadi token yangexp-nya1791500400(06.00) ditolak.
Menyambungkan ke akun
Setelah token lolos, Portal mencari user-nya dengan urutan ini:
function findOrLink(db, claims) {
const social = db.socialAccounts.find((s) => s.provider === "google" && s.providerId === claims.sub);
if (social) return db.users.find((u) => u.id === social.userId); // sudah pernah: cari lewat sub
if (claims.email_verified !== true) return { error: "email belum terverifikasi" };
let user = db.users.find((u) => u.email === claims.email);
if (!user) {
user = { id: db.nextId++, name: claims.name, email: claims.email, role: "student" };
db.users.push(user);
}
db.socialAccounts.push({ userId: user.id, provider: "google", providerId: claims.sub });
return user;
}
- Lewat
subdi tabelsocial_accounts(kolomuser_id,provider,provider_id).subgak pernah berubah untuk satu akun Google, sedangkan email bisa diganti. - Lewat email, hanya kalau
email_verifiedbernilaitrue. Email yang belum diverifikasi belum terbukti milik orang itu, jadi gak boleh menyambungkan ke akun siapa pun. - Belum ada sama sekali: buat akun baru dari data Google (nama, email), dengan role paling rendah, lalu sambungkan ke
sub-nya.
Hasilnya di Lab:
| id_token | Dicari lewat | Hasil |
|---|---|---|
| Siti, pertama kali | sub belum ada, email terverifikasi cocok dengan user 1 |
baris baru di social_accounts, masuk sebagai Siti |
| Siti, kedua kali | sub ketemu |
masuk sebagai Siti, email gak dilihat |
| Nadia, murid baru | sub dan email gak ketemu |
user 5 dibuat (role student) lalu disambungkan |
[email protected], email_verified: false |
sub belum ada, email belum terverifikasi |
gak disambungkan ke akun Pak Andi |
Jadi untuk Siti, jawabannya menyambungkan ke akun lama, karena email-nya sama dan terverifikasi. Itu jawaban tebakannya. Nadia dapat id 5, bukan 4, karena id 4 dulu milik Rizky dan id auto-increment gak dipakai ulang.
Di Laravel: Socialite
Laravel Socialite mengerjakan alur OAuth dan OIDC-nya. Untuk Google, scope default-nya sudah openid, profile, email.
// routes/api.php
Route::get('/auth/google/redirect', fn () => Socialite::driver('google')->stateless()->redirect());
Route::get('/auth/google/callback', function () {
$google = Socialite::driver('google')->stateless()->user();
$user = SocialAccount::firstWhere(['provider' => 'google', 'provider_id' => $google->getId()])?->user;
if (! $user) {
abort_unless(($google->getRaw()['email_verified'] ?? false) === true, 403, 'Email Google belum terverifikasi.');
$user = User::firstOrCreate(['email' => $google->getEmail()], ['name' => $google->getName(), 'role' => 'student']);
$user->socialAccounts()->create(['provider' => 'google', 'provider_id' => $google->getId()]);
}
return AuthResponse::for($user); // access token + cookie refresh token Portal sendiri
});
Socialite::driver('google')memilih provider Google, sesuai konfigurasi diconfig/services.php(client_id, client_secret, redirect).->user()menukar code jadi token, lalu mengambil profil dari endpoint userinfo Google.getId()adalahsub, dangetRaw()berisi semua klaim, termasukemail_verified.->stateless()membuat Socialite gak menyimpan dan gak mencocokkanstatedi session. Dipakai kalau callback-nya mendarat di API tanpa session, seperti API BelajarWeb.dev sendiri, dan aplikasinya harus menjaga alur itu dengan cara lain. Kalau route-mu punya session (grupweb), jangan pakaistateless(): biarkan Socialite mengecekstate, dan->enablePKCE()juga bisa dipakai karena verifier-nya disimpan di session.- Kalau client mengirim
id_token-nya langsung,->userFromToken($idToken)memeriksa tanda tangan dengan kunci Google,iss,aud, danexp. AuthResponse::for($user)adalah helper buatan aplikasi ini: membuat access token dan refresh token milik Portal, seperti lesson Token Refresh Flow. Token Google gak dipakai untuk API Portal.
⚠️ Jebakan yang sering ketemu
- Menyambungkan lewat email tanpa cek
email_verified. Akun orang lain bisa tersambung ke akun Google yang belum terbukti memiliki email itu. - Mencari user lewat email di setiap login. Begitu Siti ganti email, dia dapat akun baru yang kosong. Cari lewat
subdulu. - Cuma decode
id_token, tanpa memeriksanya.atobmembaca, gak memverifikasi. Periksa tanda tangan,iss,aud, danexp, atau biarkan library yang mengerjakannya. - Menaruh token aplikasi di URL redirect (
/dashboard?token=...). URL masuk history dan log. Kirim lewat cookieHttpOnlyatau body JSON.
TL;DR: Login dengan Google = OAuth 2.0 (otorisasi) + OpenID Connect (identitas): scope openid email profile, dan /token menjawab dengan id_token. Periksa berurutan: tanda tangan (kunci publik Google), iss, aud (harus client_id-mu), exp (detik), sub. Lalu cari akun lewat sub di social_accounts, sambungkan lewat email hanya kalau email_verified true, dan buat akun baru kalau belum ada. Di Laravel: Socialite::driver('google')->stateless()->user(), lalu buat token aplikasimu sendiri.