Ringkasan

OAuth 2.0 adalah protokol delegasi akses yang memungkinkan aplikasi mendapatkan akses terbatas ke resource milik user tanpa mengekspos credential. OpenID Connect (OIDC) adalah lapisan identitas di atas OAuth 2.0 untuk autentikasi. Catatan ini membahas grant types, token exchange, JWT validation, implementation flaws umum, serta perbandingan OAuth vs SAML vs OIDC.

Domain Terkait: api-security-deep-dive (fondasi) → identity-and-access-management (IAM) → web-security (konteks web) → active-directory-windows-security-deepdive (enterprise IdP) → zero-trust-security (framework keamanan)


Daftar Isi


1. OAuth 2.0 — Arsitektur & Roles

OAuth 2.0 mendefinisikan 4 roles (RFC 6749):

┌─────────────┐        ┌──────────────┐
│  Resource   │        │   Resource   │
│  Owner      │◄──────▶│   Server     │
│  (User)     │        │  (API)       │
└──────┬──────┘        └──────┬───────┘
       │                      │
       │   Authorization      │
       │      Grant           │
       ▼                      │
┌──────────────┐             │
│  Client      │─────────────▶│
│  (App)       │  Access Token│
└──────┬───────┘             │
       │                      │
       ▼                      │
┌──────────────┐             │
│ Authorization │             │
│   Server     │◄─────────────┘
│  (IdP)       │
└──────────────┘
RoleDeskripsiContoh
Resource OwnerEntitas yang memiliki resourceUser dengan fotonya di Google Photos
Resource ServerServer yang menyimpan resourceAPI Google Photos
ClientAplikasi yang minta aksesAplikasi edit foto pihak ketiga
Authorization ServerServer yang mengeluarkan tokenGoogle Accounts

2. Grant Types — Flow Lengkap

Authorization Code Grant (PKCE)

Grant type paling aman untuk aplikasi native/web. PKCE (Proof Key for Code Exchange) mencegah authorization code interception.

┌────────┐      ┌────────┐      ┌────────┐
│ Client │      │   IdP  │      │  User  │
└───┬────┘      └───┬────┘      └───┬────┘
    │               │               │
    │ 1. Auth Req   │               │
    │ + code_challenge──────────────▶│
    │               │               │
    │               │   2. Login    │
    │               │◄──────────────┤
    │               │               │
    │ 3. Auth Code  │               │
    │◄──────────────┤               │
    │               │               │
    │ 4. Code +     │               │
    │ code_verifier─▶               │
    │               │               │
    │ 5. Token      │               │
    │◄──────────────┤               │

Flow detail:

# Step 1: Generate code challenge
import hashlib, base64, secrets
 
code_verifier = secrets.token_urlsafe(64)
code_challenge = base64.urlsafe_b64encode(
    hashlib.sha256(code_verifier.encode()).digest()
).rstrip("=")
 
# Step 2: Redirect user ke IdP
auth_url = (
    f"https://idp.example.com/auth?"
    f"response_type=code&client_id=myapp"
    f"&redirect_uri=https://myapp.com/callback"
    f"&code_challenge={code_challenge}"
    f"&code_challenge_method=S256"
)
 
# Step 3: Exchange code untuk token
token_resp = requests.post("https://idp.example.com/token", json={
    "grant_type": "authorization_code",
    "code": received_code,
    "code_verifier": code_verifier,
    "redirect_uri": "https://myapp.com/callback",
    "client_id": "myapp"
})

Client Credentials Grant

Untuk machine-to-machine tanpa user involvement.

curl -X POST https://idp.example.com/token \
  -H "Content-Type: application/json" \
  -d '{
    "grant_type": "client_credentials",
    "client_id": "service-1",
    "client_secret": "secret123"
  }'

Device Code Grant

Untuk device tanpa browser (smart TV, CLI).

User buka URL: https://device-auth.example.com/activate
Masukkan code: ABCD-1234
Device polling: POST /token → pending → ... → success

Grant Type Comparison

Grant TypeUse CaseSecurity LevelRefresh Token
Authorization Code + PKCEWeb/Native appsTertinggi
Client CredentialsService-to-serviceTinggi❌ (self-contained)
Device CodeHeadless devicesSedang
Implicit (deprecated)SPA (legacy)Rendah
Resource Owner PasswordLegacy trust appsRendah

Catatan: Implicit grant sudah deprecated sejak RFC 8252. SPA wajib pakai Authorization Code + PKCE.


3. OpenID Connect — Lapisan Identitas

OIDC menambahkan lapisan autentikasi di atas OAuth 2.0 dengan ID Token (JWT).

OAuth 2.0OpenID Connect
access_token — akses ke APIaccess_token + id_token — identitas user
Scope: read, writeScope: openid, profile, email
No user info formatsub claim sebagai user identifier wajib
Resource Server verifikasi tokenClient verifikasi ID Token sendiri

OIDC Flow

# OIDC Discovery URL
discovery = requests.get("https://idp.example.com/.well-known/openid-configuration").json()
 
# Verifikasi ID Token (JWT)
from jose import jwt
 
jwks = requests.get(discovery["jwks_uri"]).json()
claims = jwt.decode(
    id_token,
    jwks,
    audience="myapp",
    issuer="https://idp.example.com",
    options={"verify_at_hash": True}
)
# claims = {
#   "sub": "user-123",
#   "email": "user@example.com",
#   "name": "John Doe",
#   "iat": 1680000000,
#   "exp": 1680003600
# }

OIDC Scopes & Claims

