liksi logo

Autoriser l’accès à vos APIs avec Keycloak

Tristan Denmat - Publié le 14/01/2021
Autoriser l’accès à vos APIs avec Keycloak

Authentification & Autorisation… Les deux vont souvent de paire mais il faut reconnaître qu’en tant que développeur on est souvent beaucoup plus à l’aise avec le premier que le second. Dans cet article, nous allons discuter d’autorisation, de la spécification UMA et des différentes options pour déléguer l’autorisation à Keycloak.

Keycloak authentifie vos utilisateurs, mais pas que…

Authentifier un utilisateur vise à montrer qu’il est bien le titulaire de l’identité qu’il présente. Cette identité est souvent matérialisée par un simple identifiant (login) ou une adresse email. Il existe plusieurs moyens de réaliser une authentification, le plus fréquent consiste à vérifier que l’utilisateur connaît bien le mot de passe associé à son login. C’est un problème très connu, auquel la plupart des développeurs doivent apporter une solution un jour ou l’autre.

Keycloak est un serveur d’authentification open-source, développé par Red Hat, qui fournit une solution “clé-en-main” à ce problème d’authentification. Cette solution est reconnue pour sa fiabilité et sa flexibilité qui permet de l’intégrer aussi bien dans des environnements complexes de type SI d’entreprise que des applications simples.

Savoir si un utilisateur authentifié a le droit de faire une action ou non est un autre problème : c’est ce qu’on appelle l’autorisation. Dans de très nombreux cas, l’autorisation est gérée dans les applications, en appliquant une politique de type RBAC (Role-based Access Control). L’exemple ci-dessous est une configuration classique de spring security pour implémenter RBAC :

http.authorizeRequests()
	.mvcMatchers("/signup").permitAll()
	.mvcMatchers(HttpMethod.GET, "/images/").hasAuthority("USER")
	.mvcMatchers(HttpMethod.POST, "/images/").hasAuthority("ADMIN")
	.http.authorizeRequests()

Sans rentrer dans les détails de Spring Security, vous n’aurez pas de mal à imaginer que, avec cette configuration, n’importe qui peut accéder à /signup, qu’il faut avoir le rôle USER pour faire une requête GET sur l’url /images et qu’il faut par contre avoir le rôle ADMIN pour faire un POST.

Définir ces règles d’accès au sein d’une application a le mérite d’être très simple mais présente néanmoins des limitations :

  • Il y a un couplage fort entre les applications et les rôles utilisateurs, ce qui rend complexe tout changement des politiques de sécurité, en particulier quand les mêmes rôles sont utilisés dans plusieurs applications
  • L’audit des politiques d’accès est compliqué car il faut analyser la configuration de chaque application
  • Seul RBAC est supporté, ce qui ne permet pas d’implémenter des politiques d’accès complexes, par exemple :
    • Accès interdit entre minuit et 6h00
    • Accès interdit depuis l’application mobile
    • Autorisation uniquement si je suis le propriétaire de la ressource

Utiliser un serveur d’autorisation externe, comme Keycloak, permet de résoudre ces problèmes :

  • Les politiques d’accès sont centralisées et auditables
  • Les politiques d’accès peuvent être aussi complexes que souhaité

La suite de l’article explique le fonctionnement de l’autorisation dans Keycloak, comment l’utiliser dans une application et, surtout, comment choisir le mode d’intégration approprié en fonction de votre contexte pour éviter les pièges.

L’autorisation avec Keycloak, comment ça marche ?

Tout d’abord, étudions les différents concepts manipulés par Keycloak

  • Resource Server : l’application que vous souhaitez protéger
  • Resource : un objet de votre application sur lequel des règles d’accès vont être définies. Cela peut être un path d’API, une ressource au sens REST, un fichier, …
  • Scope : une action pouvant être effectuée sur une ressource. Cela peut être par exemple READ / WRITE, ou simplement une action HTTP (GET, POST, PUT, …)

Ensuite, la responsabilité d’autoriser un client à effectuer une requête est partagée entre deux entités :

  • Le Policy Enforcement Point (PEP) est chargé d’identifier la ressource et le scope concernés par une requête entrante, d’appeler le PDP, puis d’effectuer ou rejeter l’action
  • Le Policy Decision Point (PDP) est chargé, étant donné un contexte (utilisateur, heure courante, IP  de la requête, …), de savoir si l’accès au couple (scope, ressource) est légitime ou non

