Go sunucu SDK'sı

Modül github.com/keenauth/keenauth-go, KeenAuth yönetim API'sinin Go istemcisidir. Her /v1 kaynağı için türlenmiş metotlar OpenAPI belgesinden üretilir (paket management, keenauth.Client içinde gömülüdür). Elle yazılmış çekirdek kimlik doğrulama, yeniden deneme, idempotency anahtarları, türlenmiş hatalar ve kullanımdan kaldırma uyarıları ekler. Modül yalnızca standart kütüphaneyi kullanır. Henüz yayımlanmamıştır.

Bir net/http uygulamasında oturum açma için Go net/http oturumları sayfasına bakın. Kendi API'nizi korumak için API'nizi koruma sayfasına bakın.

API anahtarıyla istemci oluşturun

import (
	"context"
	"fmt"
	"os"

	keenauth "github.com/keenauth/keenauth-go"
)

c, err := keenauth.NewClient("https://api.eu.keenauth.com/v1", os.Getenv("KEENAUTH_API_KEY"))
if err != nil {
	panic(err)
}
user, _, err := c.UsersAPI.GetUser(context.Background(), "usr_01j9zzzzzzzzzzzzzzzzzzzz").Execute()
if err != nil {
	panic(err)
}
fmt.Println(user.GetEmail())

Temel URL, bölgenizin API adresi ile /v1 ifadesinin birleşimidir. Anahtar (idp_sk_… veya idp_rk_…) yalnızca Authorization başlığında ve yalnızca bu adrese gönderilir; başka bir ana bilgisayara veya düz http adresine yapılan yönlendirmelerde anahtar hiçbir zaman gönderilmez. Düz http yalnızca localhost için kabul edilir.

Seçenekler:

private_key_jwt ile kimlik doğrulayın