ScopeClaimsPenggunaan
openidsubWajib, unique user ID
profilename, family_name, pictureProfil user
emailemail, email_verifiedVerifikasi email
addressaddressAlamat terformat
phonephone_numberNomor telepon

4. Token Exchange & Refresh Token Rotation

Token Exchange (RFC 8693)

Menukar satu token dengan token lain (downscoping, delegation):

POST /token HTTP/1.1
Content-Type: application/json

{
  "grant_type": "urn:ietf:params:oauth:grant-type:token-exchange",
  "subject_token": "ACCESS_TOKEN_ASD",
  "subject_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "requested_token_type": "urn:ietf:params:oauth:token-type:access_token",
  "scope": "read_only"
}

Refresh Token Rotation

Setiap kali refresh token digunakan, server mengeluarkan refresh token baru dan mencabut yang lama. Ini mencegah refresh token theft.

┌────────┐           ┌────────┐
│ Client │           │  IdP   │
└───┬────┘           └───┬────┘
    │ POST /token        │
    │ grant_type=refresh │
    │ refresh_token=R1   │
    ├────────────────────▶│
    │                    │
    │ access_token=A2   │
    │ refresh_token=R2  │← R1 revoked
    │◄───────────────────┤

Implementasi di backend:

class TokenService:
    def refresh_token(self, old_refresh: str):
        # Validate old refresh token
        token = self.db.get_refresh_token(old_refresh)
        if not token or token.revoked:
            raise InvalidTokenError()
 
        # Revoke old, issue new
        self.db.revoke_token(old_refresh)
        new_token = self.db.create_refresh_token(user_id=token.user_id)
 
        return {
            "access_token": self.create_access_token(token.user_id),
            "refresh_token": new_token,
            "token_type": "Bearer"
        }

5. JWT Structure & Validation

Struktur

header.payload.signature

header: {"alg": "RS256", "kid": "key-1", "typ": "JWT"}
payload: {"sub": "user-123", "iat": 1680000000, "exp": 1680003600, "aud": "myapp"}
signature: RSA(private_key, header + "." + payload)

Validasi Checklist

CheckMengapaJika Gagal
SignatureVerifikasi integrity tokenTolak
Expiration (exp)Token expiredMinta refresh
Issuer (iss)Hanya trust IdP yang dikenalTolak
Audience (aud)Token untuk app iniTolak
Not Before (nbf)Token belum aktifTolak jika signifikan
Algorithm (alg)Cegah alg confusion attackTolak jika none

Algorithm Confusion Attack

Serangan klasik: attacker ubah alg dari RS256 ke HS256 (symmetric), lalu sign dengan public key yang bisa didapat.

Defense: Wajib whitelist algorithm di validation.

# ❌ Vulnerable
jwt.decode(token, public_key)
 
# ✅ Safe
jwt.decode(token, public_key, algorithms=["RS256"], audience="myapp")

6. Common Implementation Flaws

FlawDampakFix
CSRF di redirect URIAttacker exchange code miliknyaPKCE + state parameter
Token leakage via RefererToken di URL → Referer header ke third-partyAuthorization Code bukan Implicit
Scope escalationClient upgrade scope tanpa otorisasiServer-side scope validation
Refresh token theftAttacker bisa refresh token terusRotation + revocation
Missing audience validationToken untuk service A dipakai di service BWajib cek aud claim
Open redirect abuseredirect_uri wildcard → phishingExact match whitelist redirect URI

Contoh: Redirect URI Bypass

# ❌ Vulnerable — pattern matching longgar
if "example.com" in redirect_uri:
    allow()
 
# ✅ Safe — exact match
ALLOWED_URIS = ["https://myapp.com/callback", "https://myapp.com/alt"]
if redirect_uri not in ALLOWED_URIS:
    deny()

7. OAuth untuk First-Party vs Third-Party Apps

AspekFirst-PartyThird-Party
Trust levelTinggi (app + IdP satu org)Rendah
Client secretBisa disimpan amanPublic client (no secret)
PKCEOpsionalWajib
Token lifetimeBisa lamaPendek
ScopeFullMinimal
Consent screenOpsionalWajib

8. OAuth vs SAML vs OIDC — Comparison

FiturOAuth 2.0SAML 2.0OIDC
PurposeDelegasi aksesSSO enterpriseSSO modern
Token formatJWT (Bearer)XML (SAML Assertion)JWT (ID Token)
TransportJSON/RESTHTTP Redirect/POST/SOAPJSON/REST
Mobile support✅ Excellent❌ Buruk✅ Excellent
Federation❌ Tidak built-in✅ Ya✅ Ya (via OAuth)
Single Logout❌ Tidak✅ Ya (SLO)✅ Ya (OpenID SLO)
Maturity2012 (RFC 6749)20052014
AdoptionAPI, mobile, IoTEnterprise, pemerintahWeb, mobile, startup

Keputusan: OIDC untuk aplikasi baru, SAML hanya jika integrasi dengan legacy enterprise.


9. Tools untuk Testing OAuth

ToolFungsi
oauth2cCLI OAuth client untuk testing flow
jwt.ioDebug JWT token
Burp Suite + OAuth pluginIntercept & manipulate OAuth flow
OAuth 2.0 PlaygroundGoogle’s interactive OAuth debugger
oidc-token-hunterCari token leakage di logs

10. Referensi

  • RFC 6749 — OAuth 2.0 Authorization Framework
  • RFC 6750 — Bearer Token Usage
  • RFC 7519 — JSON Web Token (JWT)
  • RFC 7636 — PKCE
  • RFC 8252 — OAuth for Native Apps
  • RFC 8693 — Token Exchange
  • OpenID Connect Core 1.0

Cross-link vault: