Sécuriser les applications basées sur le cloud avec OAuth 2.0 et OpenID Connect
Un guide complet pour sécuriser vos applications cloud avec OAuth 2.0 et OpenID Connect et comment offrir une excellente expérience utilisateur avec l'authentification et l'autorisation.
Introduction
Les applications basées sur le cloud sont la tendance de nos jours. Bien que le type d'application varie (web, mobile, bureau, etc.), elles ont toutes un backend cloud qui fournit des services comme le stockage, le calcul et les bases de données. La plupart du temps, ces applications doivent authentifier les utilisateurs et les autoriser à accéder à certaines ressources.
Bien que des mécanismes d'authentification et d'autorisation maison soient possibles, la sécurité est devenue l'une des principales préoccupations lors du développement d'applications cloud. Heureusement, notre industrie dispose de normes éprouvées comme OAuth 2.0 et OpenID Connect pour nous aider à implémenter une authentification et une autorisation sécurisées.
Ce post suppose les points suivants :
- Vous avez une compréhension de base du développement d'applications (web, mobile ou d'un autre type).
- Vous avez entendu parler des concepts d'authentification et d'autorisation.
- Vous avez entendu parler d'OAuth 2.0 et d'OpenID Connect.
Oui, "entendu" est suffisant pour les points 2 et 3. Ce post utilisera des exemples concrets pour expliquer les concepts et illustrera le processus avec des diagrammes. Commençons !
OAuth 2.0 vs. OpenID Connect
Si vous êtes familier avec OAuth 2.0 et OpenID Connect, vous pouvez également continuer à lire car nous couvrirons quelques exemples concrets dans cette section ; si vous êtes nouveau à ces normes, vous pouvez également continuer car nous les présenterons de manière simple.
OAuth 2.0
OAuth 2.0 est un cadre d'autorisation qui permet à une application d'obtenir un accès limité à des ressources protégées sur une autre application pour le compte d'un utilisateur ou de l'application elle-même. La plupart des services populaires comme Google, Facebook et GitHub utilisent OAuth 2.0 pour la connexion sociale (par exemple, "Se connecter avec Google").
Par exemple, vous avez une application web MyApp qui souhaite accéder au Google Drive de l'utilisateur. Plutôt que de demander à l'utilisateur de partager ses identifiants Google Drive, MyApp peut utiliser OAuth 2.0 pour demander l'accès à Google Drive pour le compte de l'utilisateur. Voici un flux simplifié :
Dans ce flux, MyApp ne voit jamais les identifiants Google Drive de l'utilisateur. Au lieu de cela, elle reçoit un access token de Google qui lui permet d'accéder à Google Drive pour le compte de l'utilisateur.
En termes d'OAuth 2.0, MyApp est le client, Google est à la fois le serveur d'autorisation et le serveur de ressources pour simplifier. Dans le monde réel, nous avons souvent des serveurs d'autorisation et de ressources séparés pour offrir une expérience de connexion unique (SSO). Par exemple, Google est le serveur d'autorisation et peut avoir plusieurs serveurs de ressources comme Google Drive, Gmail et YouTube.
Notez que le flux d'autorisation réel est plus complexe que cela. OAuth 2.0 a différents types de subventions, des portées et d'autres concepts dont vous devez être conscient. Mettons cela de côté pour l'instant et passons à OpenID Connect.
OpenID Connect (OIDC)
OAuth 2.0 est excellent pour l'autorisation, mais vous pouvez remarquer qu'il n'a pas de moyen d'identifier l'utilisateur (c'est-à-dire, l'authentification). OpenID Connect est une couche d'identité sur OAuth 2.0 qui ajoute des capacités d'authentification.
Dans l'exemple ci-dessus, MyApp doit savoir qui est l'utilisateur avant d'initier le flux d'autorisation. Notez qu'il y a deux utilisateurs impliqués ici : l'utilisateur de MyApp et l'utilisateur de Google Drive. Dans ce cas, MyApp doit connaître l'utilisateur de sa propre application.
Voyons un exemple simple, en supposant que les utilisateurs peuvent se connecter à MyApp en utilisant un nom d'utilisateur et un mot de passe :
Puisque nous authentifions l'utilisateur de notre propre application, il n'est généralement pas nécessaire de demander la permission comme Google l'a fait dans le flux OAuth 2.0. Entre-temps, nous avons besoin de quelque chose qui puisse identifier l'utilisateur. OpenID Connect introduit des concepts comme le jeton d'identité et le point d'extrémité userinfo pour nous aider avec cela.
Vous pouvez remarquer que le fournisseur d'identité (IdP) est un nouveau participant autonome dans le flux. Il est le même que le serveur d'autorisation dans OAuth 2.0, mais pour plus de clarté, nous utilisons le terme IdP pour montrer qu'il est responsable de l'authentification de l'utilisateur et de la gestion de l'identité.
Lorsque votre entreprise se développe, vous pouvez avoir plusieurs applications qui partagent la même base de données utilisateur. Tout comme OAuth 2.0, OpenID Connect vous permet d'avoir un serveur d'autorisation unique qui peut authentifier les utilisateurs pour plusieurs applications. Si l'utilisateur est déjà connecté à une application, il n'a pas besoin de saisir ses identifiants à nouveau lorsqu'une autre application les redirige vers l'IdP. Le flux peut se faire automatiquement sans interaction de l'utilisateur. C'est ce qu'on appelle la connexion unique (SSO).
Encore une fois, il s'agit d'un flux très simplifié et il y a plus de détails sous le capot. Pour l'instant, passons à la section suivante pour éviter la surcharge d'informations.
Types d'applications
Dans la section précédente, nous avons utilisé des applications web en tant qu'exemples alors que le monde est plus diversifié que cela. Pour un fournisseur d'identité, le langage de programmation exact, le cadre ou la plateforme que vous utilisez n'a pas vraiment d'importance. En pratique, une différence notable est si l'application est un client public ou un client privé (de confiance) :
- Client public : Un client qui ne peut pas garder ses identifiants confidentiels, ce qui signifie que le propriétaire de la ressource (utilisateur) peut y accéder. Par exemple, une application web s'exécutant dans un navigateur (par exemple, une application à page unique).
- Client privé : Un client qui a la capacité de garder ses identifiants confidentiels sans les exposer aux propriétaires de ressources (utilisateurs). Par exemple, une application web s'exécutant sur un serveur (par exemple, application web côté serveur) ou un service API.
Avec cela à l'esprit, voyons comment OAuth 2.0 et OpenID Connect peuvent être utilisés dans différents types d'applications.
"Application" et "client" peuvent être utilisés de manière interchangeable dans le contexte de cet article.
Applications web s'exécutant sur un serveur
L'application s'exécute sur un serveur et sert des pages HTML aux utilisateurs. De nombreux cadres web populaires comme Express.js, Django et Ruby on Rails entrent dans cette catégorie ; et les cadres backend-for-frontend (BFF) comme Next.js et Nuxt.js sont également inclus. Ces applications ont les caractéristiques suivantes :
- Puisqu'un serveur permet uniquement un accès privé (il n'y a pas moyen pour les utilisateurs publics de voir le code ou les identifiants du serveur), il est considéré comme un client privé.
- Le flux global d'authentification de l'utilisateur est le même que celui que nous avons discuté dans la section "OpenID Connect".
- L'application peut utiliser le token d'identité émis par le fournisseur d'identité (c'est-à-dire, Providence OpenID Connect) pour identifier l'utilisateur et afficher du contenu spécifique à l'utilisateur.
- Pour garder l'application sécurisée, l'application utilise généralement le flow du code d'autorisation pour l'authentification des utilisateurs et pour obtenir des tokens.
Dans le même temps, l'application peut avoir besoin d'accéder à d'autres services API internes dans une architecture de microservices ; ou c'est une application monolithique qui nécessite un contrôle d'accès pour différentes parties de l'application. Nous aborderons cela dans la section "Protéger votre API".
Applications à page unique (SPAs)
L'application s'exécute dans le navigateur d'un utilisateur et communique avec le serveur via des APIs. React, Angular et Vue.js sont des cadres populaires pour construire des SPAs. Ces applications ont les caractéristiques suivantes :
- Puisque le code de l'application est visible du public, elle est considérée comme un client public.
- Le flux global d'authentification de l'utilisateur est le même que celui que nous avons discuté dans la section "OpenID Connect".
- L'application peut utiliser le token d'identité émis par le fournisseur d'identité (c'est-à-dire, Providence OpenID Connect) pour identifier l'utilisateur et afficher du contenu spécifique à l'utilisateur.
- Pour garder l'application sécurisée, l'application utilise généralement le flow du code d'autorisation avec PKCE (Proof Key for Code Exchange) pour l'authentification des utilisateurs et pour obtenir des tokens.
Généralement, les SPAs ont besoin d'accéder à d'autres services API pour la récupération et la mise à jour des données. Nous abordons cela dans la section "Protéger votre API".
Applications mobiles
L'application s'exécute sur un appareil mobile (iOS, Android, etc.) et communique avec le serveur via des APIs. Dans la plupart des cas, ces applications ont les mêmes caractéristiques que les SPAs.
Applications machine-à-machine (M2M)
Les applications machine-à-machine sont des clients qui s'exécutent sur un serveur (machine) et communiquent avec d'autres serveurs. Ces applications ont les caractéristiques suivantes :
- Comme les applications web s'exécutant sur un serveur, les applications M2M sont des clients privés.
- L'application peut ne pas avoir besoin d'identifier l'utilisateur ; au lieu de cela, elle doit s'authentifier pour accéder à d'autres services.
- L'application peut utiliser le jeton d'accès émis par le fournisseur d'identité (c'est-à-dire, le fournisseur OAuth 2.0) pour accéder à d'autres services.
- Pour garder l'application sécurisée, l'application utilise généralement le flow des informations d'identification client pour obtenir des jetons d'accès.
Lors de l'accès à d'autres services, l'application peut avoir besoin de fournir le jeton d'accès dans l'en-tête de la requête. Nous aborderons cela dans la section "Protéger votre API".
Applications s'exécutant sur des appareils IoT
L'application s'exécute sur un appareil IoT (par exemple, appareils domestiques intelligents, objets connectés, etc.) et communique avec le serveur via des APIs. Ces applications ont les caractéristiques suivantes :
- Selon la capacité de l'appareil, il peut s'agir d'un client public ou privé.
- Le flux global d'authentification peut être différent de celui que nous avons discuté dans la section "OpenID Connect" en fonction de la capacité de l'appareil. Par exemple, certains appareils peuvent ne pas avoir d'écran pour que les utilisateurs saisissent leurs identifiants.
- Si l'appareil n'a pas besoin d'identifier l'utilisateur, il peut ne pas avoir besoin d'utiliser des jetons d'identité ou des points d'extrémité userinfo ; au lieu de cela, il peut être traité comme une application machine-à-machine (M2M).
- Pour garder l'application sécurisée, l'application peut utiliser le flow du code d'autorisation avec PKCE (Proof Key for Code Exchange) pour l'authentification des utilisateurs et obtenir des jetons ou le flow des informations d'identification client pour obtenir des jetons d'accès.
Lors de la communication avec le serveur, l'appareil peut avoir besoin de fournir le jeton d'accès dans l'en-tête de la requête. Nous aborderons cela dans la section "Protéger votre API".
Protéger votre API
Avec OpenID Connect, il est possible d'identifier l'utilisateur et de récupérer des données spécifiques à l'utilisateur via des jetons d'identité ou des points d'extrémité userinfo. Ce processus est appelé authentification. Mais vous ne souhaitez probablement pas exposer toutes vos ressources à tous les utilisateurs authentifiés, par exemple, seuls les administrateurs peuvent accéder à la page de gestion des utilisateurs.
C'est là qu'intervient l'autorisation. Rappelez-vous qu'OAuth 2.0 est un cadre d'autorisation, et OpenID Connect est une couche d'identité sur OAuth 2.0 ; ce qui signifie que vous pouvez également utiliser OAuth 2.0 lorsque OpenID Connect est déjà en place.
Rappelons l'exemple que nous avons utilisé dans la section "OAuth 2.0" : MyApp souhaite accéder au Google Drive de l'utilisateur. Il n'est pas pratique de laisser MyApp accéder à tous les fichiers de l'utilisateur sur Google Drive. Au lieu de cela, MyApp devrait explicitement déclarer ce qu'elle veut accéder (par exemple, un accès en lecture seule aux fichiers dans un dossier spécifique). En termes d'OAuth 2.0, cela s'appelle un scope.
Vous pouvez voir le terme "permission" utilisé de manière interchangeable avec "scope" dans le contexte d'OAuth 2.0 car parfois "scope" est ambigu pour les utilisateurs non techniques.
Lorsque l'utilisateur accorde l'accès à MyApp, le serveur d'autorisation émet un jeton d'accès avec la portée demandée. Le jeton d'accès est ensuite envoyé au serveur de ressources (Google Drive) pour accéder aux fichiers de l'utilisateur.
Naturellement, nous pouvons remplacer Google Drive par nos propres services API. Par exemple, MyApp a besoin d'accéder à OrderService pour récupérer l'historique des commandes de l'utilisateur. Cette fois, puisque l'authentification de l'utilisateur est déjà effectuée par le fournisseur d'identité et que MyApp et OrderService sont sous notre contrôle, nous pouvons éviter de demander à l'utilisateur d'accorder l'accès ; MyApp peut envoyer directement la requête à OrderService avec le jeton d'accès émis par le fournisseur d'identité.
Le jeton d'accès peut contenir un read:order
scope indiquant que l'utilisateur peut lire son historique de commandes.
Maintenant, supposons que l'utilisateur entre accidentellement une URL de page d'administration dans le navigateur. Puisque l'utilisateur n'est pas un administrateur, il n'y a pas de scope admin
dans le jeton d'accès. Le service OrderService rejettera la demande et renverra un message d'erreur.
Dans ce cas, le service OrderService peut renvoyer un code de statut 403 Interdit pour indiquer que l'utilisateur n'est pas autorisé à accéder à la page d'administration.
Pour les applications machine-à-machine (M2M), aucun utilisateur n'est impliqué dans le processus. Les applications peuvent demander directement des jetons d'accès au fournisseur d'identité et les utiliser pour accéder à d'autres services. Le même concept s'applique : le jeton d'accès contient les scopes nécessaires pour accéder aux ressources.
Conception de l'autorisation
Nous pouvons voir deux éléments importants à considérer lors de la conception de l'autorisation pour protéger vos services API :
- Scopes : Définir ce que le client peut accéder. Les portées peuvent être granulaires (par exemple,
read:order
,write:order
) ou plus générales (par exemple,order
) selon vos besoins. - Contrôle d'accès : Définir qui peut avoir des scopes spécifiques. Par exemple, seuls les administrateurs peuvent avoir le scope
admin
.
Concernant le contrôle d'accès, certaines approches populaires sont :
- Contrôle d'accès basé sur les rôles (RBAC) : Attribuer des rôles aux utilisateurs et définir quels rôles peuvent accéder à quelles ressources. Par exemple, un rôle administrateur peut accéder à la page d'administration.
- Contrôle d'accès basé sur les attributs (ABAC) : Définir des politiques basées sur des attributs (par exemple, le département de l'utilisateur, la localisation, etc.) et prendre des décisions de contrôle d'accès basées sur ces attributs. Par exemple, un utilisateur du département "Ingénierie" peut accéder à la page d'ingénierie.
Il convient de mentionner que pour les deux approches, la méthode standard pour vérifier le contrôle d'accès est de vérifier les scopes des jetons d'accès, plutôt que les rôles ou les attributs. Les rôles et les attributs peuvent être très dynamiques et les scopes sont plus statiques, ce qui facilite grandement leur gestion.
Pour plus d'informations sur le contrôle d'accès, vous pouvez consulter RBAC et ABAC : Les modèles de contrôle d'accès que vous devriez connaître.
Jetons d'accès
Bien que nous ayons mentionné le terme "jeton d'accès" de nombreuses fois, nous n'avons pas discuté de la façon d'en obtenir un. Dans OAuth 2.0, un jeton d'accès est émis par le serveur d'autorisation (fournisseur d'identité) après un flux d'autorisation réussi.
Regardons de plus près l'exemple de Google Drive et supposons que nous utilisons le flow du code d'autorisation :
Il y a quelques étapes importantes dans le flux pour l'émission des jetons d'accès :
- Étape 2 (Rediriger vers Google) : MyApp redirige l'utilisateur vers Google avec une demande d'autorisation. En général, cette demande inclut les informations suivantes :
- Quel client (MyApp) initie la demande
- Quels scopes MyApp demande
- Où Google doit rediriger l'utilisateur après l'autorisation
- Étape 5 (Demander la permission d'accéder à Google Drive) : Google demande à l'utilisateur d'accorder l'accès à MyApp. L'utilisateur peut choisir d'accorder ou de refuser l'accès. Cette étape s'appelle consentement.
- Étape 7 (Rediriger vers MyApp avec les données d'autorisation) : Cette étape est nouvellement introduite dans le diagramme. Plutôt que de retourner directement le jeton d'accès, Google retourne un code d'autorisation unique à MyApp pour un échange plus sécurisé. Ce code est utilisé pour obtenir le jeton d'accès.
- Étape 8 (Utiliser le code d'autorisation pour échanger contre un jeton d'accès) : C'est également une nouvelle étape. MyApp envoie le code d'autorisation à Google pour échanger contre un jeton d'accès. En tant que fournisseur d'identité, Google composera le contexte de la requête et décidera s'il convient d'émettre un jeton d'accès :
- Le client (MyApp) est celui qu'il prétend être
- L'utilisateur a accordé l'accès au client
- L'utilisateur est celui qu'il prétend être
- L'utilisateur a les scopes nécessaires
- Le code d'autorisation est valide et n'a pas expiré
L'exemple ci-dessus suppose que le serveur d'autorisation (fournisseur d'identité) et le serveur de ressources sont les mêmes (Google). S'ils sont séparés, prenant l'exemple de MyApp et OrderService, le flux sera comme ceci :
Dans ce flux, le serveur d'autorisation (IdP) émet à la fois un jeton d'identité et un jeton d'accès à MyApp (étape 8). Le jeton d'identité est utilisé pour identifier l'utilisateur, et le jeton d'accès est utilisé pour accéder à d'autres services comme OrderService. Puisque MyApp et OrderService sont des services de première partie, généralement ils ne demandent pas à l'utilisateur d'accorder l'accès ; au lieu de cela, ils s'appuient sur le contrôle d'accès dans le fournisseur d'identité pour déterminer si l'utilisateur peut accéder aux ressources (c'est-à-dire si le jeton d'accès contient les scopes nécessaires).
Enfin, voyons comment le jeton d'accès est utilisé dans les applications machine-à-machine (M2M). Puisqu'aucun utilisateur n'est impliqué dans le processus et que l'application est de confiance, elle peut demander directement un jeton d'accès au fournisseur d'identité :
Le contrôle d'accès peut encore être appliqué ici. Pour OrderService, peu importe qui est l'utilisateur ou quelle application demande les données ; il ne se préoccupe que du jeton d'accès et des scopes qu'il contient.
Les jetons d'accès sont généralement encodés en JSON Web Tokens (JWT). Pour en savoir plus sur les JWT, vous pouvez vous référer à Qu'est-ce qu'un JSON Web Token (JWT) ?.
Indicateurs de ressources
OAuth 2.0 introduit le concept de scopes pour le contrôle d'accès. Cependant, vous pouvez rapidement réaliser que les scopes ne suffisent pas :
- OpenID Connect définit un ensemble de scopes standards comme
openid
,offline_access
etprofile
. Il peut être déroutant de mélanger ces scopes standards avec vos scopes personnalisés. - Vous pouvez avoir plusieurs services API partageant le même nom de scope mais ayant des significations différentes.
Une solution courante est d'ajouter des suffixes (ou des préfixes) aux noms des scopes pour indiquer la ressource (service API). Par exemple, read:order
et read:product
sont plus clairs que read
et read
. Une extension OAuth 2.0 RFC 8707 introduit un nouveau paramètre resource
pour indiquer le serveur de ressources que le client souhaite accéder.
En réalité, les services API sont généralement définis par des URLs, donc il est plus naturel d'utiliser des URLs comme indicateurs de ressources. Par exemple, l'API OrderService peut être représentée par :
Comme vous pouvez le voir, le paramètre apporte la commodité d'utiliser les URLs de ressources réelles dans les demandes d'autorisation et les jetons d'accès. Il convient de mentionner que la RFC 8707 peut ne pas être implémentée par tous les fournisseurs d'identité. Vous devriez vérifier la documentation de votre fournisseur d'identité pour voir s'il prend en charge le paramètre resource
(Logto le prend en charge).
En un mot, le paramètre resource
peut être utilisé dans les demandes d'autorisation et les jetons d'accès pour indiquer la ressource que le client souhaite accéder.
Il n'y a aucune restriction sur l'accessibilité des indicateurs de ressources, c'est-à-dire que l'indicateur de ressources n'a pas besoin d'être une URL réelle pointant vers un service API. Ainsi, le nom "indicateur de ressource" reflète correctement son rôle dans le processus d'autorisation. Vous pouvez utiliser des URLs virtuelles pour représenter les ressources que vous souhaitez protéger. Par exemple, vous pouvez définir une URL virtuelle
https://api.example.com/admin
dans votre application monolithique pour représenter la ressource à laquelle seuls les administrateurs peuvent accéder.
Mettre le tout ensemble
À ce stade, nous avons couvert les bases de OAuth 2.0 et OpenID Connect, et comment les utiliser dans différents types d'applications et scénarios. Bien que nous les ayons discutés séparément, vous pouvez les combiner selon vos besoins d'affaires. L'architecture globale peut être aussi simple que cela :
Ou un peu plus complexe :
À mesure que votre application se développe, vous verrez que le fournisseur d'identité (IdP) joue un rôle crucial dans l'architecture ; mais cela n'a pas de rapport direct avec vos objectifs commerciaux. Bien qu'il soit une excellente idée de l'externaliser à un fournisseur fiable, nous devons choisir judicieusement le fournisseur d'identité. Un bon fournisseur d'identité peut grandement simplifier le processus, réduire l'effort de développement et vous éviter des pièges de sécurité potentiels.
Remarques de clôture
Pour les applications cloud modernes, le fournisseur d'identité (ou serveur d'autorisation) est l'endroit central pour l'authentification utilisateur, la gestion des identités, et le contrôle d'accès. Bien que nous ayons discuté de nombreux concepts dans cet article, il reste encore de nombreuses nuances à considérer lors de la mise en œuvre d'un tel système. Si vous êtes intéressé à en savoir plus, vous pouvez parcourir notre blog pour des articles plus approfondis.