Creazione di client OAuth 2 con Flutter

Il Blog di TeraNet

Semplifica lo sviluppo di client OAuth 2 con oauth2_client!

18 June 2020
Letto 2323 volte

In questo nuovo articolo vi presento la libreria oauth2_client. Nei precedenti articoli abbiamo introdotto lo standard OAuth 2 e sperimentato con mano il suo funzionamento, e abbiamo visto come per realizzare un'applicazione conforme alle specifiche OAuth sia necessario gestire diverse logiche più o meno complesse, tra le quali:

  • Implementazione del flusso di autorizzazione esposto dal provider
  • Implementazione delle logiche per gestire la scadenza o l'invalidamento dei token
  • Memorizzazione e persistenza "lato applicazione" dei token generati

oauth2_client è una libreria Flutter creata proprio con lo scopo di semplificare l'implementazione di applicazioni OAuth 2, automatizzando il processo di richiesta e aggiornamento degli Access Token.

La libreria oauth2_client mette a disposizione strumenti sia per l'interfacciamento con i principali provider, come Google, Facebook, LinkedIn, GitHub, sia per l'implementazione di client personalizzati. Inoltre fornisce metodi per l'archiviazione sicura e l'aggiornamento automatico dei token e per eseguire in modo trasparente richieste HTTP autenticate.

Supponiamo di voler implementare un'app che sfrutti le API GitHub per recuperare l'elenco dei repository dell'utente.
Prima di poter interagire con l'endpoint OAuth 2, dovremo creare una nuova app OAuth, dopodichè ci verranno forniti un clientId e un clientSecret. Infine, dovremo stabilire gli scope (cioè le "autorizzazioni") di cui avremo bisogno. Nel nostro caso utilizzeremo lo scope "repo", che permette l'accesso a tutti i repository (pubblici e privati) dell'utente.

Ok... Vediamo un po 'di codice!

import 'package:oauth2_client/github_oauth2_client.dart';
import 'package:oauth2_client/access_token_response.dart';
...
//Crea un'istanza del client GitHub
OAuth2Client client = GitHubOAuth2Client (
  //Deve corrispondere al campo Authorization Callback URL
  //impostato in fase di registrazione dell'applicazione
  redirectUri: 'my.custom.scheme://oauth2redirect',
  customUriScheme: 'my.custom.scheme'
);
//Richiede un Access Token con un grant "Authorization Code"
AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow (
  clientId: 'myclientid',
  clientSecret: 'myclientsecret',
  scopes: ['repo']);
//D'ora in avanti possono essere eseguite richieste HTTP autenticate utilizzando
//i metodi standard della classe http.Client e passando il token tra gli headers
httpClient = http.Client();
http.Response resp = await httpClient.get('https://api.github.com/user/repos',
  headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
//L'elenco dei repository dell'utente è codificato nella proprietà resp.body...

Generare un Access Token è quindi molto semplice, e poichè i token GitHub non scadono, non dovremo nemmeno preoccuparci di implementare il flusso di rinnovo!
Potrebbe però succedere che il token venga invalidato esplicitamente dal proprietario dell'app GitHub. Questo significa che dovremo comunque preoccuparci di verificare la validità del token dopo ogni richiesta e, nel caso in cui non fosse più valido, generare un nuovo token e rieseguire la richiesta.

Ad esempio:

...
httpClient = http.Client();
http.Response resp = await httpClient.get('https://api.github.com/user/repos',
	headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
if(resp.statusCode != 200){//La richiesta al servizio non è riuscita ...
	//Dobbiamo aggiornare il token (non supportato dall'endpoint GitHub) o
	AccessTokenResponse tknResp = await client.getTokenWithAuthCodeFlow(
		clientId: 'myclientid',
		clientSecret: 'myclientsecret',
		scopes: ['repo']);
	//Invio un'altra richiesta al server con il nuovo Access Token
	http.Response resp = await httpClient.get('https://api.github.com/user/repos',
		headers: {'Authorization': 'Bearer ' + tknResp.accessToken});
}

Fortunatamente, oauth2_client mette a disposizione un "helper" che si occupa di tutto il lavoro:

//Crea un'istanza del client ...
OAuth2Client client = GitHubOAuth2Client (
	redirectUri: 'my.app://oauth2redirect',
	customUriScheme: 'my.app');
//Istanzia l'helper passando il client e i parametri di autorizzazione
OAuth2Helper oauth2Helper = OAuth2Helper (client,
	grantType: OAuth2Helper.AUTHORIZATION_CODE, // valore predefinito, può essere omesso
	clientId: 'myclientid',
	clientSecret: 'myclientsecret',
	scopes: ['repo']);
http.Response resp = await oauth2Helper.get('https://api.github.com/user/repos');

Il metodo OAuth2Helper.get agisce sostanzialmente da wrapper dell'omonimo metodo nella classe standard http.Client, introducendo ulteriori funzionalità che automatizzano il processo di autorizzazione. Questo metodo esegue le seguenti operazioni:

  • Verifica se sia già stato generato in precedenza un Access Token
  • In caso negativo, genera l'Access Token utilizzando il flusso richiesto e lo memorizza nello storage protetto del dispositivo.
  • Invia la richiesta all'url specificato, aggiungendo l'header "Authorization" valorizzato con l'Access Token
  • Se il server restituisce una risposta "unauthorized", prova ad aggiornare l'Access Token tramite il flusso Refresh Token, se il servizio lo supporta, altrimenti genera un nuovo token. Infine, esegue una nuova richiesta con il nuovo token.

L'helper quindi non solo si occupa di recuperare e aggiornare i token, ma li memorizza anche in un archivio sicuro sul dispositivo. Ciò significa che se l'app viene chiusa o il dispositivo viene riavviato, i token saranno ancora disponibili e potranno essere utilizzati senza la necessità di un nuovo processo di autorizzazione.

Ok, ma se volessi implementare un client per un server custom?

L'implementazione di un client custom è davvero semplice e normalmente richiede solamente un paio di righe di codice!

Vediamo ad esempio come è implementato il client GitHub:

class GitHubOAuth2Client extends OAuth2Client {
  GitHubOAuth2Client(
      {@required String redirectUri, @required String customUriScheme})
      : super(
            authorizeUrl: 'https://github.com/login/oauth/authorize',
            tokenUrl: 'https://github.com/login/oauth/access_token',
            redirectUri: redirectUri,
            customUriScheme: customUriScheme){//Di default l'Access Token viene restituito con un formato "querystring",
    //ma noi abbiamo bisogno di una risposta in formato json,
    //che può essere richiesta con l'header "Accept"...
    this.accessTokenRequestHeaders = {'Accept': 'application/json'};
 }}

E questo è tutto! E' infatti sufficiente estendere la classe OAuth2Client e fornire gli URL di autorizzazione e di generazione del token, dopodichè sarà possibile utilizzare il client o anche l'helper come visto in precedenza, senza alcuna modifica.

In conclusione...

oauth2_client semplifica davvero il lavoro con gli endpoint OAuth2, poiché si occupa di tutte le peculiarità del protocollo e automatizza il processo di aggiornamento dei token. Inoltre implementa in modo trasparente molte delle "best practices" introdotte per rafforzare la sicurezza e l'affidabilità del processo.

Contatta il nostro team

Vuoi avere più informazioni sui software sviluppati da TeraNet? Non esitare a contattarci tramite la seguente form, il nostro team è pronto a rispondere a tutte le tue domande!

Attendere prego