Il y a quelques mois, je travaillais à la mise en place d'un serveur OpenID Connect pour gérer l'accès à des centaines de nos applications internes. Nous sommes passés de nos propres solutions, pratiques à petite échelle, à une norme largement adoptée. L'accès via un service central simplifie considérablement les opérations monotones, réduit les coûts de mise en œuvre des autorisations, permet de trouver de nombreuses solutions prêtes à l'emploi et évite de se creuser la tête lors du développement de nouvelles. Dans cet article, je parlerai de cette transition et des difficultés que nous avons rencontrées.

Il était une fois… Les débuts
Il y a plusieurs années, lorsque le nombre d'applications internes est devenu trop élevé pour une gestion manuelle, nous avons développé une application pour contrôler les accès au sein de l'entreprise. C'était une application simple développée avec Rails, qui se connectait à une base de données contenant des informations sur les employés, où les accès à différentes fonctionnalités étaient configurés. À ce moment-là, nous avons également mis en place notre premier SSO, basé sur la vérification des jetons entre le client et le serveur d'autorisation, le jeton étant transmis sous forme cryptée avec plusieurs paramètres et vérifié sur le serveur d'autorisation. Ce n'était pas la solution la plus pratique, car chaque application interne devait intégrer une couche de logique considérable, et les bases de données des employés devaient être synchronisées avec le serveur d'autorisation.
Après un certain temps, nous avons décidé de simplifier la tâche d'autorisation centralisée. Nous avons transféré le SSO sur un équilibrage de charge. Avec OpenResty en Lua, nous avons ajouté un modèle qui vérifiait les jetons, savait vers quelle application la demande était envoyée et pouvait vérifier si l'accès y était autorisé. Cette approche a considérablement simplifié la gestion des accès aux applications internes : dans le code de chaque application, il n'était plus nécessaire d'ajouter une logique supplémentaire. En fin de compte, nous avons fermé le trafic à l'extérieur, et l'application elle-même était complètement ignorante des autorisations.
Cependant, un des problèmes reste non résolu. Que faire des applications qui ont besoin d'informations sur les employés ? Il aurait été possible de créer une API pour le service d'authentification, mais cela aurait nécessité d'ajouter une logique supplémentaire pour chaque application de ce type. De plus, nous voulions nous débarrasser de la dépendance à notre propre application, qui est destinée à être transférée en OpenSource, ainsi que de notre serveur d'authentification interne. Nous en parlerons un autre jour. La solution aux deux problèmes a été OAuth.
Aux normes acceptées
OAuth est une norme d'authentification compréhensible et largement adoptée, mais comme sa seule fonctionnalité ne suffisait pas, nous avons également commencé à envisager OpenID Connect (OIDC). OIDC, en lui-même, est la troisième implémentation d'une norme d'authentification ouverte qui s'est développée en tant que surcouche au protocole OAuth 2.0 (protocole d'authentification ouvert). Cette solution résout le problème du manque de données sur l'utilisateur final, et permet également de changer de fournisseur d'authentification.
Cependant, nous n'avons pas choisi un fournisseur particulier et avons décidé d'ajouter une intégration avec OIDC à notre serveur d'authentification existant. La flexibilité d'OIDC en matière d'authentification des utilisateurs finaux a soutenu cette décision. Cela a ainsi permis de mettre en place la prise en charge d'OIDC sur notre serveur d'authentification actuel.

Notre démarche de mise en œuvre d'un serveur OIDC propre
1) Nous avons formaté les données de manière appropriée
Pour l'intégration OIDC, il est nécessaire de formater les données actuelles des utilisateurs dans un format compréhensible par la norme. Dans OIDC, cela s'appelle Claims. Les claims sont essentiellement les champs finaux dans la base de données des utilisateurs (nom, email, téléphone, etc.). Il existe , et tout ce qui n'entre pas dans cette liste est considéré comme personnalisé. Ainsi, le premier point auquel il faut prêter attention si vous souhaitez choisir un fournisseur OIDC existant est la possibilité de personnaliser facilement de nouveaux claims.
Un groupe de claims est regroupé dans un sous-ensemble appelé Scope. Lors de l'authentification, la demande d'accès ne se fait pas à des claims spécifiques, mais bien à des scopes, même si une partie des claims du scope n'est pas nécessaire.
2) Nous avons mis en œuvre les Grants nécessaires
La prochaine étape de l'intégration OIDC consiste à choisir et à mettre en œuvre des types d'autorisation, appelés grants. Le grant choisi déterminera le scénario d'interaction entre l'application sélectionnée et le serveur d'autorisation. Le schéma approximatif de choix du grant approprié est présenté dans l'illustration ci-dessous.

