Gérer l’authentification dans une Architecture Moderne - Partie 2

Dans la première partie de cet article, nous avons abordé les problématiques associées à l’authentification de l’utilisateur en restant essentiellement au sein du SI de notre startup fictive : la World Company. Dans cette deuxième partie, nous allons nous ouvrir à des problématiques liées à des échanges avec des partenaires.

Suivant la taille et l’organisation de votre SI, ces mêmes principes peuvent s’appliquer même à l’intérieur de votre entreprise. Des exigences de sécurité fortes peuvent aussi justifier l’emploi des méthodes de délégation décrites.

L’architecture de la World Company se complexifie

L’application de la World Company que nous avons vue dans la première partie de ce post a trouvé son public. Pour assurer la croissance de son service, la World Company a ouvert son application à plusieurs partenaires :

Blog-Authent

Ce deuxième schéma montre plusieurs nouvelles interactions effectuées avec des partenaires :

  • Certains partenaires consomment les services de la World Company :
    • Les utilisateurs finaux de la World Company peuvent déléguer tout ou partie de leurs droits à une application tierce. C’est le cas de "Acme Inc." qui après avoir demandé à l’utilisateur cette délégation peut lire ou écrire les données de l’utilisateur gérées par la World Company (exemple : poster sur le mur de l’utilisateur).
    • L’application de gestion interne de Nice & Little Company appelle quant à elle pour son propre compte les services de la World Company.
  • La World Company consomme, pour sa part, des services de certains partenaires :
    • C’est le cas en particulier de “Wonderful Cloud” pouvant héberger des données de ses utilisateurs.
      Ici c’est à la World Company que l’utilisateur doit déléguer les droits d’effectuer des appels en son nom auprès de “Wonderful Cloud”.
    • Malheureusement “Wonderful Cloud” n’utilise pas “Auth For Everyone” ( l’IdP utilisé par la World Company ) mais un autre Identity Provider ( “GAFA Corp” ) qui ne permet pas d’utiliser l’identité de "Auth For Everyone" : ce n’est pas bloquant mais les utilisateurs doivent se réauthentifier auprès de GAFA Corp avant de pouvoir déléguer l’accès à leurs données à la World Company.

À la lumière de ces nouveaux besoins, la discussion sur certaines préoccupations du précédent post doit être complétée et de nouveaux points apparaissent :

  • la propagation de l’identité de l’utilisateur hors du SI.
    • et comment gérer des référentiels utilisateurs distincts dans ce cadre.
  • la délégation d’autorisation d’accès.
  • l’authentification des services appelants.

Propagation de l’identité

Notre architecture fictive expose maintenant deux autres cas de propagation de l’identité de l’utilisateur :

  • d’un module de la World Company vers un partenaire
  • d’un partenaire vers World Company.

Les contraintes sont toutefois différentes dorénavant. Un aspect important à prendre en compte lors du choix de la stratégie de propagation est la relation de confiance établie entre les deux systèmes communicants. Cette relation de confiance doit de plus être nuancée par le risque d’une compromission d’un des tiers : vol de token, voire prise de contrôle (et malheureusement, l’un ou l’autre de ces risques peut entraîner des contre-mesures divergentes…).

