L’autenticazione con chiave API è un modo semplice e popolare per proteggere le API. Prevede la distribuzione di chiavi uniche ai client che poi includono queste chiavi nelle loro richieste. Questo metodo è particolarmente utile per API pubbliche dove vuoi tracciare l’utilizzo, implementare rate limiting o fornire diversi livelli di accesso a diversi client.
Le chiavi API possono essere trasmesse in diversi modi:
Il metodo più sicuro è l’uso di header, tipicamente con un nome come X-API-Key
o Authorization
.
Prima, definisci il tuo schema di sicurezza per chiave API nel tuo pacchetto di design:
package design
import (
. "goa.design/goa/v3/dsl"
)
// APIKeyAuth definisce il nostro schema di sicurezza
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Sicurezza con chiave API")
Header("X-API-Key") // Specifica nome header
})
Puoi anche usare parametri query invece degli header:
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Sicurezza con chiave API")
Query("api_key") // Specifica nome parametro query
})
Come altri schemi di sicurezza, l’autenticazione con chiave API può essere applicata a diversi livelli:
// Livello API - si applica a tutti i servizi e metodi
var _ = API("secure_api", func() {
Security(APIKeyAuth)
})
// Livello Servizio - si applica a tutti i metodi nel servizio
var _ = Service("secure_service", func() {
Security(APIKeyAuth)
})
// Livello Metodo - si applica solo a questo metodo
Method("secure_method", func() {
Security(APIKeyAuth)
})
Per i metodi che usano l’autenticazione con chiave API, includi la chiave nel payload:
Method("getData", func() {
Security(APIKeyAuth)
Payload(func() {
APIKey("api_key", "key", String, func() {
Description("Chiave API per l'autenticazione")
Example("abcdef123456")
})
Required("key")
// Campi payload aggiuntivi
Field(1, "query", String, "Query di ricerca")
})
Result(ArrayOf(String))
Error("unauthorized")
HTTP(func() {
GET("/data")
// Mappa la chiave sull'header
Header("key:X-API-Key")
Response("unauthorized", StatusUnauthorized)
})
})
Quando Goa genera il codice, dovrai implementare un gestore di sicurezza:
// SecurityAPIKeyFunc implementa la logica di autorizzazione per l'autenticazione con chiave API
func (s *service) APIKeyAuth(ctx context.Context, key string) (context.Context, error) {
// Implementa qui la tua logica di validazione chiave
valid, err := s.validateAPIKey(key)
if err != nil {
return ctx, err
}
if !valid {
return ctx, genservice.MakeUnauthorized(fmt.Errorf("chiave API non valida"))
}
// Puoi aggiungere dati specifici della chiave al contesto
ctx = context.WithValue(ctx, "api_key_id", key)
return ctx, nil
}
func (s *service) validateAPIKey(key string) (bool, error) {
// Implementazione della validazione chiave
// Questo potrebbe controllare contro un database, cache, ecc.
return key == "valid-key", nil
}
Genera chiavi API forti e casuali:
func GenerateAPIKey() string {
// Genera 32 byte casuali
bytes := make([]byte, 32)
if _, err := rand.Read(bytes); err != nil {
panic(err)
}
// Codifica in base64
return base64.URLEncoding.EncodeToString(bytes)
}
Archivia le chiavi API in modo sicuro:
Esempio di schema archiviazione chiavi:
CREATE TABLE api_keys (
id UUID PRIMARY KEY,
key_hash VARCHAR(64) NOT NULL,
client_id UUID NOT NULL,
created_at TIMESTAMP NOT NULL,
expires_at TIMESTAMP,
last_used_at TIMESTAMP,
is_active BOOLEAN DEFAULT true
);
Associa metadati alle chiavi API per un migliore controllo:
type APIKeyMetadata struct {
ClientID string
Plan string // es. "free", "premium"
Permissions []string // es. ["read", "write"]
ExpiresAt time.Time
}
func (s *service) APIKeyAuth(ctx context.Context, key string) (context.Context, error) {
metadata, err := s.getAPIKeyMetadata(key)
if err != nil {
return ctx, err
}
// Aggiungi metadati al contesto
ctx = context.WithValue(ctx, "api_key_metadata", metadata)
return ctx, nil
}
Ecco un esempio completo che mostra come implementare l’autenticazione con chiave API in un servizio Goa:
package design
import (
. "goa.design/goa/v3/dsl"
)
var APIKeyAuth = APIKeySecurity("api_key", func() {
Description("Autentica usando una chiave API")
Header("X-API-Key")
})
var _ = API("weather_api", func() {
Title("API Meteo")
Description("API previsioni meteo con autenticazione chiave API")
// Applica autenticazione chiave API di default
Security(APIKeyAuth)
})
var _ = Service("weather", func() {
Description("Servizio previsioni meteo")
Method("forecast", func() {
Description("Ottieni previsioni meteo")
Payload(func() {
// La chiave API sarà inclusa automaticamente
Field(1, "location", String, "Località per cui ottenere le previsioni")
Field(2, "days", Int, "Numero di giorni da prevedere")
Required("location")
})
Result(func() {
Field(1, "location", String, "Località")
Field(2, "forecast", ArrayOf(WeatherDay))
})
HTTP(func() {
GET("/forecast/{location}")
Param("days")
Response(StatusOK)
Response(StatusUnauthorized, func() {
Description("Chiave API non valida o mancante")
})
Response(StatusTooManyRequests, func() {
Description("Limite di velocità superato")
})
})
})
// Esempio di endpoint pubblico
Method("health", func() {
Description("Endpoint di controllo salute")
NoSecurity()
Result(String)
HTTP(func() {
GET("/health")
})
})
})
Implementa un sistema per la rotazione periodica delle chiavi:
func (s *service) rotateAPIKey(clientID string) error {
// Genera nuova chiave
newKey := GenerateAPIKey()
// Archivia nuova chiave con periodo di grazia
if err := s.storeNewAPIKey(clientID, newKey); err != nil {
return err
}
// Notifica client della nuova chiave
return s.notifyClientOfNewKey(clientID, newKey)
}
Implementa rate limiting basato sulla chiave API:
func (s *service) checkRateLimit(key string) error {
// Ottieni limiti per questa chiave
limits, err := s.getAPIKeyLimits(key)
if err != nil {
return err
}
// Controlla e aggiorna conteggi richieste
if exceeded := s.rateLimit.Check(key, limits); exceeded {
return ErrRateLimitExceeded
}
return nil
}
Traccia l’utilizzo delle chiavi API:
func (s *service) logAPIKeyUsage(ctx context.Context, key string) {
metadata := ctx.Value("api_key_metadata").(APIKeyMetadata)
s.metrics.RecordAPICall(metadata.ClientID, metadata.Plan)
s.logger.Info("API call",
"client_id", metadata.ClientID,
"plan", metadata.Plan,
"endpoint", ctx.Value("endpoint"),
)
}