Pour notre première application, nous avons utilisé le grant le plus courant : le Code d'Autorisation. Sa particularité par rapport aux autres est qu'il s'agit d'un processus en trois étapes, c'est-à-dire qu'il subit une vérification supplémentaire. D'abord, l'utilisateur fait une demande de permission d'autorisation, reçoit un jeton - le Code d'Autorisation, puis, avec ce jeton, comme un ticket de transport, demande un jeton d'accès. Tout l'interaction principale de ce scénario d'autorisation est basée sur des redirections entre l'application et le serveur d'autorisation. Vous pouvez lire plus sur ce grant. .
OAuth suit le principe que les jetons d'accès obtenus après l'autorisation doivent être temporaires et changés de préférence en moyenne toutes les 10 minutes. Le grant Code d'Autorisation implique un processus de vérification en trois étapes par le biais de redirections, effectuer ce processus toutes les 10 minutes, pour être honnête, ce n'est pas très agréable à l'œil. Pour résoudre ce problème, il existe un autre grant - le Refresh Token, que nous avons également utilisé. Ici, c'est plus simple. Lors de la vérification d'un autre grant, en plus du jeton d'accès principal, un autre jeton - le Refresh Token - est émis, qui ne peut être utilisé qu'une seule fois et dont la durée de vie est généralement beaucoup plus longue. Avec ce Refresh Token, lorsque le TTL (Time to Live) du jeton d'accès principal se termine, la demande d'un nouveau jeton d'accès sera envoyée à l'endpoint d'un autre grant. Le Refresh Token utilisé est immédiatement annulé. Ce processus de vérification est en deux étapes et peut être effectué en arrière-plan, sans que l'utilisateur ne s'en aperçoive.
3) Formats de sortie des données utilisateur configurés
Après que les subventions choisies ont été mises en œuvre, l'authentification fonctionne, il convient de mentionner l'obtention des données sur l'utilisateur final. Dans OIDC, il existe un endpoint séparé à cet effet, sur lequel, avec votre jeton d'accès actuel et s'il est valide, vous pouvez demander des données utilisateurs. Et si les données de l'utilisateur ne changent pas si souvent, mais que vous devez aller chercher les actuelles plusieurs fois, vous pouvez envisager une solution comme les jetons JWT. Ces jetons sont également pris en charge par la norme. Un jeton JWT se compose de trois parties : header (informations sur le jeton), payload (toutes les données nécessaires) et signature (le jeton est signé par le serveur et par la suite, l'origine de sa signature peut être vérifiée).
Dans l'implémentation OIDC, le jeton JWT est appelé id_token. Il peut être demandé en même temps que le jeton d'accès habituel et il ne reste plus qu'à vérifier la signature. Pour cela, le serveur d'autorisation a un endpoint distinct avec une paire de clés publiques au format . Et en parlant de cela, il convient de mentionner qu'il existe un autre endpoint qui, sur la base de la norme réfracte la configuration actuelle du serveur OIDC. Il contient toutes les adresses des endpoints (y compris l'adresse de la paire de clés publiques utilisées pour la signature), les claims pris en charge et les scopes, les algorithmes de chiffrement utilisés, les grants pris en charge, etc.
Par exemple chez Google :
{
"issuer": "https://accounts.google.com",
"authorization_endpoint": "https://accounts.google.com/o/oauth2/v2/auth",
"device_authorization_endpoint": "https://oauth2.googleapis.com/device/code",
"token_endpoint": "https://oauth2.googleapis.com/token",
"userinfo_endpoint": "https://openidconnect.googleapis.com/v1/userinfo",
"revocation_endpoint": "https://oauth2.googleapis.com/revoke",
"jwks_uri": "https://www.googleapis.com/oauth2/v3/certs",
"response_types_supported": [
"code",
"token",
"id_token",
"code token",
"code id_token",
"token id_token",
"code token id_token",
"none"
],
"subject_types_supported": [
"public"
],
"id_token_signing_alg_values_supported": [
"RS256"
],
"scopes_supported": [
"openid",
"email",
"profile"
],
"token_endpoint_auth_methods_supported": [
"client_secret_post",
"client_secret_basic"
],
"claims_supported": [
"aud",
"email",
"email_verified",
"exp",
"family_name",
"given_name",
"iat",
"iss",
"locale",
"name",
"picture",
"sub"
],
"code_challenge_methods_supported": [
"plain",
"S256"
],
"grant_types_supported": [
"authorization_code",
"refresh_token",
"urn:ietf:params:oauth:grant-type:device_code",
"urn:ietf:params:oauth:grant-type:jwt-bearer"
]
}Ainsi, grâce à l'id_token, il est possible de transmettre tous les claims nécessaires dans le payload du token et de ne pas avoir à se connecter à chaque fois au serveur d'autorisation pour demander les données de l'utilisateur. L'inconvénient de cette approche est que les modifications des données utilisateur du serveur ne sont pas immédiates, mais se font avec un nouveau token d'accès.
Bilan de la mise en œuvre
Ainsi, après la mise en œuvre de notre propre serveur OIDC et la configuration des connexions à celui-ci côté applications, nous avons résolu le problème de transmission des informations sur les utilisateurs.
Puisque OIDC est une norme ouverte, nous avons eu la possibilité de choisir un fournisseur existant ou de mettre en œuvre notre propre serveur. Nous avons essayé Keycloak, qui s'est révélé très pratique à configurer. Après avoir ajusté et modifié les configurations de connexion côté applications, il était prêt à fonctionner. Du côté des applications, il ne reste plus qu'à changer les configurations de connexion.
En parlant des solutions existantes
Dans le cadre de notre organisation, nous avons développé notre propre implémentation en tant que premier serveur OIDC, qui a été enrichie au fil du temps. Après un examen détaillé des autres solutions prêtes à l'emploi, on peut dire que c'est un sujet de débat. L'argument en faveur de la mise en œuvre de notre propre serveur est lié aux craintes des fournisseurs face à l'absence des fonctionnalités nécessaires, ainsi qu'à l'existence d'un ancien système dans lequel il y avait différentes authentifications personnalisées pour certains services et où beaucoup de données sur les employés étaient déjà stockées. Cependant, dans les implémentations prêtes à l'emploi, il existe des commodités pour l'intégration. Par exemple, Keycloak dispose de son propre système de gestion des utilisateurs et les données y sont stockées directement, et il n'est pas difficile de transférer ses propres utilisateurs là-bas. Pour cela, Keycloak propose une API qui permettra de mener à bien toutes les actions nécessaires au transfert.
Un autre exemple certifié, qui, à mon avis, est intéressant, est Ory Hydra. Ce qui le rend intéressant, c'est qu'il se compose de différents composants. Pour l'intégration, vous devrez lier votre service de gestion des utilisateurs à leur service d'autorisation et l'étendre au besoin.
Keycloak et Ory Hydra ne sont pas les seules solutions prêtes à l'emploi. Il est préférable de choisir une mise en œuvre certifiée par la OpenID Foundation. En général, ces solutions portent le logo de certification OpenID.

N'oubliez pas les fournisseurs payants existants si vous ne souhaitez pas gérer votre propre serveur OIDC. Aujourd'hui, il existe de nombreuses bonnes options.
Que faire ensuite
Dans un avenir proche, nous prévoyons de restreindre le trafic vers les services internes par un autre moyen. Nous planifions de migrer notre SSO actuel vers un load balancer utilisant OpenResty comme proxy, basé sur OAuth. Il existe également déjà de nombreuses solutions prêtes à l'emploi, par exemple :
Documents supplémentaires
– un bon service pour vérifier les jetons JWT
— une liste des implémentations OIDC certifiées
Source : habr.com
