N'oluyo · Geliştirici Dokümanları · Son güncelleme: · https://noluyo.net/gelistirici

N'oluyo ile giriş

N'oluyo ile giriş (OAuth2), bir kişinin kendi hesabıyla senin web sitene, masaüstü programına ya da mobil uygulamana açık rızasıyla giriş yapmasını sağlar. Şifresini seninle paylaşmaz; N'oluyo ekranında hangi bilgilerin paylaşılacağını görür, onaylar ve senin uygulamana tek kullanımlık bir kodla döner.

N'oluyo ile giriş

N'oluyo ile giriş (OAuth2), bir kişinin kendi hesabıyla senin web sitene, masaüstü programına ya da mobil uygulamana açık rızasıyla giriş yapmasını sağlar. Şifresini seninle paylaşmaz; N'oluyo ekranında hangi bilgilerin paylaşılacağını görür, onaylar ve senin uygulamana tek kullanımlık bir kodla döner.

Bu nedir ve ne zaman gerekir

Geliştirici portalındaki tek bir uygulama hem bot hem de N'oluyo ile giriş barındırabilir; ama ikisi tamamen farklı işler yapar:

  • Bir sunucuya bot eklemek istiyorsan bu sayfaya ihtiyacın yok. Bot ekleme akışı /bot-ekle/<uygulama_id> ekranından yürür; ayrıntısı Bot uygulamaları rehberinde.
  • Kendi sitene veya uygulamana kullanıcı girişi koymak istiyorsan doğru yer burasıdır. Bot kullanıcısı oluşturman bile gerekmez; yalnız OAuth2 ayarlarını açman yeterlidir.

Kişinin gördüğü izin ekranı

Kullanıcıyı https://noluyo.net/oauth2/authorize adresine yönlendirdiğinde karşısına N'oluyo'nun izin ekranı çıkar. Bu ekranda:

  • Uygulamanın adı, avatarı, doğrulanmış rozeti (doğrulanmamışsa açık uyarı etiketi), gizlilik politikası (privacy_url) ve kullanım koşulları (terms_url) bağlantıları yer alır.
  • İstenen her kapsam (identify, email, guilds) sade cümlelerle listelenir.
  • Ekranın altında "Uygulama şunları asla göremez" kutusu sabit olarak gösterilir: şifren, iki aşamalı doğrulama kodların, özel mesajların, sunucu mesajların, sesli sohbetlerin ve ödeme bilgilerin paylaşılmaz; uygulama senin adına mesaj atamaz veya sunucuya katılamaz.

Misafir (kayıtsız) hesaplar OAuth2 izni veremez (403 guest_not_allowed). Doğrulanmamış bir uygulamaya en çok 100 farklı kullanıcı giriş yapabilir; 101. kişi 403 oauth_user_cap uyarısı alır. Sınırı kaldırmak için Doğrulama başvurusu yapılır.

Portal ayarları ve yönlendirme adresleri

