Sécuriser une API grâce à OAuth2.0
Cet article est le deuxième d'une série d'articles concernant un POC sur la création d'API agnostiques.
Vous pouvez lire le premier article ici
La sécurité dans le cadre de création d'API peut s'avérer un frein au développement d'applications. Dans cet article, nous allons détailler une solution commune : la mise en place d'un serveur d'authentification supportant OAuth et Open ID Connect. Ce serveur d'authentification permettra à des applications tierces et des applications in-house d'ouvrir des sessions pour des utilisateurs. Nous pourrons ainsi développer des services et des applications en utilisant toujours la même source d'authentification, rendant le système très flexible.
OAuth est un protocole d'authentification. Il donne à une application ou un service la possibilité de communiquer en ayant l'assurance de l'identité par le biais de jetons de sécurité. En général, un serveur dédié à l'authentification est mis en place afin de créer ces jetons. Nous allons ici survoler le protocole OAuth2.0 et Open ID Connect puis dans la série d'articles à venir nous allons installer notre propre serveur OAuth et montrer son utilisation.
Tour d'horizon
OAuth se base sur les propriétés des clés cryptographiques asymétriques. Le principe est que le serveur d'authentification va signer avec sa clé privée des jetons, permettant à d'autres services de vérifier la signature sans pour autant arriver à falsifier cette même signature. Nous n'allons pas approfondir cet aspect cryptographique, et rester sur un tour d'horizon d'OAuth2.0. Le protocole "vu de haut" est très simple :
Une fois l'application en possession d'un jeton, elle peut faire des appels à différentes APIs en accompagnant ses requêtes du jeton de sécurité fourni par le serveur OAuth. Les APIs devront simplement valider les jetons de sécurités reçus auprès du serveur d'authentification pour définir l'authentification puis les permissions.
La sécurité de OAuth est renforcée par la nature des jetons, qui possèdent une durée de vie parfois très courte. Une application peut donc demander un nouveau jeton à chaque nouvel appel, limitant ainsi la possibilité de vol d'identité. La gestion du renouvellement de jeton peut être implémentée par le serveur d'authentification par l'intermédiaire d'un cookie (stratégie nommée par refresh token), mais cela n'est pas défini dans OAuth et dépends de l'implémentation dans le serveur d'authentification.
Une autre propriété intéressante des serveurs d'authentifications est qu'ils sont complètement décentralisés. On peut par exemple utiliser les serveurs Google ou Facebook pour authentifier des utilisateurs. Nous allons dans cette série d'articles utiliser notre propre service d'authentification permettant de centraliser nos utilisateurs à un seul endroit et de permettre à des applications d'utiliser ce serveur de la même manière qu'une authentification Facebook.
En utilisant un protocole largement utilisé, nous permettons à de nombreuses applications/services/sites internets de se connecter à la même base d'utilisateurs, tout en gardant un contrôle complet sur la localisation de nos données utilisateurs. Mais avant la mise en place de ce serveur, il est important de comprendre un peu mieux comment il fonctionne.
Demander un jeton
Le protocole OAuth définit le format du jeton, son échange et sa validation. La manière dont le jeton est demandé varie selon les implémentations et deux implémentations courantes en ont émergé.
Pour mieux comprendre ces différentes implémentations, nous allons prendre le cas de Facebook qui a contribué à populariser l'utilisation du protocole OAuth. Dans le cas de Facebook, deux cas d'utilisations différents sont distingués :
Connexion par des applications tierces (soundcloud, amazon, etc.)
Connexion par des applications in-house (messenger, site facebook)
Ces deux cas sont gérés différemment car dans le premier cas on ne peut pas se fier à l'application tierce complètement – elle pourrait fournir un formulaire de connexion non-sécurisé ou même trompeur. Dans le cas d'applications in-house, nous avons l'assurance que les standards de sécurités seront respectés, ou du moins nous avons un contrôle direct sur comment les formulaires de connexion s'affichent.
Le critère pour savoir comment demander un jeton est donc dépendant de la confiance accordée à l'application ou au service.
OIDC
Afin de régler le cas des applications tierces, le protocole Open ID Connect (OIDC) va fournir une couche supplémentaire à OAuth2.0. OIDC va permettre d'avoir un contrôle sur le formulaire qui servira à la connexion. Il s'agit au final d'un autre protocole permettant spécifiquement de gérer des applications tierces.
Le protocole est résumé par ce schéma :
Avec OIDC, nous avons la garantie que le formulaire de connexion est sécurisé, car il est développé par le serveur d'authentification. De plus par cet échange de code (point 2/3), nous savons que la même application qui a redirigé initialement l'utilisateur est bien celle qui reçoit le jeton. En pratique, les serveurs d'authentification implémentant OIDC donnent une durée de validité très courte (quelques secondes) pour le code transmis en cas de succès, afin de diminuer les risques de vols de jeton.
Il est important de noter que OIDC exige une redirection, le formulaire de connexion ne pourra donc jamais être codé par l'application. C'est une contrainte UX qui est importante à prendre en compte lors du design de l'application tierce.
Applications de confiance
Si l'application qui implémente le formulaire de connexion est de confiance, nous pouvons utiliser une autre méthode pour obtenir un jeton de sécurité. Dans le service d'authentification, nous spécifions l'URL de l'application de confiance, et générons une clé API unique à l'application. L'application devra ensuite joindre à sa demande de jeton la clé API pour avoir le droit de récupérer des jetons.
Dans le cas de Facebook Messenger par exemple, l'application peut créer son propre formulaire de connexion puis demander au serveur OAuth un jeton avec la paire email/mot de passe. Ceci est possible uniquement car le serveur d'authentification de Facebook fait complètement confiance à l'implémentation de Messenger.
A noter que la clé API utilisée par un service de confiance est une information critique qui ne doit être en aucun cas exposée au public. Il est donc essentiel dans le cas d'application javascript de créer un backend léger permettant faire l'intermédiaire. L'application javascript envoie au backend les données du formulaire, et le backend les transmet au service d'authentification avec la clé API. Ainsi seul le backend léger saura la clé API, l'application javascript étant entièrement exposée au public elle ne pourra jamais être considérée comme application de confiance.
Ces deux méthodes pour récupérer un jeton de sécurité doivent être présentes dans le cas d'un développement d'une API "agnostique" car nous ne savons pas encore qui va utiliser le service et si les applications vont être de confiance ou non.
Plan d'action
Afin de réaliser notre POC, nous allons mettre en place un serveur d'authentification, une application React et un backend Ruby on Rails. Nous allons nous concentrer sur la création d'utilisateurs par un administrateur, car cette fonctionnalité permet de travailler sur un cas très commun.
Dans la première partie, l'application React va se connecter par le biais de OIDC au serveur d'authentification. Il y aura donc l'échange de code temporaire décrit dans cet article, et le formulaire de connexion sera celui affiché par le serveur d'authentification.
Dans les deux dernières parties, nous allons détailler comment un administrateur pourra ajouter un utilisateur en passant par l'intermédiaire d'une application de confiance, ici un backend Ruby On Rails. Ce backend Rails va valider le jeton reçu par l'application React, vérifier que le jeton est celui d'un administrateur et va transmettre la création de l'utilisateur et de ses rôles associé au serveur d'authentification.
La partie React puis la partie Ruby On Rails vont être détaillés dans de prochains articles.
Conclusion
La mise en place d'un serveur d'authentification offre de nombreux avantages, et ouvre les porte du Software As A Service (SaaS). Nous pouvons grâce à un serveur d'authentification ajouter une grande extensibilité comme par exemple dans les cas suivants :
Permettre à des développeurs enthousiastes de créer des applications intégrant nos utilisateurs et nos données. Par exemple InstaGantt implémente une vue en ligne du temps des tâches Asana.
Créer de nombreuses applications web et natives indépendantes utilisant toujours les mêmes identifications d'utilisateur, comme dans le milieu associatif, les universités, grande entreprises ou même certains gouvernements.