Consommer des API protégées par OAuth2 dans Xamarin.Forms


OAuth 2.0, successeur du protocole OAuth 1.0a, est un protocole d’autorisation permettant à une application tierce d’accéder à un service web de manière sécurisée.

Largement utilisé dans le domaine du web avec notamment Facebook ou encore Google, OAuth est devenu incontournable.

En tant que développeur, nous pouvons être amenés à utiliser un serveur fournissant un accès via OAuth 2.0 ou à implémenter un serveur d’autorisation pour sécuriser une API en utilisant ce protocole.

Il ne s’agit pas ici, de vous détailler tout le fonctionnement de OAuth2 mais de vous montrer comment mettre en place un service en .NET permettant d’accéder à des API sécurisée par celui-ci.

Il est important de comprendre certaines notions essentielles dans l’implémentation ou l’utilisation de OAuth2 telles que:

  • les rôles;
  • Les différentes scénarios d’autorisation.
  • Les Tokens.

Le schéma classique d’un processus d’authentification et d’autorisation fait intervenir deux parties :

  • un serveur en mesure d’authentifier et d’autoriser l’accès à une ressource ;
  • un utilisateur qui fournit ses identifiants pour accéder à la ressource.

Les rôles de OAuth2

OAuth2 distingue quatre rôles :

  • Resource Owner (ou : user, utilisateur) : une entité qui accorde à un client l’accès à ses données protégées (ou : ressources).
  • Resource Server (ou : service) : un serveur sur lequel sont enregistrées les données protégées du Resource Owner.
  • Client (ou : tiers, Third-Party) : une application de bureau, Web ou mobile souhaitant accéder aux données protégées du Resource Owner.
  • Authorization Server : un serveur qui authentifie le Resource Owner et génère un jeton d’accès d’une durée limitée pour un domaine d’application (scope) qu’il a défini. L’Authorization Server et le Resource Server sont en pratique souvent exploités conjointement, auquel cas ils sont nommés serveurs OAuth.

Les différentes scénarios d’autorisation

On opère par ailleurs une distinction entre quatre processus d’autorisation prédéfinis (Grant Types) utilisés dans différents cas d’application :

  • Code d’autorisation (Authorization Code) : le Client demande au Resource Owner de se connecter auprès d’un Authorization Server. Le Resource Owner est ensuite redirigé vers le Client avec un code d’autorisation. En échange de ce code, l’Authorization Server génère un jeton d’accès pour le Client.
  • Autorisation implicite (Implicit Authorization) : ce processus d’autorisation est fortement similaire à l’autorisation via un code d’autorisation, mais est moins complexe puisque l’Authorization Server génère directement le jeton d’accès.
  • Déblocage du mot de passe par le Resource Owner (Resource Owner Password Credentials) : dans ce cadre, le Resource Owner confie directement au Client ses données d’accès, ce qui va certes à l’encontre du principe de base de OAuth, mais permet un effort moindre pour le Resource Owner.
  • Autorisation Client (Client Credentials) : ce processus d’autorisation particulièrement simple est utilisé lorsque le Client souhaite accéder à des données n’ayant pas de propriétaire ou pour lesquelles aucune autorisation n’est nécessaire.

Les Tokens

La demande d’accès à une ressource protégée via OAuth se traduit par la délivrance d’un token au client. Le token représente juste une chaîne de caractère unique permettant d’identifier le client et les différentes informations utiles durant le processus d’autorisation.

Le serveur d’autorisation est en mesure d’en fournir deux types.

Token d’accès : Access token

Le token d’accès permet au client d’accéder à la ressource protégée. Ce token a une durée de validité limitée et peut avoir une portée limitée.

Cette notion de portée permet d’accorder un accès limité au client. Ainsi, un utilisateur peut autoriser un client à accéder à ses ressources qu’en lecture seule.

Token de rafraîchissement : Refresh token

Le token de rafraîchissement permet au client d’obtenir un nouveau token d’accès une fois que celui-ci a expiré. Sa durée de validité est aussi limitée mais est beaucoup plus élevée que celle du token d’accès.

Son utilisation permet au client d’obtenir un nouveau token d’accès sans l’intervention du propriétaire de la ressource protégée.

Pour plus de détails théorique sur OAuth2, je vous invite à lire cet article sur zestedesavoir.com puis celui-ci sur ionos.fr

Pratique

Dans l’implémentation que nous effectuerons dans ce article, nous utiliserons le scénario d’autorisation Client Credentials. L’idée par exemple est d’envoyer les données des opérations bancaires effectuées par les clients vers une API sécurisée par OAuth2.

De quoi avons-nous besoin vis à vis de la Banque ?

  1. Les paramètres pour demander un token nous permettant d’envoyer les transactions bancaires:
  • Grant Type: C’est le scénario d’autorisation; ici, le Client Credentials.
  • Access Token URL: L’URL du serveur d’autorisation permettant d’obtenir un token d’accès.
  • Client ID: L’ID du client. On peut considérer ceci comme un nom d’utilisateur.
  • Client Secret: Mot de passe du client.
  • Scope: Les privilèges accordés au client.

  1. L’API(la ressource) recevant la liste des opérations bancaires. Techniquement, il s’agit de l’URL d’accès. En paramètre de la requête d’envoie des transactions, nous mettrons le token obtenu en (1).