/gelistirici/uygulamalar sayfasında uygulamanı açıp N'oluyo ile Giriş (OAuth2) bölümünü yapılandır:

  1. Yönlendirme adresleri (redirect_uris): En çok 10 adres eklenebilir (her biri en çok 512 karakter). Yetkilendirme isteğindeki redirect_uri parametresi buradaki kayıtlardan biriyle birebir eşleşmelidir. Joker karakter (*), #fragment, kullanici@ bilgisi veya noluyo.net / *.noluyo.net alan adları kabul edilmez. İzin verilen üç biçim vardır:
  • Web siteleri: https:// ile başlayan tam adres (https://ornek.site/giris/geri-donus).
  • Yerel geliştirme ve masaüstü geri dönüşü: http://localhost, http://127.0.0.1 veya http://[::1]. Port numarası çalışma anında esnektir: portala http://127.0.0.1/geri-donus kaydettiğinde çalışma anında http://127.0.0.1:5173/geri-donus veya rastgele seçilmiş yerel bir port kabul edilir.
  • Mobil ve masaüstü özel şema: Nokta içeren ters alan adı (com.ornek.uygulama:/oauth/geri-donus; net.noluyo ile başlayamaz).
  1. İstemci türü (oauth_public):
  • Gizli istemci (oauth_public: false, varsayılan): Kendi arka uç sunucusu olan web siteleri içindir. Portaldan Secret oluştur / yenile düğmesiyle (POST /api/v1/applications/{application_id}/oauth-secret) bir client_secret üret ve sunucundaki .env dosyasında sakla. Secret yalnızca üretildiği anda tek sefer gösterilir.
  • Açık istemci (oauth_public: true): Arka ucu olmayan tek sayfa uygulamalar (SPA), masaüstü uygulamaları ve mobil uygulamalar içindir. Kodu kullanıcının cihazında çalıştığı için client_secret kullanılmaz; güvenliği PKCE ve tam yönlendirme adresi sağlar.
  1. Yasal bağlantılar: İzin ekranında görünmesi için privacy_url ve terms_url alanlarına https:// ile başlayan gizlilik politikası ve kullanım koşulları adreslerini ekle.

Yetkilendirme adresi ve parametreler

Kullanıcıyı tarayıcıda şu adrese yönlendirirsin:

https://noluyo.net/oauth2/authorize?client_id=123456789012345678&redirect_uri=https%3A%2F%2Fornek.site%2Fgiris%2Fgeri-donus&response_type=code&scope=identify+email+guilds&state=RastgeleDurumDegeri&code_challenge=PKCEOzetDegeri&code_challenge_method=S256

Her parametrenin tam karşılığı:

ParametreZorunluAçıklama
client_idevetUygulamanın snowflake kimliği (application.id)
redirect_urievetPortalda kayıtlı yönlendirme adreslerinden biri (URL kodlu)
response_typeevetHer zaman code olmalıdır
scopeevetBoşlukla ayrılmış kapsam listesi; identify zorunludur (identify, identify email, identify email guilds)
stateevetOturumuna bağladığın rastgele tahmin edilemez metin (en az 16 bayt rastgele veri önerilir)
code_challengeevetcode_verifier değerinin SHA-256 özeti (base64url, = dolgusuz)
code_challenge_methodevetHer zaman S256 olmalıdır; plain reddedilir

PKCE ve state ne işe yarar

Hem gizli hem de açık istemcilerde PKCE (S256) ve state zorunludur:

  • code_verifier ve code_challenge (PKCE): Kullanıcıyı yönlendirmeden hemen önce 43–128 karakter uzunluğunda rastgele bir metin (code_verifier) üretip kullanıcının oturumunda saklarsın. Bunun SHA-256 özetinin base64url karşılığını (code_challenge) yetkilendirme adresine koyarsın. Kullanıcı kodla döndüğünde asıl code_verifier değerini /api/v1/oauth2/token ucuna gönderirsin. Yolda kodu ele geçiren biri code_verifier değerini bilmediği için token alamaz.
  • state: Başka birinin kendi yetkilendirme kodunu senin kullanıcının tarayıcısına enjekte etmesini (CSRF) engeller. Yönlendirmeden önce ürettiğin rastgele state değerini HTTP-only çerezde ya da sunucu oturumunda sakla; geri dönüşte gelen state birebir eşleşmiyorsa işlemi hemen reddet.

Beş adımda giriş akışı

Tam çalışan web sitesi örneklerini depodaki sdks/examples/js/oauth-site ve sdks/examples/python/oauth_site klasörlerinde bulabilirsin.

1. PKCE değerlerini üret ve kullanıcıyı yönlendir

# PKCE ve state üretimi (OpenSSL ile örnek):
VERIFIER=$(openssl rand -base64 32 | tr '+/' '-_' | tr -d '=')
CHALLENGE=$(printf '%s' "$VERIFIER" | openssl dgst -sha256 -binary | openssl base64 -A | tr '+/' '-_' | tr -d '=')
STATE=$(openssl rand -hex 16)

