Logo
HOMEABOUTPROJECTSBLOGCONTACT

Luthfi A. Pratama

Enterprise integration specialist building scalable, mission-critical solutions for modern businesses.

LINKS

  • About
  • Projects
  • Blog
  • Contact

CONNECT

GitHubLinkedInEmail

© 2026 Luthfi A. Pratama. All rights reserved.

Designed and built with passion.

Back to all articles
Security

OAuth 2.0 & OpenID Connect: Gimana "Login dengan Google" Itu Bekerja

Catatan pembelajaran tentang OAuth 2.0 dan OpenID Connect - dari kenapa kita butuh authorization framework, Authorization Code Flow step-by-step, PKCE, ID token vs access token, sampai common mistakes di production.

Luthfi A. Pratama

Luthfi A. Pratama

Software Engineer

2026-03-19
15 min read
OAuth 2.0 & OpenID Connect: Gimana "Login dengan Google" Itu Bekerja

Authentication vs Authorization: Bedanya Apa?

Sebelum masuk ke OAuth, penting untuk bedain dua konsep yang sering ketuker:

  • Authentication (autentikasi): "Siapa kamu?" verifikasi identitas. Login pakai username + password itu authentication.
  • Authorization (otorisasi): "Kamu boleh ngapain?" verifikasi hak akses. Setelah login, boleh akses data apa aja itu authorization.

OAuth 2.0 itu authorization framework, bukan authentication. Tapi karena sering dipake bareng OpenID Connect (yang nambah authentication layer), orang sering nyebutnya "login dengan Google" padahal di baliknya ada dua protokol berbeda.

Masalah yang OAuth Selesaikan

Sebelum OAuth, kalau kamu mau app A akses data kamu di Google, solusinya...kasih password Google ke app A.

Coba bayangin konsekuensinya:

  • App A punya akses penuh ke semua yang bisa kamu lakukan di Google
  • Kalau app A di-hack, password Google kamu bocor
  • Kalau mau cabut akses app A, kamu harus ganti password Google dan semua app lain yang pakai password itu ikut kena

Ini jelas buruk. OAuth muncul untuk ngasih cara yang lebih aman: app A bisa dapet akses terbatas ke data kamu di Google, tanpa pernah tau password kamu.

Empat Peran di OAuth 2.0

OAuth punya empat peran yang harus dipahami:

Peran Definisi Contoh
Resource Owner Pemilik data biasanya user Kamu
Client Aplikasi yang minta akses App foto pihak ketiga
Resource Server Server yang nyimpan data yang dilindungi Google Photos API
Authorization Server Server yang issue token setelah user approve Google Auth Server
graph TB
    RO["Resource Owner (User)"]
    C["Client (App pihak ketiga)"]
    AS["Authorization Server (Google)"]
    RS["Resource Server (Google Photos API)"]

    RO -->|"1. Approve akses"| AS
    AS -->|"2. Issue token"| C
    C -->|"3. Akses dengan token"| RS

OAuth 2.0 Grant Types

OAuth punya beberapa "flow" yang disebut grant type. Masing-masing untuk situasi yang berbeda.

Grant Type Untuk Rekomendasi
Authorization Code Web app dengan backend server ✅ Gunakan ini
Authorization Code + PKCE Mobile app / SPA (public client) ✅ Gunakan ini
Client Credentials Machine-to-machine (server ke server) ✅ Gunakan ini
Device Code Smart TV, CLI tools ✅ Kalau perlu
Implicit SPA (deprecated) ❌ Jangan
Resource Owner Password Aplikasi yang percaya penuh ❌ Jangan

Dua yang di-deprecated karena ada security issue. Yang akan kita bahas detail adalah Authorization Code (dengan dan tanpa PKCE) karena yang paling umum.

Authorization Code Flow (Confidential Client)

Ini flow yang dipakai web app yang punya backend server. Disebut "confidential client" karena bisa nyimpan secret secara aman di server.