A nos claviers maintenant 👨‍💻

Dans un projet Xamarin.Forms, intégrons RestSharp et Newtonsoft.Json pour le, processus d’authentification et de réception des données.

Model de réception du token

namespace BankApp.Models
{
    /// <summary>
    /// The implementation of the authentication response from the Bank.
    /// </summary>
    public class AuthenticationResponse
    {
        /// <summary>
        /// The access token.
        /// </summary>
        public string access_token { get; set; }

        /// <summary>
        /// The type of the token.
        /// </summary>
        public string token_type { get; set; }

        /// <summary>
        /// The token expiry in seconds.
        /// </summary>
        public int expires_in { get; set; }
    }
}

Service permettant la réception du token

using Newtonsoft.Json.Linq;
using BankApp.Models;
using RestSharp;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using static BankApp.Constants.APIResource;

namespace BankApp.Services
{
    public class BankAppAPIService
    {
        /// <summary>
        /// Defines a RestClient object.
        /// </summary>
        private static readonly RestClient client = new RestClient();

        /// <summary>
        /// Method to generate Oauth2 Token from the Bank API.
        /// </summary>
        /// <returns>A new Token</returns>
        public static async Task<AuthenticationResponse> GenerateToken()
        {
            RestRequest request = new RestRequest(AUTHORITY_URL, Method.POST);

            request.AddHeader("Accept", "application/json");
            request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
            request.AddParameter("grant_type", "client_credentials");
            request.AddParameter("client_id", CLIENT_ID);
            request.AddParameter("client_secret", CLIENT_SECRET);
            request.AddParameter("scope", SCOPE);

            IRestResponse response = await client.ExecutePostTaskAsync(request);
            if (response.IsSuccessful && response.StatusCode.HasFlag(HttpStatusCode.OK))
            {
                JObject jsonObject = JObject.Parse(response.Content);
                AuthenticationResponse tokenObject = jsonObject.ToObject<AuthenticationResponse>();
                return tokenObject;
            }
            return null;
        }     
    }
}

L’intérêt d’utiliser un Model pour le token est d’obtenir également la validité du token. Au cas où, vous voudriez juste obtenir le token seul sans passer par un Model, voici le service correspondant:

using Newtonsoft.Json.Linq;
using BankApp.Models;
using RestSharp;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using static BankApp.Constants.APIResource;

namespace BankApp.Services
{
    public class BankAppAPIService
    {
        /// <summary>
        /// Defines a RestClient object.
        /// </summary>
        private static readonly RestClient client = new RestClient();

        /// <summary>
        /// Method to generate Oauth2 Token from the Bank API.
        /// </summary>
        /// <returns>A new Token</returns>
        public static async Task<string> GenerateToken()
        {
            RestRequest request = new RestRequest(AUTHORITY_URL, Method.POST);

            request.AddHeader("Accept", "application/json");
            request.AddHeader("Content-Type", "application/x-www-form-urlencoded");
            request.AddParameter("grant_type", "client_credentials");
            request.AddParameter("client_id", CLIENT_ID);
            request.AddParameter("client_secret", CLIENT_SECRET);
            request.AddParameter("scope", SCOPE);

            IRestResponse response = await client.ExecutePostTaskAsync(request);
            if (response.IsSuccessful && response.StatusCode.HasFlag(HttpStatusCode.OK))
            {
                var jsonResponse = response.Content;
                var token = JsonConvert.DeserializeObject<Dictionary<string, object>>(jsonResponse)["access_token"].ToString();
                return token;
            }
            return null;
        }      
    }
}

Nous avons notre Token maintenant, implémentons le service d’envoi des transactions.

Service d’envoi des données d’opérations bancaires

using Newtonsoft.Json.Linq;
using BankApp.Models;
using RestSharp;
using System.Collections.Generic;
using System.Net;
using System.Threading.Tasks;
using static BankApp.Constants.APIResource;

namespace BankApp.Services
{
    public class BankAppTransactionsService
    {
        /// <summary>
        /// Defines a RestClient object.
        /// </summary>
        private static readonly RestClient client = new RestClient();

        /// <summary>
        /// Send transactions data data to the Bank API.
        /// </summary>
        /// <param name="access_token">The access token<</param>
        /// <param name="data">transactions data.</param>
        /// <returns>True if sent or False if not.</returns>
        public static async Task<bool> SendData(string access_token, List<Transation> data)
        {
            // Invalid data
            if (access_token == null || data.Count == 0)
                return false;

            var request = new RestRequest(TRANSACTIONS_ENDPOINT, Method.POST, DataFormat.Json);

            // Add hearders and token to request
            request.AddHeader("Content-Type", "application/json");
            request.AddHeader("Authorization", "Bearer " + access_token);
            // Add request Body
            request.AddJsonBody(data);

            // Make the API request and get a response
            IRestResponse response = await client.ExecutePostTaskAsync(request);
            if (response.IsSuccessful && response.StatusCode.HasFlag(HttpStatusCode.OK))
            {
                return true;
            }
            return false;
        }   
    }
}

Lorsque des erreurs liées au token apparaissent, il se peut ce dernier soit expiré. Demander un nouveau token serait une meilleure solution.

Ressources

N’hésitez pas à me contacter via le formulaire de contact ou par mail.


Commentaires



Mes Badges


Categories

Nouveaux posts