Configuration du Policy Decision Point (PDP)

Lorsque vous utilisez Keycloak pour faire de l’autorisation, c’est Keycloak qui implémente le PDP. La configuration se fera donc au travers des écrans d’administration de Keycloak (ou via les APIs pour les cas avancés). Il vous faudra réaliser les opérations suivantes  :

  1. Créer des ressources
  2. Associer des scopes aux ressources
  3. Créer des politiques d’accès (restriction à un groupe, restriction à un rôle, vérification de la présence d’un header dans la requête, …)
  4. Lier un couple (ressource, scope) à une ou plusieurs politiques d’accès

Dans cet article, nous ne nous intéresserons pas plus en détail à la manière de créer ces objets, ni aux différentes politiques supportées par Keycloak. Je vous invite à consulter la documentation de Keycloak pour plus d’information sur ces sujets : gestion des ressources et permissions dans Keycloak

Configuration du Policy Enforcement Point (PEP)

A l’inverse du PDP, le PEP est porté par votre application. Pour les cas simples, Keycloak fournit un adaptateur implémentant un PEP basique qui peut être inclus très rapidement dans votre application en tant que dépendance externe. On verra plus bas un exemple de configuration de cet adapateur.

Pour les cas plus complexes, vous devrez écrire du code qui calculera le couple (ressource, scope) concerné par une requête entrante. Ce code pourra être aussi complexe que souhaité et vous permettra également de ne pas être limité au cas des requêtes HTTP, ce qui est indispensable si vous utilisez par exemples des interfaces à base d’échanges de messages (AMQP ou autre).

Quels impacts pour les clients de mes apis ?

OIDC est en train de s’imposer comme protocole d’authentification pour protéger l’accès à des APIs. Lorsque vous utilisez cette techno, les clients doivent commencer par récupérer un accessToken auprès du serveur OIDC puis l’utiliser comme en-tête ‘Authorization’ dans toutes les requêtes vers les APIs. Pour plus d’informations sur l’usage d’OIDC vous pouvez consulter un des nombreux articles à ce sujet, par exemple celui-ci : Illustrated guide to OAuth and OIDC. Pour aller plus loin et voir comment utiliser Keycloak pour intégrer OIDC dans vos APIs Springboot, vous pouvez voir ici : Authentification OIDC avec Keycloak & Springboot

Revenons à notre problématique d’autorisation. Keycloak propose d’étendre le contenu des tokens OIDC pour y inclure des informations concernant les ressources auxquelles un utilisateur a accès**.** Cette extension n’est pas normalisée**.** L’exemple ci-dessous montre un token JSON contenant un claim “authorization” dont la valeur est une liste de permissions. L’utilisateur courant a notamment des droits en lecture et écriture sur une ressource nommée “books”.

{
  (...)
  "authorization": {
    "permissions": [
      {
        "scopes": ["READ", "WRITE"],
        "rsid": "fe9cd4cd-e5fc-4d2d-a59f-f54398f0c0c2",
        "rsname": "books"
      }
    ]
  },
  "scope": "email_profile"
  (...)
}

La récupération d’un tel token se fait via un appel supplémentaire au serveur OIDC, en “échangeant” l’accessToken initial contre un token de type urn:ietf:params:oauth:grant-type:uma-ticket :

curl -X POST
 https://keycloak_url/auth/realms/your_realm/protocol/openid-connect/token
 -H 'Content-Type: application/x-www-form-urlencoded'
 -H 'Authorization: Bearer your_token'
 -d 'grant_type=urn%3Aietf%3Aparams%3Aoauth%3Agrant-type%3Auma-ticket&audience=your_rs_client_id'

Le PEP a ensuite juste à vérifier si le couple (ressource, scope) auquel l’utilisateur cherche à accéder figure bien dans la liste des permissions. Dans l’exemple ci-dessus, le PEP autoriserait l’utilisateur a accéder aux couples (books, READ) et (books, WRITE) et rejetterait toute autre demande.

Intégrer ce mécanisme côté client est donc très simple : vous devez ajouter un appel pour remplacer votre accessToken “habituel” par un token “étendu”. Ce token pourra ensuite être utilisé exactement comme l’accessToken (il devra d’ailleurs aussi être “raffraichi”). Cela soulève tout de même une question importante : comment Keycloak choisit-il la liste des permissions à ajouter dans le token ? Il y a en fait deux possibilités : soit le client indique explicitement dans sa requête de génération de token quelles ressources et quels scopes l’intéressent ; soit Keycloak retourne l’ensemble des permissions de l’utilisateur.