Ré-étudions les stratégies évoquées dans le dernier post :

  • le partage d’un jeton d’accès initial : KO
    Tout d’abord, l’usage de cette stratégie avec un tiers qui ne serait pas d’une totale confiance est inenvisageable : elle lui permettrait de se faire passer pour l’utilisateur auprès de notre SI.

    Cette stratégie n’est pas non plus adaptée pour propager l’identité vers un tiers de confiance. En effet, comme nous l’avons évoqué, pour éviter les risques de détournement d’usage d’un jeton, le service appelé doit vérifier qu’il est bien dans l’audience du token ( qui ici ne pourra pas contenir le service tiers ). Sans parler du couplage technique important entre les deux systèmes qui en résulterait. Cette stratégie ne peut être une option qu’au sein d’un ensemble de modules constituant une “application”.

  • la création d’un token dédié à l’appel entre les deux systèmes :
    L’identité peut être transmise d’un système à l’autre dans un token JWT spécifique signé par le système appelant (dans le monde WS-*, ce jeton prend la forme d’une assertion SAML passée dans l’entête SOAP).

    Cette méthode est en particulier adaptée aux cas de relation de confiance unidirectionnelle : le service appelé fait confiance à l’appelant mais pas l’inverse. Le jeton ainsi créé, valable uniquement pour l’appel vers le service tiers, n’est pas utilisable pour se faire passer pour l’utilisateur auprès de l’appelant.

    La durée de validité de ce token ( champ “exp” : expiration time ; du token JWT ) sera typiquement très courte ce qui limite d’autant plus l’impact de l’interception de ce token. Le système appelant a la main sur le contenu du token et peut ainsi limiter le passage d’information à ce qui est strictement nécessaire.

    Dans cette configuration, le service appelant construit et signe le token et le service appelé s’assure que la signature provient bien de cet émetteur ( auquel il fait confiance ). Cela ne permet pas de se prémunir de la compromission de l’émetteur. Si ce risque nécessite d’être traité : il faut s’orienter vers des stratégies mettant une oeuvre un échange de token (le plus souvent) auprès de l’Identity Provider : qui pourra servir d’intermédiaire hautement sécurisé.
    Cela introduit une complexité plus élevé et les normes ne sont pas toutes prêtes ( ex : OAuth 2.0 Token Exchange qui pourrait être une piste pour cela est encore à l’état de draft ) et donc encore peu implémentées. Leurs usages entre deux partenaires posant probablement des complexités supplémentaires.

  • propagation applicative sans signature particulière :
    Cette stratégie décrite dans le premier post est utilisable dans les mêmes conditions. Si le service appelant s’authentifie auprès du service appelé et que le service appelé lui fait confiance : l’identité de l’utilisateur final peut faire partie de la payload applicative. Là encore si l’appelant est compromis, cela n’offre aucun moyen de se prémunir d’un appel falsifié.

Quelle que soit la façon de mettre en place les stratégies ci-dessus, on notera que l’utilisateur final n’a pas vraiment eu son mot à dire. Les systèmes ont beau partager une certaine relation de confiance, l’utilisateur n’a pas forcément donné son consentement : dans certains cas d’usage, cela peut être problématique.

Une autre approche permet de traiter ce point ainsi que le manque de confiance :

  • la délégation d’autorisation via OAuth2
    Comme nous allons le voir dans le paragraphe suivant, cette délégation OAuth2 est associée à l’identification de l’utilisateur ( le Resource Owner ) et porte donc l’identité qui nous intéresse ici.

    Cela nécessite comme indiqué l’implication de l’utilisateur qui devra accepter la délégation ( consentement ) : c’est souvent une bonne chose mais cela limite potentiellement les cas d’usage. Un des intérêts de cette stratégie est qu’elle permet la propagation d’identité même si les partenaires ont une relation de confiance faible ( mais généralement pas totalement inexistante, car l’appelant doit être un “client” connu de l’Authorization Server émettant le token ).

    À noter : le token OAuth2 utilisé alors pour appeler le service tiers n’est pas le même que le jeton d’accès initial à notre SI (s’il s’avère qu’il est lui aussi un token OAuth2).

    Le lecteur averti aura déjà noté qu’on n’obtiendra pas une propagation d’identité à proprement parler ( en plus de la délégation d’autorisation … ) : en effet le token OAuth2 est associé à l’identité de l’utilisateur géré par l’Identity Provider du système cible : ce n’est pas forcément l’identité sous laquelle l’utilisateur utilise le service appelant.

Le schéma d’architecture de la World Company proposé au début de ce billet illustre l’usage de cette stratégie pour les deux cas d’appels :

  • Acme Inc. ( après avoir identifié l’utilisateur de son côté ; ou pas d’ailleurs : on peut imaginer des use cases où Acme Inc. n’aurait pas besoin d’identifier les utilisateurs ) accède aux ressources de l’utilisateur hébergées par la World Company de cette manière.
  • La World Company, de la même manière, peut demander à l’utilisateur de lui déléguer l’accès en son nom à des services tiers : c’est le cas de l’appel à “Wonderful Cloud” illustré dans le schéma.

