Proteja una API de Go
Esta guía de inicio rápido protege una API de Go net/http con tokens de acceso de KeenAuth. Añade el middleware protect del SDK de Go, exige un ámbito en una ruta y prueba la API sin conexión con el kit de pruebas del SDK. El middleware usa solo la biblioteca estándar y funciona con cualquier router que acepte un http.Handler.
La API tiene tres endpoints:
| Endpoint | Requiere |
|---|---|
GET /api/public |
nada |
GET /api/private |
un token de acceso válido para su API |
GET /api/messages |
un token de acceso válido con el ámbito messages:read |
Antes de empezar
- Un entorno de KeenAuth. Su emisor tiene el formato
https://your-environment.eu.keenauth.app. - Una API registrada en el entorno (Cree una API). Su
identifier, por ejemplohttps://api.example.com, es la audiencia de los tokens que acepta su API. - El ámbito
messages:readen esa API, concedido a la aplicación que la llama. - Go 1.26 o posterior.
1. Instale el SDK
go get github.com/keenauth/keenauth-go
El SDK aún no está publicado. Mientras tanto, el ejemplo se compila con el código fuente del SDK mediante una directiva replace en go.mod.
2. Configure
La API lee su configuración solo desde variables de entorno:
| Variable | Obligatoria | Significado |
|---|---|---|
KEENAUTH_ISSUER |
sí | El emisor de su entorno |
KEENAUTH_AUDIENCE |
sí | El identificador de su API |
PUBLIC_BASE_URL |
sí | La URL que llaman los clientes (véase Detrás de un proxy) |
KEENAUTH_WEBHOOK_SECRET |
recomendada | El secreto de firma idp_whsec_ del endpoint de webhook que entrega token.revoked (dos, separados por comas, mientras lo rota). Sin él, la API nunca se entera de los tokens revocados |
ADDR |
no | Dirección de escucha, por defecto :8080 |
3. Proteja sus rutas
NewHandler crea el middleware y las rutas. m.Handler exige un token válido; m.Require además exige ámbitos. El handler lee las claims verificadas 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 construye el verificador real a partir de la configuración. El verificador obtiene las claves de firma de su entorno la primera vez que ve 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. Ejecute y llame a la 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 .
Para una primera llamada, obtenga un token con el flujo client credentials de una aplicación máquina a máquina que tenga el ámbito messages:read en su 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. Pruebe sin red
El paquete del kit de pruebas keenauthtest genera tokens firmados con una clave pública de prueba y construye un verificador que solo acepta esos tokens. Las pruebas pasan ese verificador a NewHandler, por lo que no necesitan red ni una cuenta de KeenAuth. Véase Kit de pruebas sin conexión.
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 ./...
Las pruebas del ejemplo comprueban que se acepta un token válido y que se rechazan, con los estados indicados abajo, un token ausente, caducado o con una audiencia incorrecta, un ámbito ausente, un token en la query de la URL y un token revocado. También envían un token vinculado con DPoP con una prueba para PUBLIC_BASE_URL. Además, envían entregas firmadas de token.revoked: una válida revoca el token, una falsificada, alterada o obsoleta recibe 400 y no revoca nada, y una repetida produce un solo efecto.
Respuestas
| Solicitud | Estado | code en el cuerpo |
|---|---|---|
Sin encabezado Authorization |
401 |
missing_token |
| Token caducado, token para otra API, firma incorrecta, token revocado | 401 |
invalid_token |
| Token sin el ámbito requerido | 403 |
insufficient_scope |
Token en la query de la URL (?access_token=) |
400 |
invalid_request |
El cuerpo es application/problem+json y el encabezado WWW-Authenticate lleva el desafío estándar. La tabla completa está en Protección de su API con middleware.
Detrás de un proxy
Si los clientes usan DPoP, el middleware comprueba que cada prueba se hizo para la URL que llamó el cliente. Establezca PUBLIC_BASE_URL con la URL externa que llaman los clientes, por ejemplo https://api.example.com, también detrás de un proxy o balanceador de carga. Es obligatoria y debe usar https (http solo para localhost, 127.0.0.1 o [::1]); la aplicación se niega a arrancar sin ella. El middleware nunca confía en los encabezados X-Forwarded-*.
Tokens revocados
Un token de acceso sigue siendo válido hasta que caduca, por lo que KeenAuth informa a su API cuando se revocan tokens. El ejemplo pasa una caché de revocación en memoria al middleware y sirve un webhook token.revoked en POST /webhooks/keenauth (main.go, webhook.go) que la alimenta.
- En el panel, cree un endpoint de webhook con la URL
https://<your host>/webhooks/keenauthque se suscriba atoken.revoked, y exporte su secreto de firma comoKEENAUTH_WEBHOOK_SECRET. - El handler verifica la firma con el SDK sobre el cuerpo sin procesar y responde
400cuando no es válida o cuando la marca temporal se desvía más de cinco minutos. Asocia el selector del evento (access_token,session,user,client,org,org_user,client_user) con un marcador de revocación y responde204; a continuación, el middleware rechaza todos los tokens coincidentes emitidos antes denot_before. - KeenAuth entrega al menos una vez, así que el handler aplica cada
webhook-iduna sola vez. Si no se puede almacenar el marcador, responde503y KeenAuth vuelve a entregar el evento. Ignora otros tipos de evento y selectores que ningún token de acceso puede coincidir (refresh_family,api).
El handler registra el id del evento, el tipo de selector y el motivo, nunca el cuerpo, una firma ni el secreto. Cada proceso tiene su propia caché; con varias instancias, guarde los marcadores en un almacén compartido detrás del mismo método Revoke. Véase Caché de revocación.
Próximos pasos
- Protección de su API con middleware: requisitos de step-up, DPoP y todas las opciones.
- Verificación de tokens: el verificador que está bajo el middleware.
- Kit de pruebas sin conexión.