04.3 — Masaüstü Uygulamaları (Electron, .NET, C++, Go)
Kimin için: Electron, WPF, Windows Forms, .NET MAUI, Qt veya Go (Wails) ile masaüstü yazılım geliştirenler.
Ön koşul: RFC 8252 OAuth 2.0 for Native Apps prensipleri.
Tahmini süre: 20 dakika.
Ne Öğreneceksiniz
- Masaüstü uygulamalarında neden gömülü WebView yerine sistem tarayıcısı kullanılması gerektiğini,
- Yerel Loopback HTTP dinleyicisi (
http://127.0.0.1:port/callback) ile geri dönüş yakalamayı, - Electron ve .NET masaüstü istemcileri için örnek kod mimarisini.
Mimarisi: Neden Sistem Tarayıcısı (System Browser)?
RFC 8252 standardına göre yerel masaüstü uygulamaları gömülü web tarayıcısı (embedded WebView) kullanmamalıdır.
- Güvenlik: Kullanıcı parolasını veya şifre yöneticilerini (1Password, Bitwarden vb.) gömülü pencerede kullanamaz; uygulama kullanıcının tuş vuruşlarını dinleyebilir.
- 2FA / Passkey: Sistem tarayıcısı WebAuthn, FIDO2 güvenlik anahtarları ve biyometrik doğrulamayı tam destekler.
- SSO Avantajı: Kullanıcı sistem tarayıcısında TALOST ID'ye zaten giriş yapmışsa, masaüstü uygulamasına tek tıkla şifre girmeden giriş yapabilir.
Akış Adımları
- Uygulama yerel makinede boş bir portta geçici bir HTTP sunucusu açar (örn:
http://127.0.0.1:42813/callback). - PKCE
verifiervechallengeüretilir. - Sistem varsayılan tarayıcısında
TALOST_ISSUER/oauth/authorizesayfası açılır. - Kullanıcı tarayıcıda giriş yapar; TALOST ID tarayıcıyı
http://127.0.0.1:42813/callback?code=...adresine yönlendirir. - Yerel HTTP sunucusu yetkilendirme kodunu (
code) yakalar, kullanıcıya tarayıcıda "Giriş başarılı, pencereyi kapatabilirsiniz" HTML yanıtı döner. - Uygulama kodu
/oauth/tokenile takas edip HTTP dinleyicisini kapatır.
Kod Örneği: Electron (Main Process)
javascript
// main.js
const { app, shell } = require('electron');
const http = require('http');
const crypto = require('crypto');
function base64Url(buf) {
return buf.toString('base64').replace(/\+/g, '-').replace(/\//g, '_').replace(/=+$/, '');
}
async function startDesktopAuth() {
const verifier = base64Url(crypto.randomBytes(32));
const challenge = base64Url(crypto.createHash('sha256').update(verifier).digest());
const state = base64Url(crypto.randomBytes(16));
// 1. Dinleyici Sunucu Aç (Port: 42813)
const PORT = 42813;
const redirectUri = `http://127.0.0.1:${PORT}/callback`;
const server = http.createServer(async (req, res) => {
const reqUrl = new URL(req.url, `http://127.0.0.1:${PORT}`);
if (reqUrl.pathname === '/callback') {
const code = reqUrl.searchParams.get('code');
const returnedState = reqUrl.searchParams.get('state');
if (returnedState !== state || !code) {
res.writeHead(400, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h3>Giriş doğrulaması başarısız. Lütfen tekrar deneyin.</h3>');
server.close();
return;
}
// 2. Token Takası
try {
const tokenRes = await fetch('http://127.0.0.1:3000/oauth/token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'authorization_code',
client_id: 'cli_dev_xxxxxxxxxxxx',
code,
redirect_uri: redirectUri,
code_verifier: verifier,
}),
});
const tokens = await tokenRes.json();
res.writeHead(200, { 'Content-Type': 'text/html; charset=utf-8' });
res.end('<h3>TALOST ID Girişi Başarılı! Bu sekmeyi kapatıp uygulamanıza dönebilirsiniz.</h3>');
console.log('Giriş başarılı, tokenlar alındı:', tokens);
// Tokenları işletim sisteminin güvenli kasasında (örn: keytar / safeStorage) saklayın
} catch (err) {
res.writeHead(500, { 'Content-Type': 'text/html' });
res.end('<h3>Sunucu hatası.</h3>');
} finally {
server.close();
}
}
});
server.listen(PORT, '127.0.0.1', () => {
// 3. Varsayılan Tarayıcıyı Aç
const authUrl = `http://127.0.0.1:3000/oauth/authorize?response_type=code&client_id=cli_dev_xxxxxxxxxxxx&redirect_uri=${encodeURIComponent(redirectUri)}&scope=openid%20profile%20email%20offline_access&state=${state}&code_challenge=${challenge}&code_challenge_method=S256`;
shell.openExternal(authUrl);
});
}
Sık Yapılan Hatalar
- Gömülü BrowserWindow İçinde Giriş Sayfası Açmak: Güvenlik ve çerez paylaşımı politikası gereği engellenmelidir. Daima
shell.openExternalveya işletim sistemi çağrısı ile harici tarayıcı kullanılmalıdır. - Port Çakışması: Eğer sabit bir loopback portu kullanıyorsanız, portun meşgul olma ihtimaline karşı portu dinamik (
server.listen(0)) seçebilir veya portalde birkaç yedek port tanımlayabilirsiniz.
Güvenlik Notları
- Masaüstü uygulamaları dağıtıldığında kullanıcı makinesinde çalıştığı için Public Client olarak yapılandırılmalıdır (gizli
client_secretderlenmiş ikili kodlara gömülmemelidir). - Alınan
access_tokenverefresh_tokendeğerleri düz metin dosyası yerine Electron'unsafeStorageAPI'si veya Windows Credential Manager / macOS Keychain kullanılarak şifreli saklanmalıdır.
İlgili Sayfalar
Son doğrulama: 2026-10-04 · Kaynak: _envanter.md, src/lib/apps/redirect-uri.ts
Son doğrulama: 2026-10-04 · Kaynak: `_envanter.md`, `src/lib/apps/redirect-uri.ts`