Sabit bir anahtar yerine, bir yönetim uygulaması kendi anahtar çifti ile oturum açabilir. SDK, ortamınızın belirteç uç noktasından bir yönetim belirteci alır, bunu bellekte tutar ve süresi dolmadan 60 saniye önce yeniler. Eşzamanlı çağrılar tek bir belirteç isteğini paylaşır.

  1. Kontrol panelinde bir yönetim uygulaması oluşturun ve genel anahtarını kaydedin (JWKS URL'si olarak veya doğrudan; en fazla 3 etkin anahtar).
  2. Özel anahtarı sunucunuzda tutun, örneğin bir gizli dosyada veya bir KMS'de.
import (
	"os"

	keenauth "github.com/keenauth/keenauth-go"
	"github.com/keenauth/keenauth-go/assertion"
)

pemBytes, err := os.ReadFile("/run/secrets/keenauth-mgmt-key.pem")
if err != nil {
	panic(err)
}
key, err := assertion.ParsePrivateKeyPEM(pemBytes)
if err != nil {
	panic(err)
}
c, err := keenauth.NewClientWithPrivateKeyJWT("https://api.eu.keenauth.com/v1", keenauth.PrivateKeyJWT{
	Issuer:   "https://acme.eu.keenauth.app", // your environment's issuer, exactly
	ClientID: "app_01j9zzzzzzzzzzzzzzzzzzzz",
	Key:      key,
	KeyID:    "mgmt-key-2026-11", // the kid you registered; optional with a single key
	Scope:    "users:read users:write", // optional; default: the grant's default scopes
})

SDK'nın gönderdikleri:

Key, herhangi bir crypto.Signer olabilir; dolayısıyla bir KMS veya HSM içinde tutulan anahtar dışa aktarılmadan kullanılabilir. Yönetim belirteci 10 dakika yaşar. API 401 yanıtı verirse SDK önbellekteki belirteci siler ve bir sonraki çağrı yeni bir belirteç alır.

Reddedilen bir belirteç isteği, HTTP durumu ve OAuth error kodu ile birlikte bir *keenauth.TokenError döndürür; örneğin bilinmeyen bir anahtar veya yanlış bir KeyID için invalid_client. Bu hata hiçbir zaman onayı veya bir belirteci içermez:

var te *keenauth.TokenError
if errors.As(err, &te) {
	log.Printf("token request refused: %d %s", te.Status, te.Code)
}

Yeniden denemeler ve idempotency anahtarları

SDK, bir çağrıyı toplamda en fazla 3 kez yeniden dener ve bunu yalnızca iki koşul birlikte sağlandığında yapar:

Idempotency-Key kabul eden her POST işlemi (yan etkisiz users:search ve …:preview çağrıları ile users/{id}:unlock hariç tümü) yeni bir UUIDv4 anahtarı alır ve o çağrının her yeniden denemesi aynı anahtarı ve aynı gövdeyi gönderir; böylece işlem en fazla bir kez çalışır. Kendi yeniden başlatmalarınız boyunca bir çağrıyı idempotent yapmak için kendi anahtarınızı (A-Z a-z 0-9 _ - karakterlerinden 16 ila 64 karakter) geçirin, örneğin .IdempotencyKey(orderKey); SDK bunu asla değiştirmez. If-Match taşıyan bir PATCH (her sürümlü güncelleme) yalnızca 429 durumunda yeniden denenir: hız sınırlayıcı, istek çalışmadan önce reddeder ve If-Match bir değişikliğin iki kez uygulanmasını önler. Diğer tüm PATCH istekleri hiçbir zaman yeniden denenmez.

Denemeler arasında SDK, Retry-After değerinin söylediği kadar bekler (saniye veya bir HTTP tarihi). Bu başlık yoksa önce 0,5 saniye, sonra 1 saniye bekler; her bekleme ±%25 rastgele sapma içerir. Retry-After 60 saniyeden fazla isterse veya bekleme bağlamınızın son tarihini aşacaksa, SDK beklemek yerine yanıtı döndürür. Bağlamın iptali beklemeyi durdurur.

Bir listede gezinin

Liste metotları tek bir sayfa döndürür: data ve before ile after imleçlerini içeren list_metadata. keenauth.All, after imlecini izler ve her öğeyi döndürür. Sonraki sayfayı yalnızca ona ulaşıldığında getirir; dolayısıyla döngüden çıkmak daha fazla istek yapılmasını durdurur. Sayfa boyutunu ve filtreleri her zamanki gibi istek üzerinde ayarlayın:

for u, err := range keenauth.All(c.UsersAPI.ListUsers(ctx).Limit(100).Status("active")) {
	if err != nil {
		return err // the iteration ends after an error
	}
	fmt.Println(u.GetId(), u.GetEmail())
}

keenauth.AllBefore bunun yerine before imlecini izler; örneğin ListLogs için daha yeni kayıtlara doğru. Üretilen her liste metodu her ikisiyle de çalışır; yalnızca next_cursor ve has_more ile sayfalayan Events API'nin ListEvents metodu hariçtir. Aralık-üzerinden-fonksiyon döngüsü Go 1.23 veya üzerini gerektirir.

Webhook'ları doğrulayın

github.com/keenauth/keenauth-go/webhook paketi, bir webhook teslimatının imzasını (Standard Webhooks) denetler. Ona uç noktanın imzalama gizli anahtarını (idp_whsec_…) verin ve ham gövdeyi alındığı gibi geçirin:

import (
	"io"
	"net/http"
	"os"

	"github.com/keenauth/keenauth-go/webhook"
)

verifier, err := webhook.NewVerifier([]string{os.Getenv("KEENAUTH_WEBHOOK_SECRET")})
if err != nil {
	panic(err) // webhook.ErrSecret: the secret is not well formed
}

http.HandleFunc("POST /webhooks/keenauth", func(w http.ResponseWriter, r *http.Request) {
	body, err := io.ReadAll(http.MaxBytesReader(w, r.Body, 1<<20))
	if err != nil {
		http.Error(w, "body too large", http.StatusRequestEntityTooLarge)
		return
	}
	if err := verifier.Verify(r.Header, body); err != nil {
		http.Error(w, "invalid webhook", http.StatusBadRequest)
		return
	}
	// Handle the event. Store webhook-id and skip a delivery you have already processed.
	w.WriteHeader(http.StatusNoContent)
})

Verify, webhook-timestamp değeri şu andan 5 dakika içindeyse (her iki yönde) ve webhook-signature içindeki v1 imzalarından biri eşleşiyorsa bir teslimatı kabul eder. İmzalar sabit zamanda karşılaştırılır ve diğer imza sürümleri yok sayılır. Hata, bir teslimatın neden reddedildiğini söyler; her biri errors.Is ile webhook.ErrHeaders (bir başlık eksik), webhook.ErrTimestamp, webhook.ErrSignature veya webhook.ErrSecret değerlerinden birine eşleşir.

Gizli anahtarı döndürürken, eski anahtar kullanılmayı bırakana kadar hem yeni hem eski anahtarı geçirin: webhook.NewVerifier([]string{newSecret, oldSecret}). Döndürme sırasında KeenAuth tek bir başlıkta iki imza da gönderebilir; bunlardan herhangi biri yeterlidir.

Türlenmiş olaylar ve talepler

github.com/keenauth/keenauth-go/events paketi, genel olay şemalarından üretilmiş her müşteri olay türü için bir yapı içerir. webhook.Verifier.VerifyEvent bir teslimatı doğrular ve ayrıştırır; events.Parse ise zaten doğruladığınız bir gövdeyi ayrıştırır. Tek bir türün alanlarını almak için bir tür anahtarı (type switch) kullanın:

ev, err := verifier.VerifyEvent(r.Header, body)
if err != nil {
	http.Error(w, "invalid webhook", http.StatusBadRequest) // webhook.Err… or events.ErrParse
	return
}
switch e := ev.(type) {
case *events.UserCreated:
	log.Printf("user %s created", e.Data.Object.ID)
case *events.EmailSendingSuspended:
	log.Printf("sending suspended: %s", e.Data.Object.Reason)
case *events.Unknown:
	// A type this SDK version does not know yet. e.Type and e.Raw() are still available.
}

github.com/keenauth/keenauth-go/claims paketi, doğrulanmış bir belirtecin taleplerini türler: bir ID belirteci için claims.ParseIDToken (OpenID Connect Core) ve bir erişim belirteci için claims.ParseAccessToken (RFC 9068). Talep türlerini denetlerler ancak hiçbir şeyi doğrulamazlar; bu nedenle verify paketinin döndürdüğü Claims haritası üzerinde çağırın:

at, err := v.VerifyAccessToken(ctx, token, nil)
if err != nil {
	return err
}
c, err := claims.ParseAccessToken(at.Claims)
if err != nil {
	return err
}
log.Printf("client %s, org %s, roles %v", c.ClientID, c.OrgID, c.OrgRoles)
_ = c.Custom["tenant_tier"] // your own claims

aud her zaman bir dilimdir (claims.Audience). Sözleşmenin parçası olmayan talepler kendi taleplerinizdir ve Custom içinde tutulur.

Hatalar

Bir API hatası, RFC 9457 sorun belgesidir. keenauth.AsProblem(err), onu makine tarafından okunabilir Code ile birlikte döndürür. Bilinmeyen bir kod olduğu gibi döndürülür; böylece sunucunun yeni kodları istemcinizi bozmaz:

if _, _, err := c.UsersAPI.GetUser(ctx, id).Execute(); err != nil {
	if pe, ok := keenauth.AsProblem(err); ok && pe.Code == "not_found" {
		// …
	}
}

Kullanımdan kaldırma uyarıları

Bir yanıt Deprecation (RFC 9745) veya Sunset (RFC 8594) başlığı taşıdığında SDK, her işlem ve başlık değeri için tüm süreç boyunca bir uyarı kaydeder; bu uyarı WithLogger ile ayarladığınız logger üzerinde WARN düzeyindedir. Uyarı, metodu ve kimlikleri {id} ile değiştirilmiş yolu, başlık değerlerini ve rel="deprecation" veya rel="sunset" içeren her Link değerini belirtir. Hiçbir zaman kimlik, belirteç veya anahtar içermez ve çağrının kendisi etkilenmez.

level=WARN msg="keenauth: the API operation is deprecated" operation="GET /users/{id}" deprecation=@1767225600 sunset="Wed, 01 Jul 2026 00:00:00 GMT"