sequenceDiagram
    participant U as User
    participant C as Client (Web App)
    participant AS as Authorization Server
    participant RS as Resource Server

    Note over U,C: 1. User klik "Login dengan Google"
    C->>AS: Redirect ke /authorize?client_id=...&response_type=code&scope=...&redirect_uri=...&state=xyz

    Note over U,AS: 2. User login dan approve
    AS->>U: Tampilkan login page
    U->>AS: Login + approve scope

    Note over AS,C: 3. Authorization Code dikirim
    AS->>C: Redirect ke redirect_uri?code=AUTH_CODE&state=xyz

    Note over C,AS: 4. Tukar code dengan token (server-to-server)
    C->>AS: POST /token {code, client_id, client_secret, redirect_uri}
    AS-->>C: {access_token, refresh_token, id_token}

    Note over C,RS: 5. Akses resource dengan token
    C->>RS: GET /photos dengan Authorization: Bearer access_token
    RS-->>C: Data

Step by step:

1. Mulai flow

User klik "Login dengan Google". Client redirect user ke Authorization Server dengan parameter:

https://accounts.google.com/o/oauth2/auth?
  client_id=YOUR_CLIENT_ID
  &response_type=code
  &scope=openid%20email%20profile
  &redirect_uri=https://yourapp.com/callback
  &state=random_state_string

Parameter penting:

  • client_id identitas aplikasi kamu, didapat saat register di Google
  • response_type=code minta authorization code
  • scope hak akses apa yang diminta (lebih detail nanti)
  • redirect_uri URL yang Google akan redirect setelah user approve
  • state random string untuk CSRF protection. Wajib. Kalau ga ada, bisa kena CSRF attack

2. User Login dan Approve

Google tampilkan halaman login (kalau belum login) dan consent screen "App X minta izin untuk: lihat email kamu, akses profil kamu."

User setuju atau tolak.

3. Authorization Code Dikirim

Kalau setuju, Google redirect user ke redirect_uri kamu dengan authorization code:

https://yourapp.com/callback?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&state=random_state_string

Authorization code ini masa berlakunya sangat pendek (biasanya 1-10 menit) dan single-use. Ini bukan token, ini cuma tiket untuk minta token.

Cek state dari response harus sama dengan yang dikirim. Kalau beda, kemungkinan CSRF attack.

4. Tukar Code dengan Token

Di backend server (bukan di browser), client POST ke token endpoint dengan code + client secret:

POST https://oauth2.googleapis.com/token
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
&client_id=YOUR_CLIENT_ID
&client_secret=YOUR_CLIENT_SECRET
&redirect_uri=https://yourapp.com/callback
&grant_type=authorization_code

Kenapa harus di backend? Karena client_secret ini rahasia ga boleh terekspos ke browser/client.

Response:

{
  "access_token": "ya29.a0AfH6SMC...",
  "token_type": "Bearer",
  "expires_in": 3600,
  "refresh_token": "1//0g...",
  "scope": "openid email profile",
  "id_token": "eyJhbGciOiJSUzI1NiJ9..."
}

5. Akses Resource

Pakai access_token untuk akses Google Photos API:

GET https://photoslibrary.googleapis.com/v1/albums
Authorization: Bearer ya29.a0AfH6SMC...

Kenapa ada authorization code dulu, bukan langsung token?

Kenapa ga langsung dapet token dari redirect? Kenapa harus dua langkah?

Karena authorization code dikirim lewat browser redirect URL bisa ketahuan dari history, referrer headers, atau browser logs. Kalau token langsung di URL, token bisa bocor.

Dengan code exchange, yang lewat browser cuma code yang short-lived dan single-use. Token yang sebenarnya ditukar di channel yang aman (server-to-server, ga lewat browser).

Authorization Code Flow + PKCE (Public Client)

Mobile app dan SPA (Single Page App) ga bisa nyimpan client_secret dengan aman. Secret di mobile app bisa di-extract dari APK. Secret di browser JavaScript bisa dilihat siapa aja.

Solusinya: PKCE (Proof Key for Code Exchange, dibaca "pixie").

PKCE menggantikan client secret dengan cryptographic proof yang dibuat per-request.

