Go API'sini koruyun

Bu hızlı başlangıç, bir Go net/http API'sini KeenAuth erişim belirteçleriyle korur. Go SDK'nın protect ara katmanını eklersiniz, bir rotada bir kapsam zorunlu kılarsınız ve API'yi SDK test kiti ile çevrimdışı test edersiniz. Ara katman yalnızca standart kütüphaneyi kullanır ve bir http.Handler kabul eden her yönlendiriciyle çalışır.

API'nin üç uç noktası vardır:

Uç nokta Gerektirir
GET /api/public hiçbir şey
GET /api/private API'niz için geçerli bir erişim belirteci
GET /api/messages messages:read kapsamına sahip geçerli bir erişim belirteci

Başlamadan önce

1. SDK'yı yükleyin

go get github.com/keenauth/keenauth-go

SDK henüz yayımlanmadı. Yayımlanana kadar örnek, go.mod dosyasında bir replace yönergesiyle SDK kaynağına karşı derlenir.

2. Yapılandırın

API yapılandırmasını yalnızca ortam değişkenlerinden okur:

Değişken Zorunlu Anlamı
KEENAUTH_ISSUER evet Ortamınızın veren adresi
KEENAUTH_AUDIENCE evet API'nizin tanımlayıcısı
PUBLIC_BASE_URL evet İstemcilerin çağırdığı URL (bkz. Proxy arkasında)
KEENAUTH_WEBHOOK_SECRET önerilir token.revoked gönderen webhook uç noktasının idp_whsec_ imza sırrı (döndürme sırasında virgülle ayrılmış iki değer). Bu olmadan API, iptal edilmiş belirteçleri asla öğrenmez
ADDR hayır Dinleme adresi, varsayılan :8080

3. Rotalarınızı koruyun

NewHandler ara katmanı ve rotaları oluşturur. m.Handler geçerli bir belirteç ister; m.Require ayrıca kapsam da ister. İşleyici, doğrulanmış talepleri protect.ClaimsFrom ile okur.

// NewHandler builds the API. The verifier is a parameter so that tests can pass the offline test kit's verifier;
// main passes a real one. The revocation cache is fed from the token.revoked webhook.
func NewHandler(v protect.AccessTokenVerifier, publicBaseURL string, revocation protect.RevocationChecker) (http.Handler, error) {
	m, err := protect.New(v, protect.Options{
		PublicBaseURL: publicBaseURL,
		Revocation:    revocation,
		OnError: func(r *http.Request, err error) {
			// The error never contains the token or claim values; never log the Authorization or DPoP header.
			log.Printf("rejected %s %s: %v", r.Method, r.URL.Path, err)
		},
	})
	if err != nil {
		return nil, err
	}

	mux := http.NewServeMux()
	// No token needed.
	mux.HandleFunc("GET /api/public", func(w http.ResponseWriter, _ *http.Request) {
		writeJSON(w, map[string]string{"message": "Hello from a public endpoint."})
	})
	// Any valid access token for this API.
	mux.Handle("GET /api/private", m.Handler(http.HandlerFunc(func(w http.ResponseWriter, r *http.Request) {
		claims, _ := protect.ClaimsFrom(r.Context())
		writeJSON(w, map[string]string{"message": "Hello from a private endpoint.", "sub": claims.Subject})
	})))
	// A valid access token that also carries the messages:read scope.
	mux.Handle("GET /api/messages", m.Require(protect.Requirement{Scopes: []string{"messages:read"}})(
		http.HandlerFunc(func(w http.ResponseWriter, _ *http.Request) {
			writeJSON(w, map[string]any{"messages": []string{"Hello from a scoped endpoint."}})
		})))
	return mux, nil
}

main yapılandırmadan gerçek doğrulayıcıyı oluşturur. Doğrulayıcı, bir belirteç gördüğü ilk seferde imza anahtarlarını ortamınızdan getirir.

func main() {
	cfg, err := ConfigFromEnv(os.Getenv)
	if err != nil {
		log.Fatal(err)
	}
	// verify.New refuses an issuer under the reserved .invalid domain, so a test token can never pass here.
	v, err := verify.New(verify.Config{Issuer: cfg.Issuer, Audience: cfg.Audience})
	if err != nil {
		log.Fatal(err)
	}
	revocation := protect.NewMemoryRevocationCache(0, nil)
	api, err := NewHandler(v, cfg.PublicBaseURL, revocation)
	if err != nil {
		log.Fatal(err)
	}
	h := api
	if len(cfg.WebhookSecrets) > 0 {
		wv, err := webhook.NewVerifier(cfg.WebhookSecrets) // the error never contains the secret
		if err != nil {
			log.Fatalf("KEENAUTH_WEBHOOK_SECRET: %v", err)
		}
		h = WithWebhook(api, NewWebhookHandler(wv, revocation, nil))
	} else {
		log.Print("KEENAUTH_WEBHOOK_SECRET is not set: token.revoked events are not received, so revoked tokens stay valid until they expire")
	}
	srv := &http.Server{Addr: cfg.Addr, Handler: h, ReadHeaderTimeout: 10 * time.Second}
	log.Printf("listening on %s", cfg.Addr)
	log.Fatal(srv.ListenAndServe())
}

4. API'yi çalıştırın ve çağırın