Les 2 options ont des inconvénients :

  • demander explicitement les permissions revient à coupler finement le client au PEP puisque le client doit savoir quelles permissions sont nécessaires pour un appel donné. Ce couplage fort n’est pas forcément souhaitable, notamment dans le cas où il y a de nombreux clients différents ou si le PEP est complexe.
  • retourner la liste complète revient à évaluer l’ensemble des permissions définies dans Keycloak, ce qui peut être coûteux en temps. De plus, la taille du token généré peut être problématique (souvenez-vous que le token doit être envoyé ensuite en header)

UMA à la rescousse

UMA est une spécification indépendante d’OIDC et OAuth qui propose des scénarios d’autorisation où des utilisateurs peuvent contrôler eux-mêmes les accès à leurs ressources. Notamment, cette spécification introduit la notion de “demande d’accès asynchrone” dans le cas où un utilisateur A va demander à accéder à une ressource possédée par B. Dans ce cas, B pourra, a posteriori, autoriser A à accéder à la ressource demandée. Keycloak implémente une partie de la spécification UMA, notamment des écrans permettant à un utilisateur de gérer ses demandes d’accès (visualisation, acceptation, révocation, …).

En plus de ce mécanisme de demande d’accès, UMA introduit la notion de “Permission Ticket” qui permet de découpler les clients du PEP. Au lieu de demander directement un token à Keycloak, le client va suivre les étapes suivantes :

  1. le client fait l’appel à l’API, avec uniquement son accessToken habituel
  2. le PEP calcul le couple (ressource, scope) et fait une requête vers Keycloak pour indiquer qu’un utilisateur cherche à accéder à ce couple. Keycloak retourne alors un “Permission Ticket” qui matérialise cette demande
  3. l’API retourne le “Permission Ticket” au client avec un code 401
  4. le client appelle Keycloak avec le Permission Ticket et son accessToken. Si l’utilisateur courant est autorisé, alors un token “étendu” (contenant les permissions) est émis.
  5. le client refait un appel vers l’API avec ce token étendu

Même si elle semble complexe par rapport au mode non-UMA, l’intérêt majeur de cette cinématique est que le client n’a aucune connaissance a priori des permissions nécessaires pour réaliser son appel à l’API. De plus, l’adaptateur Javascript fourni par Keycloak permet de réaliser cette séquence d’appels très simplement côté client.

Quels impacts pour mes apis ?

Côté back, vous devrez utiliser l’adaptateur Keycloak pour bénéficier d’un PEP sur étagère. Pour cela, il suffit d’étendre le contenu du fichier keycloak.yml (ou tout autre mécanisme de configuration de Keycloak). Je ne vais pas rentrer dans les détails ici, vous trouverez toutes les informations nécessaires dans la doc officielle : configuration de l’authentification avec Keycloak et là extension pour l’authorisation

Dans le cas où vous avez une association directe entre un couple (path d’API, opération HTTP) et un couple (ressource, scope), la mise en place de l’autorisation avec Keycloak est très simple. L’exemple ci-dessous montre comment modifier la configuration de votre fichier de configuration keycloak pour activer l’autorisation :

{
  "policy-enforcer": {}
}

Simple n’est-ce pas ? Attention, cette configuration par défaut implique que toutes les ressources aient été créées dans Keycloak en ayant un path associé, et que les scopes définis sur les ressources soient exactement les différentes méthodes HTTP.

Si cela ne vous convient pas, vous pouvez également choisir d’associer des paths aux ressources dans la configuration plutôt que dans Keycloak, et d’utiliser des scopes qui ne soient pas les méthodes HTTP :

{
  "policy-enforcer": {
    "user-managed-access": {},
    "enforcement-mode": "ENFORCING",
    "paths": [
      {
        "name": "My Resource",
        "path": "/images/{id}",
        "methods": [
          {
            "method": "DELETE",
            "scopes": ["urn:app.com:scopes:remove"]
          }
        ]
      }
    ]
  }
}

Dans cet exemple, faire un DELETE sur /images/12 correspondra à tenter d’accéder à la ressource “My Resource” avec le scope “urn:app.com:scopes:remove”