sequenceDiagram
    participant C as Client (Mobile/SPA)
    participant AS as Authorization Server

    Note over C: Generate code_verifier (random string)
    Note over C: code_challenge = BASE64URL(SHA256(code_verifier))

    C->>AS: /authorize?...&code_challenge=xxx&code_challenge_method=S256

    Note over AS: Simpan code_challenge

    AS->>C: Authorization Code

    C->>AS: POST /token {code, code_verifier}
    Note over AS: Verify: SHA256(code_verifier) == code_challenge?
    AS-->>C: Tokens

Caranya:

  1. Client generate code_verifier random string 43-128 karakter
  2. Client hitung code_challenge = BASE64URL(SHA256(code_verifier))
  3. Kirim code_challenge saat request authorization
  4. Authorization server simpan code_challenge
  5. Saat tukar code dengan token, kirim code_verifier (yang original)
  6. Authorization server hitung sendiri SHA256 dari verifier, cocokkan dengan challenge yang tersimpan

Kalau attacker intercept authorization code, mereka ga punya code_verifier dan ga bisa tukar code dengan token.

Sekarang PKCE direkomendasikan untuk semua client, termasuk web app dengan backend.

Client Credentials Flow

Ini untuk machine-to-machine ga ada user yang login, dua service yang saling berkomunikasi.

sequenceDiagram
    participant S as Service A (Client)
    participant AS as Authorization Server
    participant RS as Service B (Resource Server)

    S->>AS: POST /token {client_id, client_secret, grant_type=client_credentials}
    AS-->>S: {access_token}
    S->>RS: Request dengan access_token
    RS-->>S: Response

Contoh: microservice billing yang perlu akses microservice user untuk cek data subscription.

POST /token
Content-Type: application/x-www-form-urlencoded

grant_type=client_credentials
&client_id=billing-service
&client_secret=xxx
&scope=user:read

Ga ada authorization code, ga ada user redirect langsung dapet token.

OpenID Connect (OIDC): OAuth untuk Authentication

OAuth 2.0 itu authorization framework ngasih akses ke resource. Tapi OAuth ga ngasih cara standar untuk tahu siapa user-nya.

OpenID Connect (OIDC) itu layer di atas OAuth 2.0 yang nambah authentication. OIDC standarisasi gimana client bisa dapet informasi tentang user yang sudah login.

graph TB
    OIDC["OpenID Connect (Authentication)"]
    OAuth["OAuth 2.0 (Authorization)"]
    HTTP["HTTP/TLS (Transport)"]

    OIDC --> OAuth --> HTTP

Yang ditambah OIDC:

  • ID Token JWT yang berisi informasi user (siapa yang login)
  • UserInfo Endpoint endpoint standar untuk dapet info user
  • Standard Claims claim standar untuk user info (sub, email, name, dll)
  • Discovery endpoint .well-known/openid-configuration yang describe semua endpoint

ID Token vs Access Token

Ini sering bikin bingung:

Access Token ID Token
Untuk siapa Resource Server (API) Client (App kamu)
Isinya Hak akses (scopes) Informasi user
Format Bisa JWT atau opaque Selalu JWT
Dipakai untuk Akses API Tahu siapa yang login
Dikirim ke API? Ya Tidak

ID Token itu buat app kamu, bukan buat dikirim ke API. ID Token ngasih tau siapa yang sudah login. Access Token yang dikirim ke API untuk akses resource.

Claims di ID Token

ID Token itu JWT. Kalau di-decode, payload-nya berisi:

{
  "iss": "https://accounts.google.com",
  "sub": "110169484474386276334",
  "aud": "812741506391.apps.googleusercontent.com",
  "iat": 1353601026,
  "exp": 1353604926,
  "email": "janedoe@gmail.com",
  "email_verified": true,
  "name": "Jane Doe",
  "picture": "https://lh4.googleusercontent.com/...",
  "given_name": "Jane",
  "family_name": "Doe"
}

Standard claims:

  • sub Subject. Unique identifier user di Authorization Server. Ini yang dipake sebagai user ID, bukan email. Email bisa ganti, sub tidak.
  • iss Issuer. Siapa yang issue token
  • aud Audience. Untuk siapa token ini (client_id kamu)
  • iat Issued at
  • exp Expiry
  • email, name, picture user info (kalau scope-nya diminta)

UserInfo Endpoint

Alternatif dari ID Token, bisa juga call UserInfo endpoint dengan access token:

