Proteggere un'API ASP.NET Core non significa soltanto verificare che la richiesta contenga un token valido. In un sistema reale servono controlli distinti per identificare il chiamante, decidere cosa può fare, proteggere la capacità del servizio e ricostruire le operazioni rilevanti.
1. Autenticazione: chi sta chiamando?
L'autenticazione stabilisce l'identità del client o dell'utente. In ASP.NET Core può essere implementata con JWT Bearer, cookie, API key o integrazione con un identity provider esterno.
Il punto importante è evitare di confondere identità e permesso: un token valido dimostra chi è il chiamante, non che sia autorizzato a eseguire ogni operazione disponibile nell'API.
2. Autorizzazione: cosa può fare?
Per API con permessi granulari, le policy di ASP.NET Core permettono di modellare regole esplicite e riutilizzabili. I claim possono rappresentare capability applicative come accesso a determinate aree, download di documenti, consultazione di verifiche o operazioni di scrittura.
Questo approccio evita di distribuire controlli if nei controller. Una policy può essere applicata a più endpoint, testata separatamente e collegata a un requisito di dominio comprensibile.
3. Autorizzare anche la risorsa, non solo l'endpoint
In molti sistemi non basta sapere che il client può invocare GET /documents/{id}. Bisogna anche verificare che possa accedere proprio a quel documento. È il passaggio dall'autorizzazione per capability alla resource-based authorization.
La regola dovrebbe essere valutata il più vicino possibile al punto in cui sono disponibili sia l'identità sia la risorsa, senza affidarsi a filtri lato UI o a parametri provenienti dal client.
4. API key: non memorizzare il segreto in chiaro
Se un'integrazione usa API key, nel database conviene conservare un hash non reversibile della chiave, eventualmente combinato con un pepper mantenuto fuori dal database. La chiave completa dovrebbe essere mostrata al client solo al momento della creazione.
In questo modo una lettura non autorizzata del database non espone direttamente credenziali riutilizzabili. È inoltre utile prevedere identificazione della chiave, revoca, rotazione e tracciamento dell'ultimo utilizzo.
5. Rate limiting: proteggere capacità e stabilità
Un client autenticato può comunque generare troppo traffico, volontariamente o per errore. Il rate limiting limita il numero di richieste in una finestra temporale e può essere configurato per endpoint, API key, utente, indirizzo IP o categoria di operazione.
In ASP.NET Core il middleware di rate limiting permette di definire policy diverse. Gli endpoint costosi o sensibili possono avere limiti più restrittivi rispetto a endpoint di lettura leggeri.
Dietro reverse proxy o CDN, il limite deve inoltre usare l'identità corretta del client. Se il traffico passa da Cloudflare, ad esempio, bisogna gestire con attenzione gli header inoltrati e fidarsi solo dei proxy configurati.
6. Audit: poter ricostruire le operazioni rilevanti
I log applicativi servono soprattutto a diagnosticare il software; l'audit serve a ricostruire azioni significative dal punto di vista operativo o di sicurezza. Per una modifica importante può essere utile registrare chi ha eseguito l'azione, quale operazione, su quale risorsa, quando e con quale esito.
L'audit non dovrebbe però trasformarsi in una copia indiscriminata dei payload. Anche qui vale la minimizzazione: registrare ciò che serve per la tracciabilità senza duplicare dati personali, credenziali o contenuti sensibili.
7. Errori: non esporre dettagli interni
Una API sicura restituisce errori coerenti senza esporre stack trace, query SQL, nomi di tabelle o dettagli delle credenziali. ProblemDetails può offrire una struttura uniforme mentre i dettagli tecnici restano nei log interni, correlati tramite un identificatore di richiesta.
È utile distinguere chiaramente anche gli status code: 401 quando manca o non è valida l'identità, 403 quando il chiamante è autenticato ma non autorizzato, 429 quando viene superato un limite di traffico.
8. Consenso e vincoli di dominio restano controlli separati
In alcune API l'utente può essere autenticato e possedere il permesso tecnico, ma l'operazione deve comunque essere bloccata perché manca un consenso, una condizione contrattuale o un altro requisito di dominio.
Questi controlli non dovrebbero essere nascosti dentro l'autenticazione. Tenerli separati rende più chiaro il motivo del rifiuto e permette di evolvere le regole senza confondere identità, autorizzazioni e stato del processo.
Un modello pratico a più livelli
Un flusso tipico può essere:
- identificare il client o l'utente;
- risolvere claim e permessi;
- applicare la policy dell'endpoint;
- verificare l'accesso alla risorsa specifica;
- valutare eventuali vincoli di dominio o consenso;
- applicare il rate limiting appropriato;
- eseguire l'operazione;
- registrare l'audit quando l'azione è rilevante.
Ogni livello ha una responsabilità chiara e può essere testato separatamente. Questo riduce il rischio che la sicurezza diventi una sequenza di condizioni difficili da seguire sparse tra controller e repository.
Centralizzare dove serve, senza creare un componente monolitico
Centralizzare non significa mettere ogni controllo in un unico servizio. Conviene centralizzare ciò che deve essere coerente — validazione delle credenziali, policy, rate limiting, formato degli eventi di audit — lasciando invece ai componenti di dominio le decisioni che dipendono dalla specifica risorsa o dal processo.
Lo stesso principio vale quando si progetta un'API Enterprise con ASP.NET Core: i confini e le responsabilità devono essere espliciti, non semplicemente spostati da un controller a un altro.
FAQ
Un Bearer token è sufficiente per proteggere un'API?
No. Il token risolve principalmente il problema dell'identità. Servono poi autorizzazione, eventuali controlli sulla risorsa, protezione dagli abusi e audit delle operazioni rilevanti.
Meglio ruoli o policy in ASP.NET Core?
I ruoli possono essere sufficienti per casi semplici. Quando i permessi diventano granulari o dipendono da claim e requisiti applicativi, le policy rendono le regole più esplicite, riutilizzabili e testabili.
Il rate limiting va applicato solo agli endpoint pubblici?
No. Anche client autenticati possono generare traffico eccessivo per bug, retry aggressivi o uso improprio. I limiti possono quindi essere differenziati per endpoint, client o tipologia di operazione.
Cosa conviene salvare nell'audit?
Identità del chiamante, tipo di evento, risorsa coinvolta, timestamp ed esito sono spesso un buon punto di partenza. I payload completi dovrebbero essere evitati salvo necessità specifiche e valutate.
Conclusione
La sicurezza di un'API non si esaurisce nella presenza di un Bearer token. Una progettazione robusta distingue identità, permessi, accesso alle risorse, protezione della capacità, vincoli di dominio e tracciabilità.
ASP.NET Core offre i building block per organizzare questi aspetti in componenti separati e testabili. Il vantaggio non è solo una maggiore sicurezza: è anche un'architettura più leggibile, in cui ogni rifiuto può essere spiegato e ogni regola può evolvere senza moltiplicare controlli ad hoc.
In sintesi
Un token valido identifica il chiamante. Non decide da solo cosa può fare.
Autenticazione, autorizzazione, accesso alle risorse, rate limiting e audit devono rimanere responsabilità distinte. In questo modo la sicurezza dell'API diventa più esplicita, testabile e manutenibile.
Guide correlate
Devi progettare o mettere in sicurezza un'API .NET esistente?
Posso supportare software house e team di sviluppo nella progettazione di API ASP.NET Core, autorizzazioni granulari, integrazioni con sistemi legacy, rate limiting, logging e audit applicativo.