Notez également l’ajout de “user-managed-access” : {} qui fait basculer le PEP en mode UMA (utilisation des permissions tickets).

Comme vous pouvez le constater, l’adaptateur Keycloak est très pratique si vous voulez simplement migrer votre autorisation spring-security (ou autre) de RBAC vers des autorisations plus riches. En effet, l’autorisation est déportée vers Keycloak mais elle reste centrée sur des paths d’API et des méthodes HTTP.

Si vous désirez mettre en place quelque chose de plus subtil (mais en avez-vous vraiment besoin ?), vous ne couperez pas à écrire vous même le PEP (Keycloak fournit une librairie Java pour vous aider dans cette tâche). Cependant, ne voyez pas là un point bloquant : si vos mécanismes d’autorisation sont complexes, vous aurez nécessairement du code complexe à écrire pour les implémenter. Utiliser les objets et mécanismes de Keycloak fournit dans ce cas un cadre technique qui peut guider vos développements.

Alors, on y va ou pas ?

Vous l’aurez compris, utiliser Keycloak pour faire de l’autorisation peut être plus ou moins simple et vous avez désormais trois options sous la main pour gérer vos autorisations (par ordre de complexité croissante) :

  1. le faire vous-même dans votre application métier
  2. utiliser Keycloak en mode non-UMA
  3. utiliser Keycloak en mode UMA

Tout l’enjeu est d’utiliser la solution la mieux adaptée au besoin métier, le diagramme ci-dessous est là pour vous aider à faire ce choix :

Ce diagramme reprend l’ensemble des points dont nous avons déjà discuté dans l’article (toutes les boîtes en bleu clair). Il fait aussi apparaître un point très important dont nous n’avons pas encore parlé (indiqué en bleu foncé sur le schéma). On a vu que le PEP est l’endroit du code qui doit connaître les règles de gestion liées à l’autorisation. Mais comment faire si je souhaite utiliser ces règles de gestion à un autre endroit du code ?

Imaginons que mon API gère des images et que chaque image correspond à une ressource distincte dans Keycloak. Je peux tout à fait associer des politiques d’accès différentes à chaque image, ce qui peut être très pratique, et UMA va me permettre très simplement d’autoriser ou refuser l’accès d’un utilisateur à une ressource.

Néanmoins, comment afficher à un utilisateur l’ensemble des images qu’il peut voir ? Ou, plus compliqué, la liste paginée des images faisant moins de 15 ko ? Faire cela revient à calculer une sorte de jointure entre des informations contenues dans Keycloak et des informations contenues dans votre base métier, ce qui n’est pas possible simplement. La seule solution est de le faire en deux temps :

  • récupérer l’ensemble des ressources auxquelles l’utilisateur à accès
  • faire une requête en base en filtrant avec les ids des ressources retournées par Keycloak

(on peut également faire l’inverse : récupérer les ressources qui correspondent aux critères, puis demander à Keycloak de filtrer ces ressources).

Cette jointure entre des données métier et des données de Keycloak a une chance de fonctionner uniquement si le résultat de l’évaluation de toutes les permissions pour tous les utilisateurs tient dans le cache Keycloak. Si vous avez beaucoup d’utilisateurs et beaucoup de ressources, cela ne fonctionnera pas.

Si vous êtes dans cette situation, n’essayez pas de dupliquer des informations d’autorisation dans votre base métier et dans Keycloak, ce n’est jamais une bonne idée. Vous êtes alors dans un cas où vous devez gérer les autorisations dans votre application. Attention, cela ne vous interdit pas de conserver Keycloak pour l’authentification de vos utilisateurs.

Pour finir

Gérer les autorisations des utilisateurs est une problématique que l’on retrouve dans toutes les applications. Dans beaucoup de cas, les besoins sont simples et ne justifient pas la mise en oeuvre d’un serveur d’autorisation (cas de RBAC sur un nombre restreint et stable de paths).

Par contre, dès que les besoins deviennent plus compliqués, une solution comme Keycloak permet de sortir la partie “autorisation” de votre code métier. La spécification UMA permet même d’implémenter des workflows d’autorisation où les utilisateurs sont libres de gérer eux-mêmes l’accès à leurs ressources.

Le choix de la mise en oeuvre doit être en adéquation avec votre besoin, n’hésitez pas à utiliser le diagramme de la section précédente pour vous poser les bonnes questions !

Liens

Derniers articles