echo "Oturuma kaydet -> verifier=$VERIFIER state=$STATE"
echo "Tarayıcıda aç -> https://noluyo.net/oauth2/authorize?client_id=$CLIENT_ID&redirect_uri=http%3A%2F%2F127.0.0.1%3A3000%2Fcb&response_type=code&scope=identify+email&state=$STATE&code_challenge=$CHALLENGE&code_challenge_method=S256"
import { createHash, randomBytes } from "node:crypto";

const codeVerifier = randomBytes(32).toString("base64url");
const codeChallenge = createHash("sha256").update(codeVerifier).digest("base64url");
const state = randomBytes(16).toString("hex");

// codeVerifier ve state değerlerini kullanıcının sunucu oturumuna kaydet.
const url = new URL("https://noluyo.net/oauth2/authorize");
url.searchParams.set("client_id", process.env.CLIENT_ID);
url.searchParams.set("redirect_uri", "http://127.0.0.1:3000/cb");
url.searchParams.set("response_type", "code");
url.searchParams.set("scope", "identify email");
url.searchParams.set("state", state);
url.searchParams.set("code_challenge", codeChallenge);
url.searchParams.set("code_challenge_method", "S256");
import base64, hashlib, os, secrets
from urllib.parse import urlencode

code_verifier = secrets.token_urlsafe(32)
digest = hashlib.sha256(code_verifier.encode("ascii")).digest()
code_challenge = base64.urlsafe_b64encode(digest).rstrip(b"=").decode("ascii")
state = secrets.token_hex(16)

# code_verifier ve state değerlerini kullanıcının sunucu oturumuna kaydet.
params = urlencode({
    "client_id": os.environ["CLIENT_ID"],
    "redirect_uri": "http://127.0.0.1:3000/cb",
    "response_type": "code",
    "scope": "identify email",
    "state": state,
    "code_challenge": code_challenge,
    "code_challenge_method": "S256",
})
authorize_url = f"https://noluyo.net/oauth2/authorize?{params}"

2. Geri dönüşü karşıla ve state kontrolü yap

Kullanıcı İzin ver düğmesine bastığında tarayıcı redirect_uri?code=...&state=... adresine, Vazgeç düğmesine bastığında ise redirect_uri?error=access_denied&state=... adresine döner. Yetkilendirme kodu 2 dakika geçerlidir ve tek kullanımlıktır.

# Tarayıcının döndüğü adres sorgusundan gelen değerler:
# ?code=nly_ac_...&state=...
# Önce gelen state değerinin 1. adımdaki $STATE ile birebir aynı olduğunu doğrula.
import { timingSafeEqual } from "node:crypto";

function checkCallback(query, session) {
  const got = Buffer.from(String(query.state ?? ""));
  const expected = Buffer.from(String(session.state ?? ""));
  if (got.length === 0 || got.length !== expected.length || !timingSafeEqual(got, expected)) {
    throw new Error("Geçersiz state");
  }
  if (query.error) {
    throw new Error(`Kullanıcı vazgeçti: ${query.error}`);
  }
  return String(query.code);
}
import hmac

def check_callback(query: dict[str, str], session: dict[str, str]) -> str:
    got = query.get("state", "")
    expected = session.get("state", "")
    if not got or not hmac.compare_digest(got, expected):
        raise ValueError("Geçersiz state")
    if "error" in query:
        raise ValueError(f"Kullanıcı vazgeçti: {query['error']}")
    return query["code"]

3. Kodu access token ile değiştir

POST https://noluyo.net/api/v1/oauth2/token ucuna application/x-www-form-urlencoded gövde gönderilir (en çok 8 KiB; JSON gövde gönderirsen 400 invalid_request alırsın). Gizli istemciler client_secret alanını form içinde ya da HTTP Basic başlığında iletir; açık istemciler (oauth_public: true) client_secret göndermez. Hız sınırı client_id ve IP başına dakikada 20 istektir.

