Proteggi un'API Go
Questa guida rapida protegge un'API Go net/http con token di accesso KeenAuth. Aggiungi il middleware protect dell'SDK Go, richiedi uno scope su una route e testa l'API offline con il test kit dell'SDK. Il middleware usa solo la libreria standard e funziona con qualsiasi router che accetti un http.Handler.
L'API ha tre endpoint:
| Endpoint | Richiede |
|---|---|
GET /api/public |
nulla |
GET /api/private |
un token di accesso valido per la tua API |
GET /api/messages |
un token di accesso valido con lo scope messages:read |
Prima di iniziare
- Un ambiente KeenAuth. Il suo issuer ha questo aspetto:
https://your-environment.eu.keenauth.app. - Un'API registrata nell'ambiente (Crea un'API). Il suo
identifier, per esempiohttps://api.example.com, è l'audience dei token che la tua API accetta. - Lo scope
messages:readsu quell'API, concesso all'applicazione che la chiama. - Go 1.26 o successivo.
1. Installa l'SDK
go get github.com/keenauth/keenauth-go
L'SDK non è ancora pubblicato. Finché non lo sarà, l'esempio viene compilato con il sorgente dell'SDK tramite una direttiva replace in go.mod.
2. Configura
L'API legge la configurazione solo da variabili d'ambiente:
| Variabile | Obbligatoria | Significato |
|---|---|---|
KEENAUTH_ISSUER |
sì | L'issuer del tuo ambiente |
KEENAUTH_AUDIENCE |
sì | L'identificatore della tua API |
PUBLIC_BASE_URL |
sì | L'URL che i client chiamano (vedi Dietro un proxy) |
KEENAUTH_WEBHOOK_SECRET |
consigliata | Il segreto di firma idp_whsec_ dell'endpoint webhook che consegna token.revoked (due, separati da virgola, durante la rotazione). Senza di esso l'API non viene mai a sapere dei token revocati |
ADDR |
no | Indirizzo di ascolto, predefinito :8080 |
3. Proteggi le route
NewHandler crea il middleware e le route. m.Handler richiede un token valido; m.Require richiede anche degli scope. Il gestore legge i claim verificati con protect.ClaimsFrom.
// 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 costruisce il verificatore reale dalla configurazione. Il verificatore recupera le chiavi di firma dal tuo ambiente la prima volta che vede un token.
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. Avvia e chiama l'API
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 .
Per una prima chiamata, ottieni un token con il grant client credentials di un'applicazione machine-to-machine che ha lo scope messages:read sulla tua API:
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. Testa senza rete
Il pacchetto del test kit keenauthtest genera token firmati con una chiave di test pubblica e costruisce un verificatore che accetta solo quei token. I test passano quel verificatore a NewHandler, quindi non richiedono rete né un account KeenAuth. Vedi Test kit offline.
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 ./...
I test dell'esempio verificano che un token valido venga accettato e che un token mancante, scaduto o per un'altra audience, uno scope mancante, un token nella query dell'URL e un token revocato vengano rifiutati con gli stati indicati sotto. Inviano anche un token legato a DPoP con una prova per PUBLIC_BASE_URL. I test inviano inoltre consegne token.revoked firmate: una valida revoca il token, una contraffatta, alterata o obsoleta riceve 400 e non revoca nulla, e una ripetuta ha un solo effetto.
Risposte
| Richiesta | Stato | code nel body |
|---|---|---|
Nessun header Authorization |
401 |
missing_token |
| Token scaduto, token per un'altra API, firma non valida, token revocato | 401 |
invalid_token |
| Token senza lo scope richiesto | 403 |
insufficient_scope |
Token nella query dell'URL (?access_token=) |
400 |
invalid_request |
Il body è application/problem+json e l'header WWW-Authenticate contiene la sfida standard. La tabella completa è in Proteggere la tua API con il middleware.
Dietro un proxy
Se i client usano DPoP, il middleware verifica che ogni prova sia stata fatta per l'URL che il client ha chiamato. Imposta PUBLIC_BASE_URL sull'URL esterno che i client chiamano, per esempio https://api.example.com, anche dietro un proxy o un load balancer. È obbligatorio e deve usare https (http solo per localhost, 127.0.0.1 o [::1]); l'app rifiuta di avviarsi senza. Il middleware non si fida mai degli header X-Forwarded-*.
Token revocati
Un token di accesso resta valido fino alla scadenza, quindi KeenAuth comunica alla tua API quando i token vengono revocati. L'esempio passa una cache di revoca in memoria al middleware e serve un webhook token.revoked su POST /webhooks/keenauth (main.go, webhook.go) che la alimenta.
- Nella dashboard, crea un endpoint webhook con l'URL
https://<your host>/webhooks/keenauthche si iscrive atoken.revoked, ed esporta il suo segreto di firma comeKEENAUTH_WEBHOOK_SECRET. - Il gestore verifica la firma con l'SDK sul body grezzo e risponde
400quando la firma non è valida o il timestamp differisce di oltre cinque minuti. Mappa il selettore dell'evento (access_token,session,user,client,org,org_user,client_user) su un marcatore di revoca e risponde204; il middleware poi rifiuta ogni token corrispondente emesso prima dinot_before. - KeenAuth consegna almeno una volta, quindi il gestore applica ogni
webhook-iduna sola volta. Se il marcatore non può essere salvato risponde503e KeenAuth consegna di nuovo l'evento. Ignora gli altri tipi di evento e i selettori che nessun token di accesso può corrispondere (refresh_family,api).
Il gestore registra l'id dell'evento, il tipo di selettore e il motivo, mai il body, una firma o il segreto. Ogni processo ha la propria cache; con più istanze, metti i marcatori in un archivio condiviso dietro lo stesso metodo Revoke. Vedi Cache di revoca.
Prossimi passi
- Proteggere la tua API con il middleware: requisiti di step-up, DPoP e ogni opzione.
- Verificare i token: il verificatore sotto il middleware.
- Test kit offline.