Proteggi applicazioni basate sul cloud con OAuth 2.0 e OpenID Connect
Una guida completa per proteggere le tue applicazioni cloud con OAuth 2.0 e OpenID Connect e come offrire una grande esperienza utente con autenticazione e autorizzazione.
Introduzione
Le applicazioni basate sul cloud sono la tendenza al giorno d'oggi. Mentre il tipo di applicazione varia (web, mobile, desktop, ecc.), hanno tutte un backend cloud che fornisce servizi come storage, computing e database. La maggior parte delle volte, queste applicazioni necessitano di autenticare gli utenti e autorizzarli ad accedere a determinate risorse.
Mentre sono possibili meccanismi di autenticazione e autorizzazione fai-da-te, la sicurezza è diventata una delle principali preoccupazioni quando si sviluppano applicazioni cloud. Fortunatamente, la nostra industria ha standard collaudati come OAuth 2.0 e OpenID Connect per aiutarci ad implementare autenticazione e autorizzazione sicure.
Questo post ha le seguenti premesse:
- Hai una conoscenza di base dello sviluppo di applicazioni (web, mobile o di qualsiasi altro tipo).
- Hai sentito parlare dei concetti di autenticazione e autorizzazione.
- Hai sentito parlare di OAuth 2.0 e OpenID Connect.
Sì, "sentito" è sufficiente sia per 2 che per 3. Questo post utilizzerà esempi del mondo reale per spiegare i concetti e illustrare il processo con diagrammi. Iniziamo!
OAuth 2.0 vs. OpenID Connect
Se sei familiare con OAuth 2.0 e OpenID Connect, puoi anche continuare a leggere perché copriremo alcuni esempi del mondo reale in questa sezione; se sei nuovo a questi standard, è anche sicuro continuare poiché li introdurremo in modo semplice.
OAuth 2.0
OAuth 2.0 è un framework di autorizzazione che consente a un'applicazione di ottenere un accesso limitato a risorse protette su un'altra applicazione per conto di un utente o dell'applicazione stessa. La maggior parte dei servizi popolari come Google, Facebook e GitHub utilizzano OAuth 2.0 per il login sociale (ad esempio, "Accedi con Google").
Per esempio, hai un'applicazione web MyApp che vuole accedere al Google Drive dell'utente. Invece di chiedere all'utente di condividere le proprie credenziali Google Drive, MyApp può utilizzare OAuth 2.0 per richiedere l'accesso a Google Drive per conto dell'utente. Ecco un flusso semplificato:
In questo flusso, MyApp non vede mai le credenziali del Google Drive dell'utente. Invece, riceve un token di accesso da Google che gli consente di accedere a Google Drive per conto dell'utente.
In termini di OAuth 2.0, MyApp è il client, Google è sia il server di autorizzazione sia il server di risorse per semplicità. Nel mondo reale, spesso abbiamo server di autorizzazione e di risorse separati per offrire un'esperienza di single sign-on (SSO). Per esempio, Google è il server di autorizzazione e può avere diversi server di risorse come Google Drive, Gmail, e YouTube.
Nota che il flusso di autorizzazione effettivo è più complesso di questo. OAuth 2.0 ha diversi tipi di concessione, ambiti e altri concetti di cui dovresti essere a conoscenza. Mettiamolo da parte per ora e passiamo a OpenID Connect.
OpenID Connect (OIDC)
OAuth 2.0 è ottimo per l'autorizzazione, ma potresti notare che non ha un modo per identificare l'utente (cioè, autenticazione). OpenID Connect è uno strato di identità sopra OAuth 2.0 che aggiunge capacità di autenticazione.
Nell'esempio sopra, MyApp ha bisogno di sapere chi è l'utente prima di avviare il flusso di autorizzazione. Nota che ci sono due utenti coinvolti qui: l'utente di MyApp e l'utente di Google Drive. In questo caso, MyApp ha bisogno di conoscere l'utente della propria applicazione.
Vediamo un esempio diretto, assumendo che gli utenti possano accedere a MyApp usando nome utente e password:
Poiché stiamo autenticando l'utente della nostra propria applicazione, di solito non è necessario chiedere il permesso come ha fatto Google nel flusso OAuth 2.0. Nel frattempo, ci serve qualcosa che possa identificare l'utente. OpenID Connect introduce i concetti come token ID e endpoint userinfo per aiutarci.
Potresti notare che il provider di identità (IdP) è un nuovo partecipante autonomo nel flusso. È lo stesso del server di autorizzazione in OAuth 2.0, ma per maggiore chiarezza, usiamo il termine IdP per mostrare che è responsabile dell'autenticazione e gestione delle identità degli utenti.
Quando la tua azienda cresce, potresti avere più applicazioni che condividono lo stesso database utenti. Proprio come OAuth 2.0, OpenID Connect ti consente di avere un unico server di autorizzazione che può autenticare gli utenti per più applicazioni. Se l'utente è già connesso a un'applicazione, non ha bisogno di inserire nuovamente le proprie credenziali quando un'altra applicazione li reindirizza a IdP. Il flusso può essere completato automaticamente senza interazione dell'utente. Questo si chiama single sign-on (SSO).
Ancora una volta, questo è un flusso altamente semplificato e ci sono più dettagli "sotto il cofano". Per ora, passiamo alla sezione successiva per evitare un sovraccarico di informazioni.
Tipi di applicazione
Nella sezione precedente, abbiamo utilizzato applicazioni web come esempi mentre il mondo è più vario di così. Per un provider di identità, il linguaggio di programmazione esatto, il framework o la piattaforma che usi non importa molto. In pratica, una notevole differenza è se l'applicazione è un client pubblico o un client privato (affidabile):
- Client pubblico: Un client che non può mantenere riservate le proprie credenziali, il che significa che il proprietario della risorsa (utente) può accedervi. Per esempio, un'applicazione web che gira in un browser (es. applicazione a pagina singola).
- Client privato: Un client che ha la capacità di mantenere riservate le proprie credenziali senza esporle agli utenti (proprietari delle risorse). Per esempio, un'applicazione web che gira su un server (es. applicazione web lato server) o un servizio API.
Con questo in mente, vediamo come OAuth 2.0 e OpenID Connect possono essere utilizzati in diversi tipi di applicazione.
"Applicazione" e "client" possono essere usati in modo intercambiabile nel contesto di questo post.
Applicazioni web che girano su un server
L'applicazione gira su un server e serve pagine HTML agli utenti. Molti framework web popolari come Express.js, Django e Ruby on Rails rientrano in questa categoria; anche i framework backend-for-frontend (BFF) come Next.js e Nuxt.js sono inclusi. Queste applicazioni hanno le seguenti caratteristiche:
- Poiché un server consente solo accesso privato (non c'è modo per gli utenti pubblici di vedere il codice o le credenziali del server), è considerato un client privato.
- Il flusso generale di autenticazione degli utenti è lo stesso di quello discusso nella sezione "OpenID Connect".
- L'applicazione può utilizzare il token ID emesso dal provider di identità (cioè, il provider di OpenID Connect) per identificare l'utente e visualizzare contenuti specifici per l'utente.
- Per mantenere l'applicazione sicura, di solito si usa il flusso del codice di autorizzazione per l'autenticazione degli utenti e ottenere i token.
Nel frattempo, l'applicazione potrebbe dover accedere ad altri servizi API interni in un'architettura di microservizi; o potrebbe essere un'applicazione monolitica che necessita di controllo degli accessi per diverse parti dell'applicazione. Ne parleremo nella sezione "Proteggi la tua API".
Applicazioni a singola pagina (SPAs)
L'applicazione gira nel browser di un utente e comunica con il server tramite API. React, Angular e Vue.js sono framework popolari per costruire SPAs. Queste applicazioni hanno le seguenti caratteristiche:
- Poiché il codice dell'applicazione è visibile al pubblico, è considerato un client pubblico.
- Il flusso generale di autenticazione degli utenti è lo stesso di quello discusso nella sezione "OpenID Connect".
- L'applicazione può utilizzare il token ID emesso dal provider di identità (cioè, il provider di OpenID Connect) per identificare l'utente e visualizzare contenuti specifici per l'utente.
- Per mantenere l'applicazione sicura, di solito si usa il flusso del codice di autorizzazione con PKCE (Proof Key for Code Exchange) per l'autenticazione degli utenti e ottenere token.
Di solito, le SPAs devono accedere ad altri servizi API per il recupero e l'aggiornamento dei dati. Ne parleremo nella sezione "Proteggi la tua API".
Applicazioni mobili
L'applicazione gira su un dispositivo mobile (iOS, Android, ecc.) e comunica con il server tramite API. Nella maggior parte dei casi, queste applicazioni hanno le stesse caratteristiche delle SPAs.
Applicazioni machine-to-machine (M2M)
Le applicazioni machine-to-machine sono client che girano su un server (macchina) e comunicano con altri server. Queste applicazioni hanno le seguenti caratteristiche:
- Come le applicazioni web che girano su un server, le applicazioni M2M sono client privati.
- L'applicazione potrebbe non aver bisogno di identificare l'utente; invece, deve autenticarsi per accedere ad altri servizi.
- L'applicazione può utilizzare il token di accesso emesso dal provider di identità (cioè, il provider di OAuth 2.0) per accedere ad altri servizi.
- Per mantenere l'applicazione sicura, di solito si usa il flusso delle credenziali del client per ottenere token di accesso.
Quando si accede ad altri servizi, l'applicazione potrebbe dover fornire il token di accesso nell'intestazione della richiesta. Ne parleremo nella sezione "Proteggi la tua API".
Applicazioni in esecuzione su dispositivi IoT
L'applicazione gira su un dispositivo IoT (es. dispositivi domotici intelligenti, indossabili, ecc.) e comunica con il server tramite API. Queste applicazioni hanno le seguenti caratteristiche:
- A seconda delle capacità del dispositivo, può essere un client pubblico o privato.
- Il flusso generale di autenticazione può essere diverso da quello discusso nella sezione "OpenID Connect" a seconda delle capacità del dispositivo. Per esempio, alcuni dispositivi potrebbero non avere uno schermo per consentire agli utenti di inserire le proprie credenziali.
- Se il dispositivo non ha bisogno di identificare l'utente, potrebbe non essere necessario utilizzare token ID o endpoint userinfo; invece, può essere trattato come un'applicazione machine-to-machine (M2M).
- Per mantenere l'applicazione sicura, potrebbe usare il flusso del codice di autorizzazione con PKCE (Proof Key for Code Exchange) per l'autenticazione dell'utente e ottenere token o il flusso delle credenziali del client per ottenere token di accesso.
Quando comunica con il server, il dispositivo potrebbe dover fornire il token di accesso nell'intestazione della richiesta. Ne parleremo nella sezione "Proteggi la tua API".
Proteggi la tua API
Con OpenID Connect, è possibile identificare l'utente e recuperare dati specifici dell'utente tramite token ID o endpoint userinfo. Questo processo si chiama autenticazione. Ma probabilmente non vuoi esporre tutte le tue risorse a tutti gli utenti autenticati, per esempio, solo gli amministratori possono accedere alla pagina di gestione degli utenti.
Qui entra in gioco l'autorizzazione. Ricorda che OAuth 2.0 è un framework di autorizzazione, e OpenID Connect è uno strato di identità sopra OAuth 2.0; il che significa che puoi anche usare OAuth 2.0 quando OpenID Connect è già in atto.
Ricordiamo l'esempio che abbiamo utilizzato nella sezione "OAuth 2.0": MyApp vuole accedere al Google Drive dell'utente. Non è pratico permettere a MyApp di accedere a tutti i file dell'utente in Google Drive. Invece, MyApp dovrebbe dichiarare esplicitamente cosa vuole accedere (es. accesso solo in lettura ai file in una cartella specifica). In termini di OAuth 2.0, questo si chiama ambito.
Potresti vedere il termine "permesso" usato in modo intercambiabile con "ambito" nel contesto di OAuth 2.0 poiché a volte "ambito" è ambiguo per utenti non tecnici.
Quando l'utente concede l'accesso a MyApp, il server di autorizzazione emette un token di accesso con l'ambito richiesto. Il token di accesso viene quindi inviato al server di risorse (Google Drive) per accedere ai file dell'utente.
Naturalmente, possiamo sostituire Google Drive con i nostri servizi API. Per esempio, MyApp ha bisogno di accedere al OrderService per recuperare la cronologia degli ordini dell'utente. Questa volta, poiché l'autenticazione dell'utente viene già effettuata dal provider di identità e sia MyApp che OrderService sono sotto il nostro controllo, possiamo evitare di chiedere all'utente di concedere l'accesso; MyApp può inviare direttamente la richiesta a OrderService con il token di accesso emesso dal provider di identità.
Il token di accesso potrebbe contenere un read:order
ambito per indicare che l'utente può leggere la propria cronologia ordini.
Ora, supponiamo che l'utente inserisca accidentalmente un URL della pagina amministratore nel browser. Poiché l'utente non è un amministratore, non c'è l'ambito admin
nel token di accesso. OrderService rifiuterà la richiesta e restituirà un messaggio di errore.
In questo caso, OrderService potrebbe restituire un codice di stato 403 Forbidden per indicare che l'utente non è autorizzato ad accedere alla pagina amministratore.
Per le applicazioni machine-to-machine (M2M), nessun utente è coinvolto nel processo. Le applicazioni possono richiedere direttamente token di accesso dal provider di identità e utilizzarli per accedere ad altri servizi. Lo stesso concetto si applica: il token di accesso contiene gli ambiti necessari per accedere alle risorse.
Design dell'autorizzazione
Possiamo vedere due cose importanti da considerare quando si progetta l'autorizzazione per proteggere i tuoi servizi API:
- Ambiti: Definire quali risorse il client può accedere. Gli ambiti possono essere dettagliati (es.
read:order
,write:order
) o più generali (es.order
) a seconda delle tue esigenze. - Controllo degli accessi: Definire chi può avere ambiti specifici. Per esempio, solo gli amministratori possono avere l'ambito
admin
.
Per quanto riguarda il controllo degli accessi, alcuni approcci popolari sono:
- Controllo degli accessi basato sui ruoli (RBAC): Assegnare ruoli agli utenti e definire quali ruoli possono accedere a quali risorse. Per esempio, un ruolo di amministratore può accedere alla pagina amministratore.
- Controllo degli accessi basato su attributi (ABAC): Definire policy basate su attributi (es. dipartimento dell'utente, posizione, ecc.) e prendere decisioni di controllo degli accessi basate su questi attributi. Per esempio, un utente del dipartimento "Ingegneria" può accedere alla pagina ingegneria.
Vale la pena menzionare che per entrambi gli approcci, il modo standard per verificare il controllo degli accessi è controllare gli ambiti dei token di accesso, invece di ruoli o attributi. I ruoli e gli attributi possono essere molto dinamici e gli ambiti sono più statici, il che rende molto più facile gestirli.
Per informazioni dettagliate sul controllo degli accessi, puoi fare riferimento a RBAC e ABAC: I modelli di controllo degli accessi che dovresti conoscere.
Token di accesso
Sebbene abbiamo menzionato il termine "token di accesso" molte volte, non abbiamo discusso come ottenerne uno. In OAuth 2.0, un token di accesso viene emesso dal server di autorizzazione (provider di identità) dopo un flusso di autorizzazione riuscito.
Diamo un'occhiata più da vicino all'esempio di Google Drive e supponiamo di utilizzare il flusso del codice di autorizzazione:
Ci sono alcuni passaggi importanti nel flusso per l'emissione del token di accesso:
- Passaggio 2 (Reindirizza a Google): MyApp reindirizza l'utente a Google con una richiesta di autorizzazione. Tipicamente, questa richiesta include le seguenti informazioni:
- Quale client (MyApp) sta iniziando la richiesta
- Quali ambiti sta richiedendo MyApp
- Dove Google dovrebbe reindirizzare l'utente dopo che l'autorizzazione è completata
- Passaggio 5 (Chiedi il permesso di accedere a Google Drive): Google chiede all'utente di concedere l'accesso a MyApp. L'utente può scegliere di concedere o negare l'accesso. Questo passaggio si chiama consenso.
- Passaggio 7 (Reindirizza a MyApp con dati di autorizzazione): Questo passaggio è stato appena introdotto nel diagramma. Piuttosto che restituire il token di accesso direttamente, Google restituisce un codice di autorizzazione una tantum a MyApp per uno scambio più sicuro. Questo codice viene utilizzato per ottenere il token di accesso.
- Passaggio 8 (Usa il codice di autorizzazione per scambiare il token di accesso): Anche questo è un nuovo passaggio. MyApp invia il codice di autorizzazione a Google per scambiare il token di accesso. Come provider di identità, Google comporrà il contesto della richiesta e deciderà se emettere un token di accesso:
- Il client (MyApp) è chi dichiara di essere
- L'utente ha concesso l'accesso al client
- L'utente è chi dichiara di essere
- L'utente ha gli ambiti necessari
- Il codice di autorizzazione è valido e non scaduto
L'esempio sopra presuppone che il server di autorizzazione (provider di identità) e il server di risorse siano gli stessi (Google). Se sono separati, prendendo l'esempio di MyApp e OrderService, il flusso sarà simile a questo:
In questo flusso, il server di autorizzazione (IdP) emette sia un ID token sia un token di accesso a MyApp (passaggio 8). L'ID token viene utilizzato per identificare l'utente, e il token di accesso viene utilizzato per accedere ad altri servizi come OrderService. Poiché sia MyApp che OrderService sono servizi di prima parte, di solito non chiedono all'utente di concedere l'accesso; invece, si affidano al controllo degli accessi nel provider di identità per determinare se l'utente può accedere alle risorse (cioè se il token di accesso contiene gli ambiti necessari).
Infine, vediamo come il token di accesso viene utilizzato nelle applicazioni machine-to-machine (M2M). Poiché nessun utente è coinvolto nel processo e l'applicazione è affidabile, può richiedere direttamente un token di accesso dal provider di identità:
Il controllo degli accessi può ancora essere applicato qui. Per OrderService, non importa chi sia l'utente o quale applicazione sta richiedendo i dati; si interessa solo del token di accesso e degli ambiti che contiene.
Di solito, i token di accesso sono codificati come JSON Web Token (JWT). Per saperne di più sui JWT, puoi fare riferimento a Cos'è JSON Web Token (JWT)?.
Indicatori di risorse
OAuth 2.0 introduce il concetto di ambiti per il controllo degli accessi. Tuttavia, potresti rapidamente renderti conto che gli ambiti non sono sufficienti:
- OpenID Connect definisce un insieme di ambiti standard come
openid
,offline_access
eprofile
. Potrebbe essere confuso mescolare questi ambiti standard con i tuoi ambiti personalizzati. - Potresti avere più servizi API che condividono lo stesso nome di ambito ma hanno significati diversi.
Una soluzione comune è aggiungere suffissi (o prefissi) ai nomi degli ambiti per indicare la risorsa (servizio API). Per esempio, read:order
e read:product
sono più chiari di read
e read
. Un'estensione di OAuth 2.0 RFC 8707 introduce un nuovo parametro resource
per indicare il server di risorse a cui il client vuole accedere.
In realtà, i servizi API sono di solito definiti da URL, quindi è più naturale utilizzare gli URL come indicatori di risorse. Per esempio, l'API OrderService può essere rappresentata come:
Come puoi vedere, il parametro porta la comodità di utilizzare gli URL delle risorse effettive nelle richieste di autorizzazione e nei token di accesso. Vale la pena menzionare che l'RFC 8707 potrebbe non essere implementato da tutti i provider di identità. Dovresti verificare la documentazione del tuo provider di identità per vedere se supporta il parametro resource
(Logto lo supporta).
In sintesi, il parametro resource
può essere utilizzato nelle richieste di autorizzazione e nei token di accesso per indicare la risorsa a cui il client vuole accedere.
Non ci sono restrizioni sull'accessibilità degli indicatori di risorse, cioè l'indicatore di risorse non deve essere un URL reale che punta a un servizio API. Quindi il nome "indicatore di risorse" riflette adeguatamente il suo ruolo nel processo di autorizzazione. Puoi usare URL virtuali per rappresentare le risorse che vuoi proteggere. Per esempio, puoi definire un URL virtuale
https://api.example.com/admin
nella tua applicazione monolitica per rappresentare la risorsa che solo gli amministratori possono accedere.
Metti tutto insieme
A questo punto, abbiamo coperto le basi di OAuth 2.0 e OpenID Connect, e come usarli in diversi tipi di applicazione e scenari. Sebbene li abbiamo discussi separatamente, puoi combinarli secondo le tue esigenze aziendali. L'architettura generale può essere semplice come questa:
O un po' più complessa:
Man mano che la tua applicazione cresce, vedrai che il provider di identità (IdP) gioca un ruolo critico nell'architettura; ma non si riferisce direttamente ai tuoi obiettivi aziendali. Anche se è un'ottima idea affidarlo a un fornitore affidabile, dobbiamo scegliere il provider di identità con saggezza. Un buon provider di identità può semplificare notevolmente il processo, ridurre lo sforzo di sviluppo e salvarti da potenziali insidie di sicurezza.
Note di chiusura
Per le moderne applicazioni cloud, il provider di identità (o server di autorizzazione) è il luogo centrale per l'autenticazione, la gestione delle identità e il controllo degli accessi degli utenti. Anche se abbiamo discusso molti concetti in questo post, ci sono ancora molte sfumature da considerare quando si implementa un tale sistema. Se sei interessato ad approfondire, puoi consultare il nostro blog per articoli più dettagliati.