REST Authentication & Authorization
Last updated
Last updated
Vediamo prima i metodi comuni di autenticazione e autorizzazione usati comunemente per applicazioni.
Tutti i sistemi di autenticazione si basano in qualche modo sul fatto che l'utente per essere riconosciuto presenti la sue credenziali: uno username che identifica l'utente e una password propria dell'utente e che deve essere mantenuta segreta.
Il primo sistema di autenticazione per servizi web è stato lo schema di autenticazione, Basic Authentication.
Quando l'utente tramite il browser vuole accedere a una pagina (risorsa) che il server non vuole mantenere accessibile a tutti, ma vuole che sia accedibile solo ad alcuni, viene presentata dal browser una form per immettere le credenziali.
Il server quando vuole che il client si autentichi, alla richiesta del client, risponde con return code della response: HTTP 401 Unauthorized
e header:WWW-Authenticate: Basic realm="User Visible Realm"
o se include il parametro charset: WWW-Authenticate: Basic realm="User Visible Realm", charset="UTF-8"
Quando lo user agent (il browser) vuole mandare le credenziali di autenticazione al server, utilizza un campo Authorization
nell'header.
Il campo Authorization
è costruito nel seguente modo:
lo username e la password sono concatenati tramite il carattere :
quindi lo username non può contenere quel carattere;
la stringa risultante è encodata in una sequenza di ottetti. Il set di caratteri da utilizzare è di default non specificato, basta che sia compatibile con US-ASCII
, ma il server potrebbe suggerire di utilizzare UTF-8
inviando il parametro charset
La stringa risultante è encodata utilizzando una variante di Base64
L'authorization method e lo spazio (esempio 'Basic') è preposto alla stringa encodata.
Per esempio, se il browser utilizza Aladin come username e OpenSesame come password, allora il valore del campo è l'encoding base-64 di Aladin:OpenSesame, quindi QWxhZGRpbjpPcGVuU2VzYW1l. Così sarebbe l'Authorization
header:
Authorization: Basic QWxhZGRpbjpPcGVuU2VzYW1l
Nella figura sotto il flusso dell'interazione client (il browser) e server.
L'unico modo sicuro con questo schema di autenticazione è di usare connessione HTTPS criptata.
Svantaggi:
basso grado di sicurezza: username e password vengono inviate al server praticamente in chiaro. Da una stringa encodata in Base64 è semplice ritornare alla stringa originale;
Il sito web non ha il controllo sull'interfaccia presentata all'utente;
Per approfondimento cos'è lo schema di encoding Base64:
Esempio di Basic Authentication:
Esempio di configurazione lato server, per web server Apache e Nginx:
Digest access authentication è stata inizialmente specificata dal RFC 2069 (An Extension to HTTP: Digest Access Authentication). Viene specificato uno schema tipico di autenticazione di tipo digest con la sicurezza mantenuta dal server tramite la generazione di un nounce value (numero). L' authentication response è così formata (HA1 e HA2 sono nomi di variabili di tipo string):
Un hash MD5 è un valore di 16 byte. I valori di H1 e H2 utilizzati per calcolare la risposta sono la rappresentazione esadecimale (in lowercase) dei valori hash MD5.
MD5 è una tipo di algoritmo di tipo hash. Le funzioni di hash sono dette 'one way' nel senso che deve essere difficile determinare l'input della funzione solo dalla conoscenza dell'output.
L' RFC 2069 è stato successivamente rimpiazzato da RFC 2617 (HTTP Authentication: Basic and Digest Access Authentication). Sono stati introdotti una serie di miglioramenti alla sicurezza opzionali alla digest authentication; "quality of protection" (qop), un contatore nounce incrementato dal client, e un random nounce generato dal client.
If the algorithm directive's value is "MD5" or unspecified, then HA1 is
If the algorithm directive's value is "MD5-sess", then HA1 is
If the qop
directive's value is "auth" or is unspecified, then HA2 is
If the qop
directive's value is "auth-int", then HA2 is
If the qop
directive's value is "auth" or "auth-int", then compute the response as follows:
If the qop
directive is unspecified, then compute the response as follows:
Vediamo un esempio di flusso di interazione tra client e server:
La classica, ad esempio quando vi collegate al sito di Amazon, c'è una form con il bottone di submit per autenticarvi.
Esempio parte del codice HTML per creare la form per inserire le credenziali di autenticazione:
Il client esegue una HTTP POST con i parametri username e password encodati nel body della request.
Il sito web ha il controlla della form di autenticazione presentata all'utente. E' il sistema attualmente più comune per autenticarsi.
Lo username e la password passano in chiaro all'interno del body della POST request; l'unico modo per impedire a terzi di rubare le credenziali è l'utilizzo di una connessione sicura di tipo HTTPS.
Utilizzo di cookie o un session id per gestire la sessione tra l'utente e il server tra la diverse richieste essendo il protocollo HTTP/HTTPS stateless.
Più utilizzate da servizi REST per l'autenticazione e autorizzazione è JWT.
JWT - JSON Web Token (RFC 7519) Autenticazione basata su token firmati lato server.
Il token è generato dal server al momento dell’autenticazione
Il token contiene non solo un identificativo del client, ma tutte le informazioni che servono alla business logic (ad esempio il ruolo dell’utente)
Il token è firmato dal server e non può quindi essere manomesso
Il token viene inviato da client nell'header della request:Authorization: Bearer <token>
JSON Web Tokens consiste di tre parti che sono separate dal carattere punto ( .
), che sono:
Header
Payload
Signature
Quindi, un JWT tipicamente appare così:
xxxxx.yyyyy.zzzzz
vediamo singolarmente le differenti parti
Header
L'header in genere consiste in due parti: il tipo di token, che è JWT, e l'algoritmo di hashing come HMAC SHA256 o RSA.
Esempio header:
Payload
La seconda parte del token è il payload, che contiene le claim (affermazioni). Le Cliam sono statement su un'entità (tipicamente l'utente) e metadati addizionali. Ci sono tre tipi di claim: reserved, public e private.
reserved claims: sono un set di claim predefiniti, che non sono obbligatori ma raccomandati, pensati per produrre un set di claim utili e interoperabili. Alcuni di questi sono: iss (issuer), exp (expiration time), sub (subject), aud (audience) tra gli altri.
public claims: puoi registrare i public claim a IANA "JSON Web Token Claims" registry e si dovrebbe fare attenzione che i public claim siano 'collision-resistant' cioè che sia altamente improbabile che collidano con altri nomi. Esempi sono UUID, OID o nomi di dominio.
private claims: questi sono custom claims creati per scambiare informazioni tra le parti.
Esempio payload:
Signature
La creazione della signature ad esempio utilizzando l'algoritmo HMAC SHA256, sarà:
Mettendo tutto assieme:
Se si vuole provare JWT e mettere questi concetti in pratica, si può jwt.io Debugger utilizzare per decodificare, verificare e generate dei JWT.
Dalle specifiche del protocollo OAuth 1 e 2:
Many luxury cars come with a valet key. It is a special key you give the parking attendant and unlike your regular key, will only allow the car to be driven a short distance while blocking access to the trunk and the onboard cell phone. Regardless of the restrictions the valet key imposes, the idea is very clever. You give someone limited access to your car with a special key, while using another key to unlock everything else.
As the web grows, more and more sites rely on distributed services and cloud computing: a photo lab printing your Flickr photos, a social network using your Google address book to look for friends, or a third-party application utilizing APIs from multiple services.
The problem is, in order for these applications to access user data on other sites, they ask for usernames and passwords. Not only does this require exposing user passwords to someone else — often the same passwords used for online banking and other sites — it also provide these application unlimited access to do as they wish. They can do anything, including changing the passwords and lock users out.
OAuth provides a method for users to grant third-party access to their resources without sharing their passwords. It also provides a way to grant limited access (in scope, duration, etc.).
For example, a web user (resource owner) can grant a printing service (client) access to her private photos stored at a photo sharing service (server), without sharing her username and password with the printing service. Instead, she authenticates directly with the photo sharing service which issues the printing service delegation-specific credentials.
RFC 5849 The OAuth 1.0 Protocol e RFC 6749 The OAuth 2.0 Authorization Framework.
E' un authorization framework.
I componenti con cui abbiamo a che fare:
Il resource owner ha accesso alle API e può delegare l'accesso;
La risorsa protetta (protected resource) è la risorsa a cui il resource owner ha accesso;
il client è il componente software che accede alla protected resource al posto del resource owner.In OAuth, il client è qualunque componente che consuma delle API che mascherano un risorsa protetta.
Lo scopo di OAuth è permettere al client di accedere a una risorsa al posto del resource owner.
Copiare le credenziali dell'utente e ritrasmetterle a un altro servizio.
Un approccio comune, per le applicazioni enterprise, è copiare le credenziali dell'utente, e reinviarle a un altro servizio. Nel caso di un servizio di stampaggio foto, si assume che l'utente utilizzi le stesse credenziali per il servizio di stampaggio di quelle utilizzate per il servizio di storage delle foto. Quando l'utente si autentica al servizio stampaggio, questo servizio reinvia lo username e la password dell'utente al servizio di storage, per acquisire l'accesso al servizio, come se fosse l'utente.
In questo scenario, l'utente si deve autenticare all'applicazione client utilizzando qualche tipo di credenziali, in genere qualcosa che è centralmente controllata e accettata sia per il client che per la risorsa protetta.
Se il servizio di stampaggio è offerto dalla stessa azienda che provvede il servizio di storage, questa tecnica può funzionare se l'utente ha le stesse credenziali su entrambi i servizi.
Il client in questo modo impersonifica l'utente, e la risorsa protetta non ha modo di accorgersi della differenza tra il resource owner e il client che impersonifica perché entrambi stanno usando le stesse username e password.
Ma cosa fare se i due servizi occupano due differenti security domain, come nello scenario del nostro servizio di stampaggio delle foto di esempio? Non possiamo copiare le password che l'utente ci da per autenticarsi all'interno dell'applicazione, perché non funzionerebbero con il sito remoto. L'unico modo è chiederle all'utente.
Se il servizio di stampaggio vuole accedere al servizio di storage delle foto, esso può richiedere all'utente per le sue username e password sul servizio di photo-storage. Così come aveva fatto nel caso precedente, il servizio di stampaggio reinvia queste credenziali alla risorsa protetta e impersonifica così l'utente. In questo scenario, le credenziali che l'utente utilizza per autenticarsi al client possono essere diverse di quelle per autenticarsi alla risorsa protetta.
Questo rimane uno degli approcci più comuni per le applicazioni mobile per accedere a servizi di back end: l'applicazione mobile richiede all'utente per le credenziali e poi le reinvia all'applicazione di back end. Il fatto che il client immagazzini la password diventa molto pericoloso. Basta che un'applicazione client sia compromessa per accedere a tutte le password degli utenti di back end.
In entrambi questi approcci il client sta impersonificando il resource owner, e la risorsa protetta non ha modo di distinguere una chiamata diretta dal resource owner da una chiamata diretta dal client. Perché questo non è desiderabile?
Consideriamo il nostro printing service, consideriamo che non vogliamo che il printing service possa cancellare o fare l'upload di foto dallo storage service. Si vuole che il print service possa solo leggere le foto che si vuole stampare, e si vuole poter revocare questa possibilità quando si vuole. Tutto questo con la condivisione delle password non è possibile:
si danno gli stessi diritti al client del resource owner sulla risorsa protetta;
non è possibile revocare l'accesso se non cambiano la password del resource owner sulla risorsa protetta;
bisogna dare troppo credito al client, se fosse fatto in modo malevolo per prendervi le password che molti utenti poi utilizzano uguali per molti servizi?
Si vede quindi che questa soluzione non funziona molto bene, ha molti lati negativi.
Altro modo è l'utilizzo di una special key, cioè non utilizzare il reinvio delle password del resource owner.
In questo approccio, la developer key agisce come una chiave universale che permette al client di impersonificare ogni utente che vuole, probabilmente tramite un parametro dell'API. Questo ha il vantaggio di non esporre le credenziali dell'utente al client, ma la costo di dare al client delle credenziali molto potenti.
Un altro possibile approccio è di dare una password speciale che è utile solo per condividere con servizi di terze parti.
Possiamo fare meglio di così?
Se si riuscisse ad avere questo tipo di credenziali limitate, distribuite separatamente per ogni cliente e ogni combinazione di utenti, per essere usate per accedere a una risorsa protetta? Potremmo attaccare dei limitati diritti a queste limitate credenziali. Se ci fosse un protocollo che permettesse la generazione di questa credenziali in un modo che è sia user-friendly che scalabile? Ecco a cosa serve il protocollo OAuth.
OAuth è un protocollo proprio disegnato per questo: in OAuth, l'utente finale dalega una parte dei suoi diritti per accedere a una risorsa protetta a una applicazione client per agire al suo posto. Per fare che questo possa avvenire, OAuth introduce un altro componente all'interno del sistema: l'authorization server.
L' authorization server è autorizzato dalla risorsa protetta di distribuire security credentials - chiamate OAuth access token - ai client.
L' Authorization server automatizza il processo di distribuzione di password specifiche per il servizio.
Sotto il processo OAuth ad alto livello.
In nessun momento di questo processo ci sono le credenziali del resource owner esposte al client: il resource owner si autentica sull'autentication server separatamente.
Neppure il client ha una developer-key: il client non è in grado di accedere a nulla da se stesso e invece deve essere autorizzato da un valido resource owner prima per accedere a una risorsa protetta.
L' utente generalmente non deve vedere o avere a che fare con l'access token direttamente. Invece di richiedere all'utente di generare i token e copiarli dentro i client, il protocollo OAuth facilita questo processo e rende relativamente semplice per un client ottenere un token e per l'utente autorizzare il client.
OAuth fu concepito dall'inizio per l'utilizzo con le API, dove la maggior parte dell'interazione è fuori dal browser.
OAuth è un protocollo di sicurezza complesso, con differenti componenti che si scambiano pezzi di informazione in una modalità precisa che può essere descritta come una danza. Ma fondamentalmente ci sono due step in una transazione OAuth: emettere il token e utilizzare il token. Il token rappresenta l'accesso che è stato delegato al client.
Sebbene i dettagli di ogni step possono variare in base a diversi fattori, una transazione OAuth canonica consiste nella seguente sequenza di eventi:
Il Resource Owner indica al client che vorrebbe che il Client agisse al suo posto (per esempio, "Carica le mie foto da quel servizio così che io le possa stampare");
Il Client richiede l'autorizzazione dal Resource Owner all'Authorization Server;
Il Resource Owner concede l'autorizzazione al Client;
Il Client riceve un Token dall' Authorization Server;
Il Client presenta il Token alla Protected Resource;
Differenti installazioni del processo OAuth possono gestire ognuno di questi step in un modo leggermente diverso, spesso ottimizzando il processo riunendo qualche step in una singola azione, ma la sostanza del processo rimane identica.
L'authorization code grant utilizza una credenziale temporanea, l'authorization code, per rappresentare la delega del resource owner al client.
Quando il client realizza che deve ottenete un nuovo OAuth access token, esso invia il resource owner all'authentication server con una richiesta che indica che il client sta chiedendo di essere delegato di qualche autorità dal resource owner.
Per esempio, il nostro servizio di stampa foto può chiedere a un servizio di storage delle foto la possibilità di leggere le foto lì conservate.
Siccome abbiamo un web client, questo prende la forma di un HTTP redirect a un endpoint di un authorization server.
La risposta di una client application è di questo tipo:
Notiamo nella request: Location: http://localhost:9001/authorize?response_type=code&scope=foo&client _id=oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000%2Fcallback& state=Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
Nella figura sopra, l'invio del resoure owner all'authorization server per iniziare il processo.
Questa redirect causa che il browser invii una GET HTTP
all'authorization server.
Da notare: GET /authorize?response_type=code&scope=foo&client_id=oauth-client-1&redirect_uri=http%3A%2F%2Flocalhost%3A9000% 2Fcallback&state=Lwt50DDQKUB8U7jtfLQCVGDL9cnmwHH1
In particolare il client si identifica con un client_id.
L'authorization server può parsare questi parametri a agire di conseguenza, anche se il client non sta facendo le richieste direttamente.
Ora, l'authorization server in genere richiede al client di autenticarsi. Questo step è essenziale nel determinare chi è il resource owner e quali diritti vuole delegare (concedere) al client:
L'autenticazione dell'utente avviene tra l'utente (attraverso il browser) e l'authorization server; non ha nulla a che vedere con il client. Questo è un aspetto importante perché protegge l'utente dal condividere le credenziali con il client (è proprio quello che volevamo impedire).
Questo approccio inoltre isola il client da cambiamenti nel modo di autenticazione dell'utente.
Prossimo passo, l'utente autorizza l'applicazione client. In questo step, il resource owner sceglie di delegare qualche parte dei propri permessi all'applicazione client. La richiesta del client potrebbe includere un indicazione di quale tipo di accesso vorrebbe.
Inoltre, molti authorization server conservano queste decisioni sull'autorizzazione per uso futuro. In questo caso, future richieste per lo stesso accesso da parte dello stesso client non chiederanno ancora all'utente. L'utente verrà ancora rediretto all'authorization endpoint, e dovrà ancora loggarsi, ma la decisione di delegare l'autorità al client, è già stata presa durante precedenti azioni.
Ora, l'authorization server ridirige l'utente indietro all'applicazione client.
Prende la forma di una HTTP redirect al client redirect_uri
.
Da notare: Location: http://localhost:9000/callback?code=8V1pr0rJ&state=Lwt50DDQKU B8U7jtfLQCVGDL9cnmwHH1
Questo causa che il browser invii la seguente richiesta indietro al client:
Da notare che questa richiesta HTTP è al client e non all'authorization server.
Siccome stiamo utilizzando l'authorization code grant type, la redirect include lo speciale code
query parameter. Il valore di questo parametro è di utilizzo una sola volta, conosciuto anche come authorization code, e esso rappresenta il risultato delle authorization decision dell'utente. Il client può parsare questo parametro per prendere il valore dell'authorization code all'arrivo della richiesta, e utilizzerà questo codice nel prossimo step. Il client inoltre controllerà il valore del parametro state
di ritorno, che sia identico al valore inviato.
Ora che il client possiede questo code
, lo può reinviare all'authorization server al token endpoint.
Il client esegue una HTTP POST con i parametri form-encoded nel body della request, passando il suo client_id
e client_secret
come HTTP Basic authorization header. Questa HTTP request è fatta direttamente tra il client e l'authorization server, senza implicare il browser o il resource owner.
La separazione tra differenti HTTP connection assicura che il client può autenticare se stesso direttamente senza che altri componenti siano capaci di vedere e manipolare la richiesta del token.
Il client riceve l'access token:
Il client può ora parsare la risposta e prendere l'access token in modo da proteggere le risorse. In questo caso, noi abbiamo un OAuth Bearer token, come indicato dal token_type
campo della risposta.
La risposta potrebbe anche includere un refresh token
(utilizzata per ottenere un nuovo access token senza chiedere l'autorizzazione di nuovo).
Il client può immagazzinare questo access token in qualche posto sicuro.
Utilizzo dell'access_token
da parte del client, ad esempio:
La risorsa protetta può estrarre il token dall'header, determinare se è ancora valido, recuperare l'informazione riguardo chi ha autorizzato e per che cosa ha autorizzato, e ritornare una risposta di conseguenza.
Il modo più semplice è che il resource server e l'authorization server condividano un database che contenga le informazioni del token. L'authorization server scrive i token nel database quando vengono generati, e il resource server legge i token dal database quando gli sono presentati.
Un OAuth refresh token è simile come concetto ad un access token, infatti è rilasciato al client dall'authorization server. Cosa è differente, tuttavia, è che il token non è mai inviato alla risorsa protetta. Invece il client utilizza il refresh token per chiedere un nuovo access token.
Playground di Google per vedere flusso di autorizzazione OAuth:
Libro su OAuth: "OAuth 2.0 in Action" di Justin Richer & Antonio Sanso, Mannings editore.