export KEENAUTH_ISSUER=https://your-environment.eu.keenauth.app
export KEENAUTH_AUDIENCE=https://api.example.com
export PUBLIC_BASE_URL=https://api.example.com
go run .

İlk çağrı için, ortamınızdaki API üzerinde messages:read kapsamına sahip bir makineden makineye uygulamanın istemci kimlik bilgileri akışıyla bir belirteç alın:

ACCESS_TOKEN=$(curl -s -u "$CLIENT_ID:$CLIENT_SECRET" \
  -d grant_type=client_credentials \
  -d resource=https://api.example.com \
  -d scope=messages:read \
  https://your-environment.eu.keenauth.app/oauth2/token | jq -r .access_token)

curl -i http://localhost:8080/api/messages -H "Authorization: Bearer $ACCESS_TOKEN"

5. Ağ olmadan test edin

Test kiti paketi keenauthtest genel test anahtarıyla imzalanmış belirteçler üretir ve yalnızca bu belirteçleri kabul eden bir doğrulayıcı oluşturur. Testler bu doğrulayıcıyı NewHandler işlevine iletir; bu yüzden ağ bağlantısına ve KeenAuth hesabına ihtiyaç duymazlar. Bkz. Çevrimdışı test kiti.

func newTestAPI(t *testing.T) (http.Handler, *protect.MemoryRevocationCache) {
	t.Helper()
	v, err := keenauthtest.NewVerifier(verify.Config{Audience: audience})
	if err != nil {
		t.Fatal(err)
	}
	cache := protect.NewMemoryRevocationCache(0, nil)
	h, err := NewHandler(v, baseURL, cache)
	if err != nil {
		t.Fatal(err)
	}
	return h, cache
}
go test ./...

Örneğin testleri, geçerli bir belirtecin kabul edildiğini; eksik, süresi dolmuş veya yanlış hedef kitleli belirtecin, eksik kapsamın, URL sorgusundaki bir belirtecin ve iptal edilmiş bir belirtecin aşağıdaki durum kodlarıyla reddedildiğini denetler. Ayrıca PUBLIC_BASE_URL için bir kanıtla birlikte DPoP'a bağlı bir belirteç gönderirler. Testler ayrıca imzalı token.revoked teslimatları gönderir: geçerli bir teslimat belirteci iptal eder; sahte, değiştirilmiş veya eski bir teslimat 400 yanıtı alır ve hiçbir şeyi iptal etmez; tekrarlanan bir teslimatın etkisi tek olur.

Yanıtlar

İstek Durum Gövdede code
Authorization başlığı olmayan 401 missing_token
Süresi dolmuş belirteç, başka bir API için belirteç, hatalı imza, iptal edilmiş belirteç 401 invalid_token
Gerekli kapsamı olmayan belirteç 403 insufficient_scope
URL sorgusundaki belirteç (?access_token=) 400 invalid_request

Gövde application/problem+json ve WWW-Authenticate başlığı standart meydan okumayı taşır. Tam tablo ara katmanla API'nizi koruma sayfasındadır.

Proxy arkasında

İstemciler DPoP kullanıyorsa, ara katman her kanıtın istemcinin çağırdığı URL için yapıldığını denetler. PUBLIC_BASE_URL değerini, istemcilerin çağırdığı dış URL'ye ayarlayın, örneğin https://api.example.com; proxy veya yük dengeleyici arkasında da böyledir. Zorunludur ve https kullanmalıdır (http yalnızca localhost, 127.0.0.1 veya [::1] için); bu olmadan uygulama başlamayı reddeder. Ara katman X-Forwarded-* başlıklarına asla güvenmez.

İptal edilmiş belirteçler

Erişim belirteci süresi dolana kadar geçerli kalır; bu yüzden KeenAuth, belirteçler iptal edildiğinde API'nize bildirir. Örnek, ara katmana bellek içi bir iptal önbelleği verir ve bunu besleyen bir token.revoked webhook'unu POST /webhooks/keenauth adresinde (main.go, webhook.go) sunar.

  1. Panelde, https://<your host>/webhooks/keenauth URL'sine sahip ve token.revoked olaylarına abone olan bir webhook uç noktası oluşturun ve imza sırrını KEENAUTH_WEBHOOK_SECRET olarak dışa aktarın.
  2. İşleyici, imzayı ham gövde üzerinde SDK ile doğrular ve geçersizse ya da zaman damgası beş dakikadan fazla sapmışsa 400 yanıtı verir. Olayın seçicisini (access_token, session, user, client, org, org_user, client_user) bir iptal işaretçisine eşler ve 204 yanıtı verir; ara katman bundan sonra not_before öncesinde verilmiş eşleşen tüm belirteçleri reddeder.
  3. KeenAuth en az bir kez teslim eder, bu yüzden işleyici her webhook-id değerini bir kez uygular. İşaretçi kaydedilemezse 503 yanıtı verir ve KeenAuth olayı yeniden teslim eder. Diğer olay türlerini ve hiçbir erişim belirtecinin eşleşemeyeceği seçicileri (refresh_family, api) yok sayar.

İşleyici olay kimliğini, seçici türünü ve nedeni kaydeder; gövdeyi, imzayı veya sırrı asla kaydetmez. Her süreç kendi önbelleğine sahiptir; birden fazla örnekle çalışırken işaretçileri, aynı Revoke yöntemi arkasında paylaşılan bir depoya koyun. Bkz. İptal önbelleği.

Sonraki adımlar