OAuth 2.0 è stato a lungo sinonimo di complessità: sei grant type diversi, tutorial spesso contraddittori, e sviluppatori che finivano per scegliere il flusso sbagliato e pubblicare applicazioni insicure. Nel 2026 questo scenario appartiene al passato. OAuth 2.1 ha fatto ciò che la community chiedeva da anni: ha eliminato i flussi pericolosi, ha reso PKCE obbligatorio su ogni grant di autorizzazione, e ha lasciato una specifica molto più facile da imparare e quasi impossibile da usare in modo scorretto.
Se state sviluppando con .NET 10, questo articolo copre tutto ciò che dovete sapere. Tre flussi. Cinque secondi per scegliere quello giusto. Partiamo.
Il problema con OAuth 2.0
OAuth 2.0 nacque con una buona intenzione: delegare l’autorizzazione senza condividere le credenziali. Ma la specifica era così flessibile da includere flussi profondi (come l’Implicit Flow per le SPA) che erano già problematici nel 2012 e sono diventati veri e propri anti-pattern con l’evoluzione del web. Il risultato? Anni di articoli in conflitto, sviluppatori confusi, e vulnerabilità di sicurezza difficili da rilevare in code review.
OAuth 2.1 risolve questo alla radice: mantiene quello che funziona, rimuove quello che è pericoloso, e consolida le best practice nel testo normativo stesso.
Flusso 1: Client Credentials — comunicazione machine-to-machine
Quando nessun utente umano è coinvolto nella comunicazione, si usa il flusso Client Credentials. Esempi tipici:
- Un job notturno che interroga un’API di reportistica
- Un microservizio di spedizione che notifica il microservizio di inventario
- Un worker in background che elabora una coda di messaggi
- Un’API interna che chiama un altro servizio interno
In questi scenari, è il servizio stesso ad essere l’identità — agisce per proprio conto, non per conto di un utente. Il flusso è diretto e senza reindirizzamenti browser:
- Il servizio invia le proprie credenziali al token service via HTTP POST
- Il token service verifica l’identità e restituisce un access token
- Il servizio usa il token per chiamare le API target
// .NET 10 — richiesta di un token Client Credentials con IdentityModel
var client = new HttpClient();
var response = await client.RequestClientCredentialsTokenAsync(
new ClientCredentialsTokenRequest
{
Address = "https://identity.example.com/connect/token",
ClientId = "service-a",
ClientSecret = "segreto-sicuro",
Scope = "api1.read api1.write"
});
var accessToken = response.AccessToken;
// Usa accessToken nell'Authorization header delle chiamate successive
OAuth 2.1 supporta tre meccanismi di autenticazione del client, in ordine crescente di sicurezza:
- Client secret:
client_ideclient_secretnell’header Basic o nel body — semplice ma richiede una buona gestione dei segreti - private_key_jwt: il client firma un JWT con la propria chiave privata; il token service valida la firma con la chiave pubblica registrata
- Mutual TLS (mTLS): autenticazione al livello di trasporto con certificati X.509 — massima sicurezza per ambienti ad alto rischio
Flusso 2: Authorization Code + PKCE — applicazioni con utente
Se un essere umano deve autenticarsi, questa è la risposta universale. Che si tratti di un’app Razor Pages server-side, un’app mobile nativa, un’applicazione desktop o una SPA dietro un Backend-for-Frontend, Authorization Code con PKCE è il flusso corretto in OAuth 2.1 — senza eccezioni.
Come funziona
- L’applicazione reindirizza l’utente all’authorization endpoint del provider di identità
- L’utente si autentica (password, MFA, policy aziendali)
- Il provider reindirizza l’utente all’applicazione con un authorization code di breve durata
- L’applicazione scambia il codice per i token tramite una chiamata back-channel diretta
Le credenziali dell’utente non toccano mai l’applicazione. I token non transitano mai attraverso la barra degli indirizzi del browser.
PKCE: protezione contro l’intercettazione del codice
PKCE (Proof Key for Code Exchange, pronunciato “pixie”) aggiunge uno strato critico di protezione all’exchange del codice. Prima di avviare il flusso, l’applicazione:
- Genera una stringa casuale (
code_verifier) - Calcola il suo hash SHA-256 (
code_challenge) - Invia il
code_challengenella richiesta di autorizzazione
Quando poi scambia il codice per i token, invia il code_verifier originale. Il token service verifica che l’hash corrisponda alla challenge registrata. Un attaccante che intercetta l’authorization code — attraverso un’app malevola sullo stesso custom URI scheme, un redirect compromesso, o qualsiasi altro vettore — non può usarlo senza il code_verifier. Il codice è inutile senza di esso.
// .NET 10 — configurazione OIDC con Authorization Code + PKCE
builder.Services
.AddAuthentication(options =>
{
options.DefaultScheme = "cookie";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("cookie")
.AddOpenIdConnect("oidc", options =>
{
options.Authority = "https://identity.example.com";
options.ClientId = "web-app";
options.ClientSecret = "segreto-sicuro";
options.ResponseType = "code"; // Authorization Code Flow
options.UsePkce = true; // PKCE (abilitato di default in .NET)
options.SaveTokens = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("api1.read");
});
Nota importante sulle SPA: le best practice correnti raccomandano di non esporre token al codice JavaScript lato client. Le SPA dovrebbero usare il pattern Backend-for-Frontend (BFF), dove è il server a gestire il flusso OIDC e a esporre solo cookie di sessione al browser.
Flusso 3: Device Authorization — dispositivi senza browser
Alcuni dispositivi non hanno un browser o una tastiera utilizzabile: smart TV, console di gioco, sensori IoT, strumenti CLI in ambienti headless. Non si può reindirizzare un utente a una pagina di login che non esiste.
Il flusso Device Authorization (RFC 8628) risolve questo con un pattern disaccoppiato:
- Il dispositivo richiede un codice utente e un URL di verifica al token service
- Il dispositivo mostra all’utente qualcosa come: “Vai su https://login.example.com/device e inserisci il codice: ABCD-1234”
- L’utente prende il proprio telefono o laptop, naviga all’URL, inserisce il codice e si autentica normalmente
- Nel frattempo, il dispositivo fa polling al token endpoint a intervalli regolari
- Quando l’utente completa l’autenticazione, il dispositivo riceve l’access token
È semplice, sicuro, e non richiede al dispositivo vincolato di rendere un’interfaccia di login.
L’albero decisionale di OAuth 2.1
Scegliere il flusso corretto richiede esattamente due domande:
- È coinvolto un utente umano?No → Client Credentials
- Sì → vai al punto 2
- Il dispositivo ha un browser?Sì → Authorization Code + PKCE
- No → Device Authorization
Questo è l’intero albero decisionale. Niente eccezioni. Niente casi speciali (a parte scenari legacy di migrazione).
Cosa ha rimosso OAuth 2.1 e perché
Tre flussi di OAuth 2.0 sono stati eliminati dallo standard. Non è necessario impararli per le nuove applicazioni, ma capire perché sono stati rimossi aiuta a riconoscerli se ci si imbatte in codice datato:
- Implicit Flow: era nato per le SPA in un’epoca in cui i browser non supportavano chiamate cross-origin POST. Restituiva i token direttamente nel fragment dell’URL, rendendoli visibili nella history del browser, nelle intestazioni referer e nei log del server. Con il supporto universale di CORS, la sua ragion d’essere è svanita.
- Resource Owner Password Credentials (ROPC): chiedeva agli utenti di digitare username e password direttamente nell’applicazione client — vanificando l’intero scopo di OAuth. Non supportava MFA o login federato, e abituava gli utenti a consegnare le proprie credenziali ad app che non avrebbero dovuto averle.
- Authorization Code senza PKCE: funzionava sulle app server-side, ma su piattaforme mobile più applicazioni possono registrarsi sullo stesso URI scheme personalizzato. Un’app malevola poteva intercettare l’authorization code e scambiarlo per token. Con PKCE obbligatorio, il codice intercettato diventa inutile.
Conclusioni
OAuth 2.1 è il protocollo di autorizzazione che avremmo voluto avere dal principio: tre flussi chiari, PKCE obbligatorio, nessuna ambiguità nella scelta. Per chi sviluppa in .NET 10, l’ecosistema è già allineato — le librerie come Duende IdentityServer e IdentityModel implementano questi pattern nativamente. Il passo successivo è una revisione del codice esistente per identificare eventuali flussi legacy da migrare.
Fonte: OAuth 2.1 Made Simple: The Only Flows You Need — Khalid Abuhakmeh, Duende Software, 6 maggio 2026