GET https://openidconnect.googleapis.com/v1/userinfo
Authorization: Bearer access_token

Response:

{
  "sub": "110169484474386276334",
  "email": "janedoe@gmail.com",
  "name": "Jane Doe"
}

Scopes: Minta Hak Akses yang Tepat

Scope itu hak akses spesifik yang diminta client. Prinsipnya: minta sesedikit mungkin yang dibutuhkan (principle of least privilege).

Contoh scope Google:

openid         → Aktifkan OIDC, dapet ID token
email          → Baca email address
profile        → Baca nama, foto, dll
https://www.googleapis.com/auth/gmail.readonly  → Baca email di Gmail
https://www.googleapis.com/auth/calendar        → Akses Google Calendar

Kalau app cuma perlu email user untuk register, minta openid email ga perlu gmail.readonly atau akses yang lebih luas.

User akan lihat consent screen dengan daftar scope yang kamu minta. Semakin banyak akses yang diminta, semakin kecil kemungkinan user setuju.

Refresh Token

Access token punya masa berlaku pendek (biasanya 1 jam). Setelah expired, ada dua pilihan:

  1. Suruh user login ulang buruk untuk UX
  2. Pakai refresh token untuk minta access token baru secara silent
sequenceDiagram
    participant C as Client
    participant AS as Authorization Server

    Note over C: Access token expired
    C->>AS: POST /token {grant_type=refresh_token, refresh_token=xxx, client_id, client_secret}
    AS-->>C: {access_token baru, refresh_token baru (opsional)}
    Note over C: Simpan token baru

Refresh token biasanya:

  • Masa berlaku jauh lebih panjang (hari/bulan)
  • Bisa di-revoke
  • Harus disimpan dengan aman jangan di localStorage browser

Kalau refresh token bocor, attacker bisa terus minta access token baru sampai refresh token di-revoke.

Token Storage: Jangan Salah Tempat Nyimpan

Token Web App (Backend) SPA (Browser) Mobile App
Access Token Memory / server session Memory (JS variable) Secure storage
Refresh Token HttpOnly cookie / server session ❌ Jangan di localStorage Keychain / Keystore
ID Token Server session Memory (kalau perlu) Secure storage

Jangan pernah simpan refresh token di localStorage. XSS bisa baca localStorage → refresh token bocor → attacker bisa impersonate user selamanya.

HttpOnly cookie yang di-set server itu aman dari XSS karena JavaScript ga bisa baca HttpOnly cookie.

OAuth di Microservice

Di microservice, ada pattern umum: API Gateway verify token, service di belakangnya trust gateway.

graph LR
    Client -->|"Bearer token"| GW["API Gateway"]
    GW -->|"Verify token"| AS["Authorization Server"]
    AS -->|"Valid + claims"| GW
    GW -->|"X-User-Id, X-User-Role"| SA["Service A"]
    GW -->|"X-User-Id, X-User-Role"| SB["Service B"]

Gateway verify token sekali, extract claims (user ID, roles, scopes), terus forward ke service sebagai trusted header.

Service di belakang ga perlu verify token lagi mereka trust header dari gateway (dan gateway ada di internal network, ga bisa diakses langsung).

Common Mistakes

1. Ga validasi state parameter

# ❌ Bahaya
@app.route('/callback')
def callback():
    code = request.args.get('code')
    # langsung tukar code, ga cek state

# ✅ Benar
@app.route('/callback')
def callback():
    state = request.args.get('state')
    if state != session.pop('oauth_state', None):
        abort(403)  # CSRF!
    code = request.args.get('code')

Tanpa validasi state, app kamu rentan CSRF. Attacker bisa trick user untuk menghubungkan akun mereka ke akun attacker.

2. Simpan refresh token di localStorage

// ❌ Jangan
localStorage.setItem('refresh_token', token);

// ✅ Serahkan ke backend, simpan di HttpOnly cookie
// Frontend ga perlu pegang refresh token

3. Ga cek aud di ID Token

# ❌ Bahaya - ga cek audience
decoded = jwt.decode(id_token, key, algorithms=['RS256'])

