web-dev-qa-db-ja.com

Reactフロントエンドでのoidc-client.jsおよびIdentityserver4による認証

最近、ReactクライアントでIdentityServer4を使用して認証をセットアップしようとしています。IdentityServerドキュメントのAdding a JavaScript clientチュートリアル(一部)を実行しました: https:/ /media.readthedocs.org/pdf/identityserver4/release/identityserver4.pdfQuickstart7_JavaScriptClientファイルも使用します。

欠点は、Reactをフロントエンドとして使用していることと、Reactに関する私の知識が、チュートリアルで使用されているのと同じ機能を実装するのに十分ではないことです。 Reactを使用します。

それにもかかわらず、私は読み始め、とにかくそれを使い始めようとしました。 IdentityServerプロジェクトとAPIが設定され、正しく機能しているようです(他のクライアントでもテストされています)。

まず、oidc-client.jsをビジュアルコードプロジェクトに追加しました。次に、最初にレンダリングされるページ(Authentication.jsという名前)を作成しました。ここには、ログイン、コールAPI、ログアウトボタンが含まれています。このページ(Authentication.js)は次のようになります。

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>
      );
    }
  }

Testoidc.jsファイル(上記でインポートしたもの)に、使用するすべてのoidc関数(サンプルプロジェクトのapp.js)を追加しました。ルート部分は、callback.htmlを使用可能にする必要があります。そのファイルはそのままにしておきます(これはおそらく間違っています)。

Testoidc.jsファイルには、次のような関数が含まれています。

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();
} 

うまくいかないことがいくつかあります。ログインボタンをクリックすると、identityServerのログインページにリダイレクトされます(これで問題ありません)。有効な認証情報でログインすると、React app: http:// localhost:3000/callback.html#id_token = Token にリダイレクトされます

Identityプロジェクトのこのクライアントは、次のように定義されています。

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"
                    }

                }

コールバック関数が呼び出されることはないようですが、非常に長いトークンが後ろにあるコールバックURLに留まっています。

また、ログイン後、getUser関数は「ログインしていないユーザー」を表示し続け、「APIを呼び出す」ボタンはトークンがないことを示し続けます。したがって、明らかに物事は正しく機能していません。私はそれがどこで間違っているのかわからない。検査すると、ローカルストレージに生成されたトークンがあることがわかります。

enter image description here

また、ログアウトボタンをクリックすると、Identity Hostのログアウトページにリダイレクトされますが、ログアウトをクリックすると、クライアントにリダイレクトされません。

私の質問は:

  • IdentityServer4と組み合わせてoidc-clientを実装することは正しい軌道に乗っていますか?
  • 正しいライブラリを使用していますか、それともoidc-client.jsとは異なるライブラリが必要ですか。
  • 反応フロントエンドがIdentityServer4とoidc-client(reduxなし)と組み合わせて使用​​されるチュートリアルはありますか、見つかりませんでした。
  • どのように/どこでcallback.htmlを追加しますか、それを書き直すべきですか?

誰かが私を正しい方向に向けることができますか?おそらくここで間違っていることがもっとありますが、現時点ではどこから始めればいいのか悩んでいます。

15
Nicolas

IdentityServer4は、OIDCのバックエンド実装にすぎません。そのため、指定するAPIを使用してクライアントにフローを実装するだけで済みます。 oidc-client.jsファイルが何であるかはわかりませんが、おそらく自分で実装したのと同じことをしています。フロー自体は非常に単純です。

  1. Reactアプリはリクエストを準備し、client_idredirect_uri(および状態、ノンス)を使用してユーザーを認証サーバーにリダイレクトします
  2. IdentityServerは、client_idredirect_uriが一致するかどうかを確認します。
    • ユーザーがログインしていない場合は、ログインボックスを表示する
    • 同意フォームが必要な場合(一部のアプリでFacebook/Google経由でログインする場合と同様)、必要なやり取りを示します
    • ユーザーが認証および承認されている場合は、ページをredirect_uriに新しいパラメーターでリダイレクトします。あなたの場合、URLは次のようになります:https://example.com/cb#access_token=...&id_token=...&stuff-like-nonce-and-state
  3. Reactアプリは、URLを解析し、値にアクセスし、将来のリクエストで使用されるトークンをどこかに保存する必要があります。

ロジックを実現する最も簡単な方法は、まずロジックを実行するコンポーネントに解決するルートをルーターに設定することです。このコンポーネントは「非表示」にすることができます。何もレンダリングする必要さえありません。次のようにルートを設定できます:

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

次に、AuthorizeCallbackコンポーネントにOIDCクライアントロジックを実装します。コンポーネントでは、URLを解析するだけです。 location.hash を使用して、URLの#access_token=...&id_token=...&stuff-like-nonce-and-state部分にアクセスできます。 RLSearchParams または qs のようなサードパーティのライブラリを使用できます。次に、値をどこかに保存します(sessionStorage、localStorage、および可能であればCookie)。他に行うことは、実装の詳細です。たとえば、私のアプリの1つで、ユーザーがアプリでアクセスしていたアクティブページを記憶するために、sessionStorageに値を保存し、そのストレージの値をAuthorizeCallbackに使用して、ユーザーを適切なページ。したがって、認証サーバーはAuthorizeCallbackに解決される「/ cb」にリダイレクトし、このコンポーネントはユーザーの場所に基づいて目的の場所(場所が設定されていない場合は「/」)にリダイレクトします。

また、承認サーバーのセッションCookieの有効期限が切れていない場合、トークンの有効期限が切れているか、トークンが削除されていても、再ログインする必要はありません。これは、トークンの有効期限が切れている場合に役立ちますが、ログアウトすると問題が発生する可能性があります。そのため、ログアウトするときに、ストレージからトークンを削除する直前に、トークンを削除/期限切れにするリクエストを承認サーバーに送信する必要があります。

1
Gasim