web-dev-qa-db-fra.com

Authentification avec oidc-client.js et Identityserver4 dans un frontend React

Dernièrement, j'essaie de configurer l'authentification à l'aide d'IdentityServer4 avec un client React. J'ai suivi le Adding a JavaScript client tutoriel (en partie) de la documentation IdentityServer: https://media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdf utilisant également le Quickstart7_JavaScriptClient fichier.

L'inconvénient est que j'utilise React comme mon front-end et ma connaissance de React n'est pas assez bonne pour implémenter la même fonctionnalité utilisée dans le tutoriel) en utilisant React.

Néanmoins, j'ai commencé à lire et j'ai essayé de commencer de toute façon. Mon projet IdentityServer et mon API sont configurés et semblent fonctionner correctement (également testés avec d'autres clients).

J'ai commencé par ajouter le oidc-client.js à mon projet Visual Code. Ensuite, j'ai créé une page qui est rendue au début (appelée Authentication.js) et c'est l'endroit où les boutons Connexion, API d'appel et Déconnexion sont inclus. Cette page (Authentication.js) se présente comme suit:

import React, { Component } from 'react';
import {login, logout, api, log} from '../../testoidc'
import {Route, Link} from 'react-router';

export default class Authentication extends Component {
    constructor(props) {
      super(props);
    }

    render() {
      return (
        <div>
            <div>  
                <button id="login" onClick={() => {login()}}>Login</button>
                <button id="api" onClick={() => {api()}}>Call API</button>
                <button id="logout" onClick={() => {logout()}}>Logout</button>

                <pre id="results"></pre>

            </div>  

            <div>
                <Route exact path="/callback" render={() => {window.location.href="callback.html"}} />

                {/* {<Route path='/callback' component={callback}>callback</Route>} */}
            </div>
        </div>
      );
    }
  }

Dans le fichier testoidc.js (qui est importé ci-dessus), j'ai ajouté toutes les fonctions oidc qui sont utilisées (app.js dans les exemples de projets). La partie route devrait rendre le callback.html disponible, j'ai laissé ce fichier tel quel (ce qui est probablement faux).

Le fichier testoidc.js contient les fonctions suivantes:

import Oidc from 'oidc-client'


export function log() {
  document.getElementById('results').innerText = '';

  Array.prototype.forEach.call(arguments, function (msg) {
      if (msg instanceof Error) {
          msg = "Error: " + msg.message;
      }
      else if (typeof msg !== 'string') {
          msg = JSON.stringify(msg, null, 2);
      }
      document.getElementById('results').innerHTML += msg + '\r\n';
  });
}

var config = {
  authority: "http://localhost:5000",
  client_id: "js",
  redirect_uri: "http://localhost:3000/callback.html",
  response_type: "id_token token",
  scope:"openid profile api1",
  post_logout_redirect_uri : "http://localhost:3000/index.html",
};
var mgr = new Oidc.UserManager(config);

mgr.getUser().then(function (user) {
  if (user) {
      log("User logged in", user.profile);
  }
  else {
      log("User not logged in");
  }
});

export function login() {
  mgr.signinRedirect();
}

export function api() {
  mgr.getUser().then(function (user) {
      var url = "http://localhost:5001/identity";

      var xhr = new XMLHttpRequest();
      xhr.open("GET", url);
      xhr.onload = function () {
          log(xhr.status, JSON.parse(xhr.responseText));
      }
      xhr.setRequestHeader("Authorization", "Bearer " + user.access_token);
      xhr.send();
  });
}

export function logout() {
  mgr.signoutRedirect();
} 

Il y a plusieurs choses qui vont mal. Lorsque je clique sur le bouton de connexion, je suis redirigé vers la page de connexion de l'identitéServer (ce qui est bien). Lorsque je me connecte avec des informations d'identification valides, je suis redirigé vers mon React app: http: // localhost: 3000/callback.html # id_token = Token

Ce client dans le projet d'identité est défini comme suit:

new Client
                {
                    ClientId = "js",
                    ClientName = "JavaScript Client",
                    AllowedGrantTypes = GrantTypes.Implicit,
                    AllowAccessTokensViaBrowser = true,

                    // where to redirect to after login
                    RedirectUris = { "http://localhost:3000/callback.html" },

                    // where to redirect to after logout
                    PostLogoutRedirectUris = { "http://localhost:3000/index.html" },

                    AllowedCorsOrigins = { "http://localhost:3000" },
                    AllowedScopes =
                    {
                        IdentityServerConstants.StandardScopes.OpenId,
                        IdentityServerConstants.StandardScopes.Profile,
                        "api1"
                    }

                }

Bien qu'il semble que la fonction de rappel ne soit jamais appelée, elle reste simplement sur l'URL de rappel avec un jeton très long derrière elle.

De plus, la fonction getUser affiche toujours 'Utilisateur non connecté' après la connexion et le bouton Call API continue de dire qu'il n'y a pas de jeton. Alors, évidemment, les choses ne fonctionnent pas correctement. Je ne sais simplement pas sur quels points ça tourne mal. Lors de l'inspection, je peux voir qu'un jeton est généré dans le stockage local:

enter image description here

De plus, lorsque je clique sur le bouton de déconnexion, je suis redirigé vers la page de déconnexion de l'hôte d'identité, mais lorsque je clique sur déconnexion, je ne suis pas redirigé vers mon client.

Mes questions sont:

  • Suis-je sur la bonne voie pour implémenter le client oidc en combinaison avec IdentityServer4?
  • Suis-je en utilisant les bibliothèques correctes ou réagit nécessite des bibliothèques différentes de celle oidc-client.js.
  • Existe-t-il un tutoriel où un front-end React est utilisé en combinaison avec IdentityServer4 et le client oidc (sans redux), je n'en ai pas trouvé.
  • Comment/où ajouter le callback.html, doit-il être réécrit?

Quelqu'un pourrait-il m'orienter dans la bonne direction, il y a très probablement plus de choses qui vont mal ici mais pour le moment je suis juste coincé par où commencer.

15
Nicolas

IdentityServer4 n'est qu'une implémentation d'arrière-plan d'OIDC; ainsi, tout ce que vous devez faire est d'implémenter le flux dans le client à l'aide des API données. Je ne sais pas ce qu'est le fichier oidc-client.js mais il fait probablement la même chose que vous auriez pu implémenter vous-même. Le flux lui-même est très simple:

  1. L'application React prépare la demande et redirige l'utilisateur vers le serveur Auth avec client_id et redirect_uri (et état, nonce)
  2. IdentityServer vérifie si le client_id et redirect_uri rencontre.
    • Si l'utilisateur n'est pas connecté, affichez une boîte de connexion
    • Si un formulaire de consentement est nécessaire (similaire à lorsque vous vous connectez via Facebook/Google dans certaines applications), montrez les interactions nécessaires
    • Si l'utilisateur est authentifié et autorisé, redirigez la page vers le redirect_uri avec de nouveaux paramètres. Dans votre cas, l'URL ressemblera à ceci: https://example.com/cb#access_token=...&id_token=...&stuff-like-nonce-and-state
  3. Maintenant, l'application React doit analyser l'URL, accéder aux valeurs et stocker le jeton quelque part à utiliser dans les demandes futures:

Le moyen le plus simple de réaliser la logique consiste à définir d'abord un itinéraire dans le routeur qui se résout en un composant qui fera la logique. Ce composant peut être "invisible". Il n'a même pas besoin de rendre quoi que ce soit. Vous pouvez définir l'itinéraire comme ceci:

<Route path="/cb" component={AuthorizeCallback} />

Ensuite, implémentez la logique client OIDC dans le composant AuthorizeCallback. Dans le composant, il vous suffit d'analyser l'URL. Vous pouvez utiliser location.hash pour accéder à #access_token=...&id_token=...&stuff-like-nonce-and-state partie de l'URL. Vous pouvez utiliser RLSearchParams ou une bibliothèque tierce comme qs . Ensuite, stockez simplement la valeur quelque part (sessionStorage, localStorage et, si possible, cookies). Tout ce que vous faites n'est que des détails de mise en œuvre. Par exemple, dans l'une de mes applications, afin de mémoriser la page active sur laquelle se trouvait l'utilisateur dans l'application, je stocke la valeur dans sessionStorage, puis j'utilise la valeur de ce stockage dans AuthorizeCallback pour rediriger l'utilisateur vers la bonne page. Ainsi, le serveur Auth redirige vers "/ cb" qui se résout en AuthorizeCallback et ce composant redirige vers l'emplacement souhaité (ou "/" si aucun emplacement n'a été défini) en fonction de l'emplacement de l'utilisateur.

N'oubliez pas également que si le cookie de session du serveur d'autorisation n'est pas expiré, vous n'aurez pas besoin de vous reconnecter si le jeton a expiré ou supprimé. C'est utile si le jeton a expiré, mais cela peut être problématique lorsque vous vous déconnectez. C'est pourquoi lorsque vous vous déconnectez, vous devez envoyer une demande au serveur d'autorisation pour supprimer/expirer le jeton immédiatement avant de supprimer le jeton de votre stockage.

1
Gasim