# ✅ Selalu cek audience
decoded = jwt.decode(
    id_token, key,
    algorithms=['RS256'],
    audience=YOUR_CLIENT_ID  # Pastikan token memang untuk app kamu
)

Kalau ga cek aud, token yang diissue untuk app lain bisa dipake ke app kamu.

4. Pakai email sebagai user identifier

# ❌ Email bisa ganti
user = db.find_by_email(id_token['email'])

# ✅ Pakai kombinasi iss + sub (stable dan unique)
user = db.find_by_provider(
    provider=id_token['iss'],
    provider_user_id=id_token['sub']
)

User bisa ganti email di Google. sub adalah identifier yang permanent dan unik per user per provider.

5. Redirect URI yang terlalu permissive

# ❌ Terlalu lebar
redirect_uri=https://yourapp.com/

# ✅ Exact match
redirect_uri=https://yourapp.com/auth/callback

Authorization server harus validasi exact match redirect URI. Kalau terlalu permissive, attacker bisa arahkan authorization code ke domain mereka.

6. Tidak pakai PKCE

# ❌ Tanpa PKCE (untuk SPA/mobile)
url = f"{AUTH_URL}?client_id={CLIENT_ID}&response_type=code&..."

# ✅ Dengan PKCE
import secrets, hashlib, base64

code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b'=').decode()

url = f"{AUTH_URL}?...&code_challenge={code_challenge}&code_challenge_method=S256"

OIDC Discovery

OIDC punya standar discovery endpoint di:

https://accounts.google.com/.well-known/openid-configuration

Endpoint ini return JSON yang describe semua endpoint (authorization, token, userinfo, jwks_uri), algoritma yang didukung, dan capabilities lainnya.

Library OAuth/OIDC biasanya auto-discover dari sini, jadi kamu ga perlu hardcode URL.

Ringkasan

Konsep Penjelasan
OAuth 2.0 Framework untuk authorization delegasi akses ke resource
OpenID Connect Layer authentication di atas OAuth 2.0
Authorization Code Flow Flow utama untuk web app dengan backend
PKCE Extension untuk public client (mobile, SPA)
Client Credentials Flow untuk machine-to-machine
Access Token Token untuk akses API/resource
ID Token JWT berisi informasi user (OIDC)
Refresh Token Token untuk minta access token baru
Scope Hak akses spesifik yang diminta
State CSRF protection parameter

Kesimpulan

"Login dengan Google" yang kelihatan simpel itu sebenernya ada banyak yang terjadi di baliknya.

Yang perlu diingat:

  1. OAuth ≠ Authentication. OAuth itu authorization. OpenID Connect yang nambah authentication
  2. Gunakan Authorization Code + PKCE aman untuk semua jenis client, ga ada alasan pakai Implicit
  3. ID Token untuk app kamu, Access Token untuk API jangan tukar-tukar
  4. Pakai sub sebagai user identifier, bukan email
  5. Refresh token itu sensitif simpan di tempat yang aman, jangan di localStorage
  6. Validasi state selalu, tanpa pengecualian
  7. Minta scope seminimal mungkin prinsip least privilege

OAuth dan OIDC itu kompleks, tapi library modern (Passport.js, spring-security-oauth2, authlib) handle banyak detail ini. Yang penting paham konsepnya supaya tahu cara konfigurasinya dengan benar dan bisa debug kalau ada yang salah.

Topics

SecuritySoftware Engineering

Related Articles

Caching: Kenapa Sistem Cepat Itu Sistem yang Males Ngitung Ulang

Catatan pembelajaran tentang caching - dari kenapa cache itu penting, strategi caching, Redis vs Memcached, cache invalidation, CDN, sampai pitfalls yang sering kejadian di production.

Clustering: Gimana Banyak Server Kerja Bareng Jadi Satu Sistem

Catatan pembelajaran tentang konsep clustering - dari kenapa satu server ga cukup, gimana cluster milih leader, sampai consensus algorithm dan real-world implementations.

DNS: Gimana Komputer Tahu Jalan ke google.com

Catatan pembelajaran tentang DNS - dari kenapa kita butuh DNS, gimana proses resolusi domain bekerja, tipe record, caching, sampai DNS di production dan serangan yang sering terjadi.