Délégation d’autorisation

La délégation d’autorisation introduite précédemment nécessite donc la mise en oeuvre du “framework d’autorisation” OAuth2 :

Une introduction détaillée est disponible sur le site de Digital Ocean par exemple.

En deux mots, OAuth2 permet de mettre en relation quatre “acteurs” :

  • l’utilisateur final ( le Resource Owner ) : le propriétaire des “données”,
  • le Client ( l’application qui veut obtenir un token ) : un service tiers ou une application SPA ou mobile,
  • l’Authorization Server (AS) qui émet les tokens d’accès,
  • le Resource Server (RS) qui héberge des services dont l’appel doit être sécurisé.

Le but d’OAuth2 est de fournir au Client (=application consommatrice), un access token qui lui permet d’appeler le Resource Server au nom de l’utilisateur final (= Resource Owner) qui lui délègue donc ses droits.
Cet access token est généralement associé à un scope, qui permet de limiter la délégation à une partie des droits et/ou des ressources de l’utilisateur final. Par exemple, dans le cas de Google, via ce mécanisme un utilisateur peut déléguer à une application tierce : uniquement l’accès en lecture seule à son calendrier ou bien uniquement l’accès en lecture/écriture à son Drive.

Lors de l’obtention de l’access token, l’utilisateur Resource Owner doit bien évidemment s’authentifier auprès de l’Authorization Server (dans notre architecture cette authentification est déléguée mais ce n’est pas obligatoire).

Lorsque le Resource Server reçoit un appel accompagné de l’access token, il est généralement capable d’obtenir en collaboration avec l’Authorization Server :

  • l’identité de l’utilisateur final,
  • l’identité du client qui a reçu la délégation de droit,
  • le scope.

Dans la première partie de ce billet, seule l’identité de l’utilisateur final nous importait : l’usage de OAuth2 pouvait donc paraître “overkill”. Ce n’est pas tout à fait vrai : il nous permettait de bénéficier d’un mécanisme standard (nous permettant d’utiliser des produits et frameworks éprouvés du marché) gérant en particulier l’expiration voire le refresh du token (pour le mobile) de manière sécurisée et robuste. De plus, maintenant sans changer de protocole, ce même mécanisme nous permet de proposer à des partenaires comme Acme Inc de nous appeler au nom de nos utilisateurs.

Dans notre architecture d’exemple : le Resource Server de la World Company est la gateway : comme nous l’avons vu, la propagation de l’identité aux micro-services sous-jacents peut s’effectuer de différentes façons, pas forcément avec l’access token OAuth2.

OAuth2 propose plusieurs méthodes pour obtenir un access_token : on les appelle les grant_type. Le tutorial de Digital Ocean, mentionné ci-dessus, décrit très bien les principaux d’entre eux.

On trouvera :

  • Dans le cas général et en particulier dans le cadre de l’explication précédente, le grant_type à utiliser est “authorization code”.
  • Nous avons mentionné la fois dernière que le grant_type “implicit” était adapté aux applications SPA.
  • Nous verrons plus loin l’usage du grant_type “client_credentials” dans le cas particulier où le Client et le Resource Owner sont la même entité.
  • Le grant_type “refresh_token” permet d’obtenir, sous certaines conditions, un nouvel access token avec une nouvelle date d’expiration.
  • Le dernier grant_type classique est “Resource owner password credentials” : il doit être réservé aux Client sous votre contrôle total car il nécessite que l’utilisateur fournisse son mot de passe au Client.

