Ulaşım
- Adres:Batıkent Mh. 8910 Sk. 6. Etap 1H No: 18 Yeni Toki Eyyübiye / Şanlıurfa (Yeni Alım Satım Karşısı)
- Telefon:0 (545) 528 88 93
- eMail: info@alestaweb.com
2023'te yayınlanan OAuth 2.1, OAuth 2.0'ın bilinen güvenlik açıklarını kapatan yeni standarttır. OAuth 2.0 vs OAuth 2.1 karşılaştırmasında en kritik farklar: PKCE zorunluluğu, implicit flow kaldırılması, refresh token rotation. Alesta Web olarak bu değişikliklerin production sistemlerinize etkisini analiz ediyoruz.
OAuth 2.1 (RFC 9700), OAuth 2.0'ın evolution'ı olup, 10+ yıllık production deneyiminden öğrenilen güvenlik iyileştirmelerini standartlaştırır:
| Özellik | OAuth 2.0 (2012) | OAuth 2.1 (2023) |
|---|---|---|
| PKCE | Optional (mobile için önerilen) | ✅ Zorunlu (tüm client'lar) |
| Implicit Flow | Destekleniyor (SPA için) | ❌ Kaldırıldı (güvenli değil) |
| Refresh Token Rotation | Optional | ✅ Zorunlu (önerilen) |
| Redirect URI | Substring match kabul | ✅ Exact match zorunlu |
| Authorization Code Lifetime | 10 dakika (önerilen) | ✅ Max 60 saniye |
| Client Secret | Public client'ta optional | ✅ PKCE ile değiştirildi |
2026 itibariyle major OAuth provider'ların OAuth 2.1 uyumluluğu:
OAuth 2.1 en önemli değişiklik: PKCE (Proof Key for Code Exchange) tüm client'lar için zorunlu:
PKCE, authorization code interception saldırılarını önler:
// ❌ OAuth 2.0: PKCE olmadan (güvenlik açığı)
// 1. Client authorization code alır
const authUrl = `https://oauth.example.com/authorize?
response_type=code&
client_id=my-app&
redirect_uri=https://myapp.com/callback`
// 2. User consent verir, code döner
// https://myapp.com/callback?code=AUTH_CODE_123
// 3. Token exchange (saldırıya açık!)
fetch('https://oauth.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: 'AUTH_CODE_123', // ❌ Çalınabilir (MITM, malicious app)
client_id: 'my-app',
redirect_uri: 'https://myapp.com/callback'
})
})
// Saldırgan AUTH_CODE_123'ü çalarsa kendi adına token alabilir!
// PKCE implementation
import crypto from 'crypto';
// 1. Code verifier ve challenge oluştur
function generateCodeVerifier() {
return crypto.randomBytes(32).toString('base64url'); // 43 karakter
}
function generateCodeChallenge(verifier) {
return crypto.createHash('sha256')
.update(verifier)
.digest('base64url');
}
const codeVerifier = generateCodeVerifier(); // Client'ta sakla
const codeChallenge = generateCodeChallenge(codeVerifier);
// 2. Authorization request (code_challenge gönder)
const authUrl = `https://oauth.example.com/authorize?
response_type=code&
client_id=my-app&
redirect_uri=https://myapp.com/callback&
code_challenge=${codeChallenge}&
code_challenge_method=S256` // SHA-256
// 3. User consent, code döner
// https://myapp.com/callback?code=AUTH_CODE_123
// 4. Token exchange (code_verifier gönder)
fetch('https://oauth.example.com/token', {
method: 'POST',
body: new URLSearchParams({
grant_type: 'authorization_code',
code: 'AUTH_CODE_123',
client_id: 'my-app',
redirect_uri: 'https://myapp.com/callback',
code_verifier: codeVerifier // ✅ Server SHA-256 ile doğrular
})
})
// Saldırgan code'u çalsa bile code_verifier olmadan token alamaz! ✅
OAuth 2.0 vs OAuth 2.1 en büyük fark: Implicit flow artık yok:
// Implicit flow - OAuth 2.0 (SPA için kullanılıyordu)
const authUrl = `https://oauth.example.com/authorize?
response_type=token& // ❌ Direkt token fragment'te döner
client_id=my-spa-app&
redirect_uri=https://myapp.com/callback`
// User consent sonrası:
// https://myapp.com/callback#access_token=TOKEN_ABC&token_type=Bearer&expires_in=3600
// JavaScript ile token al
const token = new URLSearchParams(window.location.hash.substring(1)).get('access_token');
// ❌ Güvenlik sorunları:
// 1. Token URL fragment'te → Browser history'de kalır
// 2. Token JavaScript erişilebilir → XSS saldırısı riski
// 3. Refresh token yok → Long-lived access token (güvenli değil)
// 4. PKCE kullanılamaz
// Modern SPA authentication (OAuth 2.1)
// 1. PKCE ile authorization code flow
const codeVerifier = generateCodeVerifier();
const codeChallenge = generateCodeChallenge(codeVerifier);
const authUrl = `https://oauth.example.com/authorize?
response_type=code& // ✅ Code döner (token değil)
client_id=my-spa-app&
redirect_uri=https://myapp.com/callback&
code_challenge=${codeChallenge}&
code_challenge_method=S256`
// 2. Callback'te code al
const code = new URLSearchParams(window.location.search).get('code');
// 3. Token exchange (backend'siz, PKCE ile)
const tokenResponse = await fetch('https://oauth.example.com/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
code: code,
client_id: 'my-spa-app',
redirect_uri: 'https://myapp.com/callback',
code_verifier: codeVerifier // ✅ PKCE
})
});
const { access_token, refresh_token } = await tokenResponse.json();
// ✅ Token fragment yerine POST body'de → Browser history'de yok
// ✅ Refresh token var → Short-lived access token kullanılabilir
// ✅ PKCE koruması
Alesta Web müşterilerinde implicit flow kullanan SPA'lar için:
OAuth 2.1, refresh token rotation'ı zorunlu kılar (önerir):
// ❌ OAuth 2.0: Refresh token tekrar kullanılabilir (risky)
// İlk token exchange
POST /token
{
"grant_type": "authorization_code",
"code": "...",
...
}
Response:
{
"access_token": "ACCESS_1",
"refresh_token": "REFRESH_STATIC", // ❌ Aynı token her zaman
"expires_in": 3600
}
// Access token expire olunca refresh
POST /token
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_STATIC" // ❌ Aynı token tekrar kullanılıyor
}
Response:
{
"access_token": "ACCESS_2",
"refresh_token": "REFRESH_STATIC", // ❌ Yine aynı
"expires_in": 3600
}
// Problem: Refresh token çalınırsa saldırgan sonsuza kadar access token alabilir!
// İlk token exchange
POST /token
{
"grant_type": "authorization_code",
"code": "...",
...
}
Response:
{
"access_token": "ACCESS_1",
"refresh_token": "REFRESH_1", // ✅ Benzersiz token
"expires_in": 3600
}
// Access token expire olunca refresh
POST /token
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_1"
}
Response:
{
"access_token": "ACCESS_2",
"refresh_token": "REFRESH_2", // ✅ Yeni refresh token döner
"expires_in": 3600
}
// REFRESH_1 artık geçersiz! ✅
// Eğer REFRESH_1 tekrar kullanılırsa (saldırı!)
POST /token
{
"grant_type": "refresh_token",
"refresh_token": "REFRESH_1" // ❌ Eski token
}
Response:
{
"error": "invalid_grant",
"error_description": "Refresh token reuse detected"
}
// Server TÜM refresh token'ları invalidate eder (breach detection) ✅
Alesta Web production sistemlerinde gözlemlenen iyileşmeler:
OAuth 2.1, redirect URI'da exact match zorunlu kılar:
// OAuth provider config (OAuth 2.0)
const registeredRedirectURIs = [
'https://myapp.com/callback' // Registered URI
];
// Authorization request
const authUrl = `https://oauth.example.com/authorize?
...
redirect_uri=https://myapp.com/callback?evil=attacker.com` // ❌ Substring match
// OAuth 2.0 bazı provider'lar şunu kabul eder:
// https://myapp.com/callback?evil=attacker.com
// Çünkü "https://myapp.com/callback" ile başlıyor
// Saldırgan code'u kendi domain'ine redirect edebilir!
// OAuth provider config (OAuth 2.1)
const registeredRedirectURIs = [
'https://myapp.com/callback',
'https://myapp.com/callback?source=mobile' // Exact URI gerekirse ekle
];
// Authorization request
const authUrl = `https://oauth.example.com/authorize?
...
redirect_uri=https://myapp.com/callback` // ✅ Exact match
// ❌ Bu REJECT edilir:
// redirect_uri=https://myapp.com/callback?evil=attacker.com
// Error: redirect_uri mismatch
// ✅ Sadece kayıtlı URI'lar kabul edilir
OAuth 2.1 exceptions:
http://localhost:[any_port]/callback kabul edilir (development)myapp://callback mobile app için kabul edilirhttps://*.myapp.com/callback güvenli değil)Alesta Web migration checklist:
// Tüm authorization request'lere code_challenge ekle
// Tüm token exchange'lere code_verifier ekle
// response_type=token → response_type=code
// SPA'larda authorization code + PKCE kullan
// Token refresh sonrası eski refresh_token'ı invalidate et
// Yeni refresh_token döndür
// Exact match validation ekle
// Substring match'i kaldır
// 10 dakika → 60 saniye
// npm install openid-client
import { Issuer, generators } from 'openid-client';
// 1. OAuth provider discovery
const auth0Issuer = await Issuer.discover('https://YOUR_DOMAIN.auth0.com');
const client = new auth0Issuer.Client({
client_id: 'YOUR_CLIENT_ID',
redirect_uris: ['https://myapp.com/callback'],
response_types: ['code'], // ✅ Authorization code only
token_endpoint_auth_method: 'none' // ✅ PKCE, client secret yok
});
// 2. PKCE code verifier/challenge
const codeVerifier = generators.codeVerifier();
const codeChallenge = generators.codeChallenge(codeVerifier);
// 3. Authorization URL
const authUrl = client.authorizationUrl({
scope: 'openid profile email',
code_challenge: codeChallenge,
code_challenge_method: 'S256' // ✅ PKCE
});
// 4. Callback handling
const params = client.callbackParams(req);
const tokenSet = await client.callback(
'https://myapp.com/callback',
params,
{ code_verifier: codeVerifier } // ✅ PKCE
);
// 5. Refresh token rotation (automatic with openid-client)
const refreshed = await client.refresh(tokenSet.refresh_token);
// refreshed.refresh_token → Yeni token ✅
Alesta Web production recommendations:
E-commerce platform (100K+ users):
| Feature | OAuth 2.0 | OAuth 2.1 | Impact |
|---|---|---|---|
| PKCE | Optional | Mandatory ✅ | High |
| Implicit Flow | Supported | Removed ❌ | High |
| Refresh Token Rotation | Optional | Recommended ✅ | Medium |
| Redirect URI Match | Substring OK | Exact Match ✅ | Medium |
| Bearer Token | Supported | Unchanged | - |
| Client Credentials | Supported | Unchanged | - |
OAuth 2.1, OAuth 2.0'ın güvenlik açıklarını kapatan evrimdir. En kritik değişiklikler: PKCE zorunlu (authorization code interception önlenir), implicit flow kaldırıldı (XSS riski azaltıldı), refresh token rotation (token theft detection). Alesta Web olarak tüm yeni projelerde OAuth 2.1 kullanmanızı öneriyoruz.
Migration Checklist:
Faydalı Kaynaklar:
© 2026 Alesta Web - OAuth ve API security uzmanınız. Tüm hakları saklıdır.