GitHub Login Entegrasyonu: OAuth 2.0 ile Kimlik Doğrulama Rehberi
Bir web uygulaması geliştiriyorsunuz ve kullanıcı kayıt/giriş sistemi kurmak zorundasınız. Kendi authentication sisteminizi sıfırdan yazmak hem zaman alıcı hem de güvenlik açısından riskli. İşte tam burada OAuth 2.0 devreye giriyor. GitHub Login entegrasyonu, özellikle geliştirici odaklı uygulamalar için hem pratik hem de kullanıcı dostu bir çözüm sunuyor. Bu yazıda OAuth 2.0’ın ne olduğunu, nasıl çalıştığını ve GitHub OAuth entegrasyonunu production ortamında nasıl güvenli şekilde hayata geçireceğinizi adım adım ele alacağız.
OAuth 2.0 Nedir ve Neden Önemlidir
OAuth 2.0, bir uygulamanın kullanıcı adına başka bir servise sınırlı erişim almasını sağlayan açık bir yetkilendirme protokolüdür. Burada kritik bir ayrımı hemen yapalım: OAuth 2.0 bir authentication (kimlik doğrulama) protokolü değil, bir authorization (yetkilendirme) protokolüdür. Ancak OpenID Connect gibi üst katmanlarla birlikte authentication amacıyla da yaygın şekilde kullanılır.
Klasik senaryoda kullanıcı sizin uygulamanıza GitHub şifresini vermek yerine, GitHub’ın kendi login sayfasından giriş yapıyor ve sizin uygulamanıza belirli izinler veriyor. Bu sayede:
- Kullanıcı şifresi asla sizin sunucunuzdan geçmiyor
- Kullanıcı verdiği izinleri istediği zaman iptal edebiliyor
- Siz sadece ihtiyacınız olan verilere erişiyorsunuz
- Güvenlik ihlali yaşanırsa etki alanı sınırlı kalıyor
GitHub OAuth App Oluşturma
İlk adım GitHub tarafında bir OAuth Application kaydetmektir. GitHub’a giriş yapın ve şu adımları takip edin:
Settings > Developer settings > OAuth Apps > New OAuth App
Burada dolduracağınız alanlar:
- Application name: Uygulamanızın adı (kullanıcılar bunu görecek)
- Homepage URL: Uygulamanızın ana adresi, örneğin
https://myapp.example.com - Authorization callback URL: GitHub login sonrası yönlendirme adresi, örneğin
https://myapp.example.com/auth/github/callback
Uygulamayı oluşturduktan sonra size bir Client ID ve Client Secret verilecek. Client Secret’ı bir daha göremeyeceğiniz için hemen güvenli bir yere kaydedin. Bu değerleri asla Git reponuza commit etmeyin, environment variable olarak yönetin.
# .env dosyası oluşturun
cat > .env << 'EOF'
GITHUB_CLIENT_ID=your_client_id_here
GITHUB_CLIENT_SECRET=your_client_secret_here
SESSION_SECRET=super_secret_session_key_here
APP_BASE_URL=https://myapp.example.com
EOF
# .env dosyasını .gitignore'a ekleyin
echo ".env" >> .gitignore
echo "*.env" >> .gitignore
OAuth 2.0 Akışı: Authorization Code Flow
GitHub OAuth için kullanılan akış Authorization Code Flow‘dur. Bu akış üç temel adımdan oluşur:
Adım 1 – Authorization Request: Kullanıcıyı GitHub’ın yetkilendirme sayfasına yönlendirirsiniz.
Adım 2 – Authorization Grant: Kullanıcı GitHub’da izin verince, GitHub sizin callback URL’inize bir code parametresiyle yönlendirme yapar.
Adım 3 – Token Exchange: Aldığınız bu geçici kodu GitHub API’sine göndererek gerçek access_token ile değiştirirsiniz.
Bu akışı daha iyi anlamak için her adımı kod örnekleriyle inceleyelim.
Node.js ile GitHub OAuth Implementasyonu
Express.js kullanarak sıfırdan bir GitHub OAuth implementasyonu yapalım.
# Proje kurulumu
mkdir github-oauth-demo && cd github-oauth-demo
npm init -y
npm install express axios express-session dotenv crypto
npm install --save-dev nodemon
Önce ana uygulama dosyamızı oluşturalım:
# app.js
cat > app.js << 'EOF'
require('dotenv').config();
const express = require('express');
const session = require('express-session');
const crypto = require('crypto');
const axios = require('axios');
const app = express();
// Session middleware
app.use(session({
secret: process.env.SESSION_SECRET,
resave: false,
saveUninitialized: false,
cookie: {
secure: process.env.NODE_ENV === 'production',
httpOnly: true,
maxAge: 24 * 60 * 60 * 1000 // 24 saat
}
}));
// GitHub OAuth parametreleri
const GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize';
const GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token';
const GITHUB_API_URL = 'https://api.github.com';
// Adım 1: GitHub login başlat
app.get('/auth/github', (req, res) => {
// CSRF koruması için state parametresi oluştur
const state = crypto.randomBytes(16).toString('hex');
req.session.oauthState = state;
const params = new URLSearchParams({
client_id: process.env.GITHUB_CLIENT_ID,
redirect_uri: `${process.env.APP_BASE_URL}/auth/github/callback`,
scope: 'user:email read:user',
state: state,
allow_signup: 'true'
});
res.redirect(`${GITHUB_AUTH_URL}?${params.toString()}`);
});
// Adım 2 & 3: GitHub callback ve token exchange
app.get('/auth/github/callback', async (req, res) => {
const { code, state, error } = req.query;
// Hata durumu
if (error) {
return res.redirect('/?error=oauth_denied');
}
// State doğrulama (CSRF koruması)
if (!state || state !== req.session.oauthState) {
return res.status(403).json({ error: 'State mismatch - possible CSRF attack' });
}
// State'i temizle
delete req.session.oauthState;
try {
// Access token al
const tokenResponse = await axios.post(GITHUB_TOKEN_URL, {
client_id: process.env.GITHUB_CLIENT_ID,
client_secret: process.env.GITHUB_CLIENT_SECRET,
code: code,
redirect_uri: `${process.env.APP_BASE_URL}/auth/github/callback`
}, {
headers: { Accept: 'application/json' }
});
const { access_token, token_type, scope } = tokenResponse.data;
if (!access_token) {
throw new Error('Access token alınamadı');
}
// GitHub kullanıcı bilgilerini çek
const userResponse = await axios.get(`${GITHUB_API_URL}/user`, {
headers: {
Authorization: `Bearer ${access_token}`,
Accept: 'application/vnd.github.v3+json'
}
});
const githubUser = userResponse.data;
// Session'a kullanıcı bilgilerini kaydet
req.session.user = {
id: githubUser.id,
login: githubUser.login,
name: githubUser.name,
avatar_url: githubUser.avatar_url,
email: githubUser.email
};
// Access token'ı session'da sakla (dikkatli kullanın)
req.session.accessToken = access_token;
res.redirect('/dashboard');
} catch (err) {
console.error('OAuth hatası:', err.message);
res.redirect('/?error=oauth_failed');
}
});
// Korumalı route örneği
app.get('/dashboard', requireAuth, async (req, res) => {
res.json({
message: 'Hoş geldiniz!',
user: req.session.user
});
});
// Logout
app.get('/auth/logout', (req, res) => {
req.session.destroy();
res.redirect('/');
});
// Auth middleware
function requireAuth(req, res, next) {
if (!req.session.user) {
return res.redirect('/auth/github');
}
next();
}
app.listen(3000, () => {
console.log('Server 3000 portunda çalışıyor');
});
EOF
Email Adresini Ayrıca Çekme
GitHub kullanıcıları email adreslerini gizleyebilir. Bu durumda /user endpoint’i email döndürmez. user:email scope’unu verip ayrıca /user/emails endpoint’ini kullanmanız gerekir:
# Email çekme fonksiyonu
cat > helpers/getGithubEmail.js << 'EOF'
const axios = require('axios');
async function getGithubPrimaryEmail(accessToken) {
try {
const response = await axios.get('https://api.github.com/user/emails', {
headers: {
Authorization: `Bearer ${accessToken}`,
Accept: 'application/vnd.github.v3+json'
}
});
// Primary ve verified email'i bul
const primaryEmail = response.data.find(
email => email.primary && email.verified
);
if (primaryEmail) {
return primaryEmail.email;
}
// Primary bulunamazsa ilk verified email'i döndür
const verifiedEmail = response.data.find(email => email.verified);
return verifiedEmail ? verifiedEmail.email : null;
} catch (error) {
console.error('Email çekilemedi:', error.message);
return null;
}
}
module.exports = { getGithubPrimaryEmail };
EOF
Python/Flask ile GitHub OAuth
Node.js ekosistemi dışında çalışıyorsanız, işte Python/Flask implementasyonu:
# Gerekli paketleri kur
pip install flask requests python-dotenv
# Flask uygulaması
cat > app.py << 'EOF'
import os
import secrets
import requests
from flask import Flask, redirect, request, session, jsonify, url_for
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
app.secret_key = os.getenv('SESSION_SECRET')
GITHUB_CLIENT_ID = os.getenv('GITHUB_CLIENT_ID')
GITHUB_CLIENT_SECRET = os.getenv('GITHUB_CLIENT_SECRET')
GITHUB_AUTH_URL = 'https://github.com/login/oauth/authorize'
GITHUB_TOKEN_URL = 'https://github.com/login/oauth/access_token'
GITHUB_API_URL = 'https://api.github.com'
@app.route('/auth/github')
def github_login():
state = secrets.token_hex(16)
session['oauth_state'] = state
params = {
'client_id': GITHUB_CLIENT_ID,
'redirect_uri': url_for('github_callback', _external=True),
'scope': 'user:email read:user',
'state': state
}
auth_url = f"{GITHUB_AUTH_URL}?{'&'.join(f'{k}={v}' for k, v in params.items())}"
return redirect(auth_url)
@app.route('/auth/github/callback')
def github_callback():
error = request.args.get('error')
if error:
return redirect('/?error=oauth_denied')
code = request.args.get('code')
state = request.args.get('state')
# State doğrulama
if not state or state != session.get('oauth_state'):
return jsonify({'error': 'State mismatch'}), 403
session.pop('oauth_state', None)
# Token exchange
token_response = requests.post(GITHUB_TOKEN_URL,
json={
'client_id': GITHUB_CLIENT_ID,
'client_secret': GITHUB_CLIENT_SECRET,
'code': code
},
headers={'Accept': 'application/json'}
)
token_data = token_response.json()
access_token = token_data.get('access_token')
if not access_token:
return redirect('/?error=oauth_failed')
# Kullanıcı bilgilerini çek
user_response = requests.get(f'{GITHUB_API_URL}/user',
headers={
'Authorization': f'Bearer {access_token}',
'Accept': 'application/vnd.github.v3+json'
}
)
github_user = user_response.json()
session['user'] = {
'id': github_user['id'],
'login': github_user['login'],
'name': github_user.get('name'),
'avatar_url': github_user.get('avatar_url')
}
return redirect('/dashboard')
@app.route('/dashboard')
def dashboard():
if 'user' not in session:
return redirect('/auth/github')
return jsonify({'user': session['user']})
@app.route('/auth/logout')
def logout():
session.clear()
return redirect('/')
if __name__ == '__main__':
app.run(debug=False, port=3000)
EOF
Nginx ile Reverse Proxy Konfigürasyonu
Production ortamında uygulamanızın önüne Nginx koymanız gerekecek. GitHub OAuth callback URL’iniz HTTPS olmalı:
# /etc/nginx/sites-available/myapp.conf
cat > /etc/nginx/sites-available/myapp.conf << 'EOF'
server {
listen 80;
server_name myapp.example.com;
return 301 https://$server_name$request_uri;
}
server {
listen 443 ssl http2;
server_name myapp.example.com;
ssl_certificate /etc/letsencrypt/live/myapp.example.com/fullchain.pem;
ssl_certificate_key /etc/letsencrypt/live/myapp.example.com/privkey.pem;
ssl_protocols TLSv1.2 TLSv1.3;
ssl_ciphers HIGH:!aNULL:!MD5;
# OAuth callback için özel header
proxy_set_header X-Forwarded-Proto $scheme;
proxy_set_header X-Real-IP $remote_addr;
proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for;
location / {
proxy_pass http://127.0.0.1:3000;
proxy_http_version 1.1;
proxy_set_header Upgrade $http_upgrade;
proxy_set_header Connection 'upgrade';
proxy_set_header Host $host;
proxy_cache_bypass $http_upgrade;
# Session cookie güvenliği için
proxy_cookie_flags ~ secure httponly;
}
# Rate limiting - OAuth endpoint koruma
location /auth/ {
limit_req zone=auth_limit burst=10 nodelay;
proxy_pass http://127.0.0.1:3000;
proxy_set_header Host $host;
proxy_set_header X-Forwarded-Proto $scheme;
}
}
# Rate limiting zone tanımı (nginx.conf içinde http bloğuna ekleyin)
# limit_req_zone $binary_remote_addr zone=auth_limit:10m rate=5r/m;
EOF
# Konfigürasyonu aktif et ve test et
ln -s /etc/nginx/sites-available/myapp.conf /etc/nginx/sites-enabled/
nginx -t && systemctl reload nginx
Environment Variable Yönetimi ve Güvenlik
Production ortamında secret’ları doğru yönetmek kritik önemdedir. Birkaç pratik yaklaşım:
# Systemd service için environment variables
cat > /etc/systemd/system/myapp.service << 'EOF'
[Unit]
Description=MyApp OAuth Service
After=network.target
[Service]
Type=simple
User=appuser
WorkingDirectory=/opt/myapp
EnvironmentFile=/etc/myapp/env
ExecStart=/usr/bin/node app.js
Restart=always
RestartSec=10
# Güvenlik kısıtlamaları
NoNewPrivileges=true
ProtectSystem=strict
ProtectHome=true
ReadWritePaths=/opt/myapp
[Install]
WantedBy=multi-user.target
EOF
# Environment dosyası oluştur (izinlere dikkat)
mkdir -p /etc/myapp
cat > /etc/myapp/env << 'EOF'
NODE_ENV=production
GITHUB_CLIENT_ID=your_client_id
GITHUB_CLIENT_SECRET=your_client_secret
SESSION_SECRET=your_very_long_random_secret
APP_BASE_URL=https://myapp.example.com
EOF
# Dosyayı sadece root okuyabilsin
chmod 600 /etc/myapp/env
chown root:root /etc/myapp/env
# Servisi başlat
systemctl daemon-reload
systemctl enable myapp
systemctl start myapp
Access Token Güvenliği ve Token Revocation
GitHub’dan aldığınız access token’ı kullanıcı çıkış yaptığında iptal etmek iyi bir pratiktir:
# Token revocation implementasyonu
cat > helpers/revokeToken.js << 'EOF'
const axios = require('axios');
async function revokeGithubToken(accessToken) {
const clientId = process.env.GITHUB_CLIENT_ID;
const clientSecret = process.env.GITHUB_CLIENT_SECRET;
try {
// GitHub token revocation API
await axios.delete(
`https://api.github.com/applications/${clientId}/token`,
{
auth: {
username: clientId,
password: clientSecret
},
data: { access_token: accessToken },
headers: {
Accept: 'application/vnd.github.v3+json'
}
}
);
console.log('Token başarıyla iptal edildi');
return true;
} catch (error) {
// 404 = token zaten geçersiz, bu durumu handle et
if (error.response?.status === 404) {
console.log('Token zaten geçersizdi');
return true;
}
console.error('Token iptal edilemedi:', error.message);
return false;
}
}
module.exports = { revokeGithubToken };
EOF
Yaygın Sorunlar ve Çözümleri
State mismatch hatası: Genellikle session konfigürasyon sorunlarından kaynaklanır. Load balancer arkasında çalışıyorsanız sticky sessions kullanın ya da Redis gibi merkezi bir session store’a geçin.
- Redirect URI mismatch: GitHub’a kaydettiğiniz callback URL ile uygulamanızdaki URL birebir aynı olmalı, trailing slash dahil
- Bad credentials hatası: Client Secret’ın doğru environment variable’dan okunduğunu kontrol edin, boşluk veya newline karakter sızmamış olsun
- Rate limit aşımı: GitHub API’si IP başına saatte 60 unauthenticated, 5000 authenticated istek izin verir. Bunu aşıyorsanız caching ekleyin
- HTTPS zorunluluğu: Production’da GitHub OAuth yalnızca HTTPS callback URL’lerini kabul eder. Local development için
http://localhostistisnası vardır - Scope yetkileri: İstediğiniz veriyi alamıyorsanız scope’larınızı kontrol edin.
read:userveuser:emailçoğu temel senaryo için yeterlidir
Local Development Ortamı
Development sırasında HTTPS zorunluluğunu aşmak için GitHub http://localhost adresine izin verir:
# Local development için ayrı .env.local
cat > .env.local << 'EOF'
NODE_ENV=development
GITHUB_CLIENT_ID=dev_client_id_here
GITHUB_CLIENT_SECRET=dev_client_secret_here
SESSION_SECRET=dev_session_secret
APP_BASE_URL=http://localhost:3000
EOF
# GitHub Developer Settings'de development için ayrı bir OAuth App oluşturun
# Callback URL: http://localhost:3000/auth/github/callback
# Böylece production ve development credential'larını birbirinden ayırmış olursunuz
# Uygulamayı development modunda başlat
NODE_ENV=development node -r dotenv/config app.js --env=.env.local
Production ve development için ayrı GitHub OAuth App’ları oluşturmak en sağlıklı yaklaşımdır. Bu sayede development’taki bir hata production credential’larını etkilemez.
Sonuç
GitHub OAuth entegrasyonu, kendi authentication sisteminizi sıfırdan yazmaktan çok daha güvenli ve hızlı bir alternatif sunar. Ancak “kolay kurulum” basit görünse de production’da dikkat etmeniz gereken birkaç kritik nokta var.
State parametresi CSRF saldırılarına karşı zorunlu bir korumadır, asla atlamayın. Client Secret server side’da kalmalı, frontend koduna, GitHub’a veya loglara asla sızmamalı. HTTPS production’da pazarlık konusu değil. Token revocation kullanıcı deneyimi ve güvenlik açısından logout akışının parçası olmalı.
Geliştirici hedef kitlesine sahip uygulamalarda GitHub OAuth’un kullanıcı benimsemesi oldukça yüksek. Kullanıcılar ayrı bir hesap oluşturmak yerine zaten bildikleri GitHub hesabıyla giriş yapabilmeyi tercih ediyor. Bu da kayıt oranlarını artırıp kullanıcı yönetim yükünüzü ciddi ölçüde azaltıyor.
Son olarak, bu entegrasyonu microservice mimarisinde kullanacaksanız, token doğrulamayı bir API Gateway veya middleware katmanına taşıyın. Her servisin ayrı ayrı GitHub API’sine istek atması hem performans hem de rate limit açısından sorun yaratır. Merkezi bir auth servisi bu problemi temiz şekilde çözer.