curl -X POST "https://noluyo.net/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=authorization_code" \
  --data-urlencode "code=$CODE" \
  --data-urlencode "redirect_uri=http://127.0.0.1:3000/cb" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "client_secret=$CLIENT_SECRET" \
  --data-urlencode "code_verifier=$VERIFIER"
const body = new URLSearchParams({
  grant_type: "authorization_code",
  code,
  redirect_uri: "http://127.0.0.1:3000/cb",
  client_id: process.env.CLIENT_ID,
  client_secret: process.env.CLIENT_SECRET, // Açık istemcide bu satırı çıkar
  code_verifier: session.codeVerifier,
});

const tokenRes = await fetch("https://noluyo.net/api/v1/oauth2/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body,
});
const tokens = await tokenRes.json();
if (!tokenRes.ok) throw new Error(`${tokens.error}: ${tokens.error_description}`);
// tokens: { access_token, token_type: "Bearer", expires_in: 3600, refresh_token, scope }
import os, aiohttp

async with aiohttp.ClientSession() as http:
    async with http.post(
        "https://noluyo.net/api/v1/oauth2/token",
        data={
            "grant_type": "authorization_code",
            "code": code,
            "redirect_uri": "http://127.0.0.1:3000/cb",
            "client_id": os.environ["CLIENT_ID"],
            "client_secret": os.environ["CLIENT_SECRET"],  # Açık istemcide çıkar
            "code_verifier": session["code_verifier"],
        },
    ) as resp:
        tokens = await resp.json()
        if resp.status != 200:
            raise RuntimeError(f"{tokens['error']}: {tokens.get('error_description')}")

4. Kullanıcı profilini ve sunucu listesini oku

access_token 1 saat (expires_in: 3600) geçerlidir ve Authorization: Bearer <access_token> başlığıyla kullanılır (Bot önekiyle değil!). Okuma uçlarının hız sınırı 5 saniyede 30 istektir (oauth_read).

curl "https://noluyo.net/api/v1/oauth2/@me" \
  -H "Authorization: Bearer $ACCESS_TOKEN"

# "guilds" kapsamı istendiyse kullanıcının üye olduğu sunucular:
curl "https://noluyo.net/api/v1/oauth2/@me/guilds" \
  -H "Authorization: Bearer $ACCESS_TOKEN"
const meRes = await fetch("https://noluyo.net/api/v1/oauth2/@me", {
  headers: { Authorization: `Bearer ${tokens.access_token}` },
});
const me = await meRes.json();
if (!meRes.ok) throw new Error(me.code);
// me.user.id -> kendi veritabanındaki tekil anahtar!
async with aiohttp.ClientSession() as http:
    async with http.get(
        "https://noluyo.net/api/v1/oauth2/@me",
        headers={"Authorization": f"Bearer {tokens['access_token']}"},
    ) as resp:
        me = await resp.json()
        if resp.status != 200:
            raise RuntimeError(me["code"])
        user_id = me["user"]["id"]

5. Token yenile (rotasyon) ve çıkışta iptal et

refresh_token 30 gün geçerlidir ve her yenilemede döner (rotate olur): yanıt sana yeni bir access_token ve yeni bir refresh_token verir; hemen ikisini birden veritabanında güncelle. Eşzamanlı ağ isteklerinin birbirini kırmaması için eski refresh_token 30 saniyelik bir geçiş penceresi boyunca geçerli kalır; 30 saniye geçtikten sonra eski bir refresh_token tekrar gönderilirse çalıntı şüphesiyle o iznin tüm açık token'ları anında iptal edilir ve 400 invalid_grant döner.

Kullanıcı senin sitenden çıkış yaptığında ya da hesabını ayırdığında POST /api/v1/oauth2/revoke çağrısıyla token'ı iptal et (her zaman 200 döner):

# Yenileme:
curl -X POST "https://noluyo.net/api/v1/oauth2/token" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "grant_type=refresh_token" \
  --data-urlencode "refresh_token=$REFRESH_TOKEN" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "client_secret=$CLIENT_SECRET"