D’autres grant_type existent, ils ont été ajoutés à OAuth2 pour des cas plus spécifiques (par exemple pour l'usage d'assertion SAML pour identifier l'utilisateur)

Liens entre des services n’ayant pas le même référentiel d’utilisateur

Différents services n’utilisent pas forcément le même référentiel d’identité ( le même Identity Provider ) : c’est le cas dans notre exemple de la World Company et de “Wonderful Cloud”.

En dehors de tout mécanisme de IdP Discovery et de Fédération d’identité, on ne peut alors malheureusement pas proposer de SSO à l’utilisateur : ce n’est pas bloquant et c’est plutôt classique sur le web.

Cela n’empêche absolument pas la délégation d’autorisation :

  • la World Company (ayant authentifié Bob dans son propre référentiel d’utilisateur) peut le rediriger vers l’Authorization Server de “Wonderful Cloud” pour l’obtention de l’access token.
  • l’Authorization Server de “Wonderful Cloud” authentifie Bob dans son propre référentiel (ici en déléguant à l’Identity Provider de GAFA Corp) avant de fournir l’access token à la World Company (après avoir obtenu le consentement de Bob).
    • On notera que rien n’oblige Bob à s’authentifier sous le nom de “Bob” : il peut utiliser son compte perso ou pro ou un compte alternatif qu’il s’est créé à part ou le compte de son amie Alice pour bénéficier de l’abonnement de cette dernière chez Wonderful Cloud...
  • Il est alors possible à la World Company de créer un lien entre l’utilisateur et l’accès qu’il lui a donné sur le service tiers en stockant l’access token (et le refresh token associé) dans les données associées à l’utilisateur :
    • la World Company n’a pas besoin de connaître l’identité précise utilisée par Bob auprès de “Wonderful Cloud” : ce qui peut être bénéfique en terme de maintien de la vie privée pour Bob.

Appels de service à service

Il n’est pas toujours nécessaire, entre partenaires, d’appeler un service “au nom d’un utilisateur” ou “sous l’identité de cet utilisateur”.

C’est le cas dans notre exemple de l’application de gestion interne de Nice & Little Company qui doit pouvoir s’interfacer avec la World Company et effectuer des appels pour son propre compte (disons pour mettre à jour un stock, envoyer une facture…).

Il reste toutefois nécessaire d’authentifier le service appelant lui-même pour contrôler que l’accès est bien autorisé.

Comme nous l’avons vu dans la première partie, cet aspect a aussi du sens au sein du même SI (et peut alors être un pré-requis pour propager applicativement l’identité de l’utilisateur final).

Le besoin est ancien et classique : aux solutions établies depuis longtemps s’ajoutent des méthodes plus récentes :

  • authentification HTTP classique :
    • Basic Auth / Digest Auth,
  • certificat client :
    • dans le cadre d’une authentification TLS mutuelle le plus souvent,
  • API Key (ou API Token) :
    • à partir du moment où chaque client potentiel obtient une clef d’API qui lui est dédiée,
  • OAuth2 via le grant_type client_credentials :
    • comme indiqué dans ce grant_type particulier le Resource Owner est le Client lui-même,
    • il suffit au client de présenter uniquement son couple client_id/client_secret (qui sert déjà à identifier le Client dans la plupart des autres grant_type) pour obtenir un access token valide,
    • bien sûr charge au Resource Server de gérer le fait que l’access token est associé à un Client et non un utilisateur final : et de lui donner accès (ou non) aux différents services qu’il propose,
  • certaines plateformes ont des méthodes spécifiques :
    • citons en particulier AWS Signature qui sécurise l’accès à toutes les APIs de management de AWS.

Conclusion

Dans cette second partie nous avons abordé plus en détail les problématiques associées aux appels de service entre tiers. Nous avons insisté sur la notion de confiance entre ces services qui est un critère majeur dans le choix des stratégies qui s’offrent à nous.

Nous avons en particulier montré que OAuth2 permettait de répondre à différents cas d’usage de manière uniforme :

  • maintien d’un contexte d’authentification avec le SI (comme abordé dans la 1ère partie),
  • délégation des droits des utilisateurs à des tiers,
  • authentification d’un service appelant.

Il n’est toutefois pas toujours adapté aux besoins et contraintes : souvent un simple token JWT répondra de manière simple et efficace à un besoin de propagation d’identité si les conditions le permettent.