# İptal etme:
curl -X POST "https://noluyo.net/api/v1/oauth2/revoke" \
  -H "Content-Type: application/x-www-form-urlencoded" \
  --data-urlencode "token=$REFRESH_TOKEN" \
  --data-urlencode "client_id=$CLIENT_ID" \
  --data-urlencode "client_secret=$CLIENT_SECRET"
// Yenileme:
const refreshed = await fetch("https://noluyo.net/api/v1/oauth2/token", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    grant_type: "refresh_token",
    refresh_token: storedRefreshToken,
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
  }),
}).then((r) => r.json());

// Çıkışta iptal:
await fetch("https://noluyo.net/api/v1/oauth2/revoke", {
  method: "POST",
  headers: { "Content-Type": "application/x-www-form-urlencoded" },
  body: new URLSearchParams({
    token: refreshed.refresh_token,
    client_id: process.env.CLIENT_ID,
    client_secret: process.env.CLIENT_SECRET,
  }),
});
async with aiohttp.ClientSession() as http:
    # Yenileme:
    async with http.post(
        "https://noluyo.net/api/v1/oauth2/token",
        data={
            "grant_type": "refresh_token",
            "refresh_token": stored_refresh_token,
            "client_id": os.environ["CLIENT_ID"],
            "client_secret": os.environ["CLIENT_SECRET"],
        },
    ) as resp:
        refreshed = await resp.json()

    # Çıkışta iptal:
    await http.post(
        "https://noluyo.net/api/v1/oauth2/revoke",
        data={
            "token": refreshed["refresh_token"],
            "client_id": os.environ["CLIENT_ID"],
            "client_secret": os.environ["CLIENT_SECRET"],
        },
    )

Kapsamlar ve dönen alanlar

N'oluyo kasıtlı olarak yalnızca üç salt-okunur kapsam sunar; mesaj okuma, mesaj yazma ya da sunucuya sokma kapsamı yoktur:

KapsamZorunluDöndürdüğü uç ve alanlar
identifyevetGET /api/v1/oauth2/@me içinde application: {id, name}, scopes, expires_at ve user: {id, username, display_name, avatar}
emailhayırGET /api/v1/oauth2/@me içindeki user nesnesine email (string veya null) ve email_verified (boolean) ekler
guildshayırGET /api/v1/oauth2/@me/guilds ucunu açar; her sunucu için {id, name, icon, owner, permissions} dizisi döndürür (permissions dizgi olarak izin bit maskesidir)

Açık istemciler ve CORS

Tarayıcıda çalışan tek sayfa uygulamalar (SPA), Electron/Tauri masaüstü programları ve mobil uygulamalar kaynak kodunda gizli anahtar saklayamaz. Bu uygulamalarda:

  1. Portalda Açık istemci (oauth_public) seçeneğini aç ve istemci koduna hiçbir zaman client_secret gömme.
  2. /api/v1/oauth2/token, /api/v1/oauth2/revoke, /api/v1/oauth2/@me ve /api/v1/oauth2/@me/guilds uçları Access-Control-Allow-Origin: * (çerezsiz CORS) başlığıyla tarayıcıdan doğrudan çağrılabilir.
  3. Tarayıcı uygulamasında access_token değerini localStorage yerine bellek içinde tut; masaüstü ve mobilde işletim sisteminin güvenli anahtar zincirini (Keychain / Keystore / Credential Locker) kullan.

Hata tablosu

/api/v1/oauth2/token ve /revoke uçları RFC 6749 biçiminde {"error": "...", "error_description": "..."} döndürür; diğer tüm uçlar standart {"code": "...", "message": "..."} zarfını kullanır:

DurumKod / errorNeden ve çözüm
400invalid_requestForm kodlaması (application/x-www-form-urlencoded) kullanılmadı, PKCE S256 eksik ya da zorunlu alan boş.
401invalid_clientclient_id veya client_secret yanlış, ya da gizli istemcide secret gönderilmedi.
400invalid_grantYetkilendirme kodunun 2 dakikalık süresi doldu, kod ikinci kez kullanıldı, redirect_uri veya code_verifier eşleşmedi ya da 30 saniyeyi geçmiş eski bir refresh_token tekrar gönderildi. Kullanıcıdan yeniden giriş iste.
400unsupported_grant_typegrant_type alanı authorization_code veya refresh_token dışında bir değer.
400invalid_scopeİstenen kapsam tanınmıyor veya identify eklenmemiş.
400oauth_unknown_clientYetkilendirme ekranındaki client_id bulunamadı veya uygulama durdurulmuş.
400oauth_bad_redirectredirect_uri portalda kayıtlı adreslerle eşleşmiyor. Portaldaki listeyi kontrol et.
400oauth_bad_requestresponse_type=code, state, code_challenge veya code_challenge_method=S256 eksik/geçersiz.
403oauth_user_capDoğrulanmamış uygulamanın 100 kullanıcı sınırı doldu. Doğrulama başvurusu yap.
403guest_not_allowedMisafir hesapla giriş denendi; kullanıcının kalıcı hesap açması gerekir.
401invalid_oauth_tokenaccess_token süresi doldu, iptal edildi ya da kullanıcı şifresini değiştirdi. refresh_token ile yenile; o da reddedilirse yeniden giriş ekranına yönlendir.
403insufficient_scope/oauth2/@me/guilds çağrıldı ama izin alınırken guilds kapsamı istenmemişti.

Oturum iptali, şifre değişimi ve hesap silme

Token yaşam döngüsü kullanıcının hesap güvenliği hareketlerini anında izler:

  • Kullanıcı erişimi kaldırdığında: Kullanıcı Ayarlar → Bağlı uygulamalar ekranından uygulamanı kaldırdığında tüm access_token ve refresh_token kayıtları anında silinir; bir sonraki @me çağrısı 401 invalid_oauth_token döndürür.
  • Şifre değişimi veya sıfırlama: Kullanıcı şifresini değiştirdiğinde ya da sıfırladığında açık access_token ve refresh_token değerleri iptal olur (uygulama Bağlı uygulamalar listesinde kalır; kullanıcı sitene geldiğinde yeniden giriş yapar). "Diğer cihazlardan çık" düğmesi ve iki aşamalı doğrulama değişiklikleri OAuth2 token'larını düşürmez.
  • Hesap veya uygulama kapanışı: Kullanıcı N'oluyo hesabını sildiğinde/dondurduğunda ya da sen uygulamayı sildiğinde/durdurduğunda tüm token'lar derhal geçersiz kalır.

Güvenlik listesi ve yasal yükümlülüklerin

Yayına çıkmadan önce şu maddeleri doğrula:

  • client_secret hiçbir zaman tarayıcı koduna, mobil pakete veya Git deposuna girmez; yalnız arka uç .env dosyasında durur.
  • Her giriş isteğinde yeni bir state ve code_verifier üretilir ve geri dönüşte state doğrulanmadan kod takas edilmez.
  • Portalda yalnızca gerçekten kullandığın tam redirect_uri adresleri kayıtlıdır.
  • Kullanıcı kimliği yalnızca user.id üzerinden tutulur ve user.email === null durumu hatasız ele alınır.
  • Yasal sorumluluk: N'oluyo ile giriş üzerinden kendi sistemine çektiğin veriler için kendi kullanıcılarına karşı bağımsız veri sorumlusun. Portalda doğru bir gizlilik politikası (privacy_url) yayınlamalı ve sitende hesabını silen kişinin senden tutulmasını istemediği OAuth2 token'larını /oauth2/revoke ile iptal edip verilerini silmelisin.

Bu sayfayı yapay zekaya verirsen

Sırada ne var

  • Doğrulama: 100 kullanıcı sınırını kaldırmak için doğrulama başvurusu
  • Güvenlik: secret ve token saklama kuralları
  • Hatalar: tüm HTTP durum ve hata kodlarının tam listesi