Web技術がどのように進化しているにつれて、それに対応するセキュリティ方法とプロトコルも進化しています。OAuth 2.0とOpenID Connectのプロトコルは、新たなセキュリティ脅威とウェブアプリケーションの複雑さの増加に対応して、大きく進化しました。従来の認証方法は一度有効であったものが、現在、モダンなSPAにとって時代遅れとなっており、新しいセキュリティの挑戦に直面しています。この背景で、Backend-For-Frontend(BFF)のアーキテクチャパターンが推奨される解決策となっており、SPAとバックエンドシステムの間のインタラクションを組織することができ、認証とセッション管理においてより安全で、管理性が高い方法を提供しています。この記事はBFFパターンを深く探索し、.NETとReactを使用した最小限のソリューションを実装して、その実用的な適用を示します。最終的に、BFFパターンをどのように利用してwebアプリケーションのセキュリティと機能を向上させるかを明确に理解することができます。
歴史の背景
OAuth 2.0とOpenID Connectの歴史は、インターネット技術の進行にとって象徴的なものです。これらのプロトコルと、モダンなウェブアプリケーションに与えた影響についてより詳細に見てみましょう。
2012年に導入されたOAuth 2.0プロトコルは、認可において幅広く採用されている標準として進化しました。これにより、第三者のアプリケーションは、クライアントにユーザーの資格情報を露出することなく、ユーザーのリソースに限られたアクセスを取得することができます。OAuth 2.0は、それぞれ、幅広い用途に適したフローをサポートしています。
OAuth 2.0の基礎を建て、2014年にOpenID Connect (OIDC)プロトコルが出現し、基本的な認証機能を追加した。これは、クライアントアプリケーションに標準的な方法を提供して、ユーザーのIDを確認し、JWT (JSON Web Token)形式のIDトークンを取得することで標準化されたアクセスポイントを通じてまたはそれによって基本的な情報を得ることができます。
脅威モデルの進化
SPAの機能が増え、人気が上がるに伴い、SPA用の脅威モデルも進化しました。XSS(Cross-Site Scripting)とCSRF(Cross-Site Request Forgery)などの脆弱性がより一般的になりました。SPAはよくAPIを通じてサーバーとやりとりするため、アクセストークンとリフレッシュトークンを安全に保管して使用することは安全になるために重要です。
時代の要求に応じて、OAuthとOpenID Connectプロトコルは新しい技術に伴う新しい挑戦に适応するために進化し続けています。同時に、脅威の常に進化し、セキュリティの慣習の改善により、古い手法は modern security requirements を満たすことができなくなっています。結果として、OpenID Connectプロトコルは現在、幅広い機能を提供していますが、多くの機能はすでに、またはすぐに、古いものと考えられ、しばしば安全でないと考えられます。この多様性は、SPA開発者にとって、OAuth 2.0とOpenID Connectサーバーとのやりとりを最適かつ安全な方法で行うのに困难的な状況を引き起こします。
特に、隐式フローは今や过时とされており、SPA、モバイルアプリ、デスクトップアプリのいずれものクライアントにとって、認可コードフローを使用して、プロof Key for Code Exchange(PKCE)を伴うことは強く推奨されています。
现代的なSPAの安全性
なぜ、 modern SPAsはPKCEを使用した認可コードフローを使用していても依然として脆弱性があると考えられているのか? この質問にはいくつかの答えがあります。
JavaScriptコードの脆弱性
JavaScriptは強力なプログラミング言語であり、现代的な单一ページアプリケーション(SPA)に关键的な役割を果たしています。しかし、その幅広い機能と一般的な使用は潜在的な危険をもたらしています。React、Vue、Angularなどのライブラリやフレームワークに基づいて構築される现代的なSPAは、 vast numberのライブラリと依存関係を持っています。node_modules
フォルダーにあり、これらの依存関係の数は数百としても、または数千としてもあり得ます。これらのライブラリのそれぞれには、異なる程度の重要性の脆弱性が含まれる可能性があり、SPA開発者は使用されているすべての依存関係のコードを彻底に確認することができません。開発者は通常、それぞれの依存関係の完全なリストを追跡することもできず、それらはお互いに依存関係を持っているためです。自作コードを最高の品質と安全性の標準に開発したとしても、完成したアプリケーションに脆弱性がないかを完全に保証することはできません。
悪意のあるJavaScriptコードは、Cross-Site Scripting(XSS)などの攻撃や第三者のライブラリの侵害を通じてアプリケーションに注入されることができ、正当なアプリケーションコードと同じ権限とデータアクセスレベルを得ることができます。これにより、悪意のあるコードは現在のページからデータを盗むこと、アプリケーションインターフェースとのやり取り、バックエンドにリクエストを送信すること、ローカルストレージ(localStorage、IndexedDB)からデータを盗むこと、甚至に自身の認証セッションを開始し、同じ認可コードとPKCEフローを使用して自身のアクセストークンを取得することができます。
Spectre弱点
Spectre 弱点は、 modern processor architecture の特徴を利用して、 isolation に应收じられるデータにアクセスすることができます。このような脆弱性は、SPAに特に危険です。
まず、SPAはJavaScriptを強く使用して、アプリケーション状態を管理し、サーバとのやり取りを行います。これにより、悪意のあるJavaScriptコードがSpectre脆弱性を利用するための攻撃面を大幅に増やします。また、 tradition multi-page applications(MPA)とは異なり、SPAは再読み込みを少なくするために、ページと読み込まれたコードが長い時間活性に保ちます。これにより、攻撃者は恶意のJavaScriptコードを使用して攻撃を行う時間を大幅に増やすことができます。
Spectre脆弱性により、攻撃者はJavaScriptアプリケーションのメモリに保存されたアクセストークンを盗むことができ、正しいアプリケーションを mimic して保護されたリソースにアクセスすることができます。推論実行をもって、ユーザーセッションデータを盗むこともできます。これにより、SPAが閉じられた後でも攻撃者は攻撃を続けることができます。
未来に、Spectreに似たような他の脆弱性の発見は排除できません。
どうするのか?
重要な中间結論をまとめましょう。モダンなSPAは、多くの第三者のJavaScriptライブラリに依存し、ユーザー装置上のブラウザ環境で実行しています。開発者が完全にコントロールできないソフトウェアとハードウェア環境で作動しています。したがって、これらのアプリケーションは本质的に脆弱であると考えるべきです。
リストされた脅威に対する対策として、多くの専門家が、トークンをブラウザに保存するのを完全に避け、アプリケーションを設計して、アクセストークンとリフレッシュトークンをアプリケーションのサーバー側しか取得して処理しないようにすることに倾斜しています。これらはブラウザ側に渡られることはないようにする。後端とSPAを組み合わせた場合、Backend-For-Frontend (BFF) アーキテクチャパターンを使用することでこれを実現することができます。
認可サーバー(OP)、BFFパターンを実装しているクライアント(RP)、第三者のAPI(リソースサーバー)の間のやり取りのスケームは以下のようになります:
BFFパターンを使用してSPAを保護することには、いくつかの利点があります。アクセストークンとリフレッシュトークンはサーバー側に保存され、ブラウザ側に渡られることはないため、脆弱性による盗難が防止されます。セッションとトークンの管理はサーバーで行われ、より良いセキュリティ控制在とより信頼性のある認証検証が可能です。クライアントアプリケーションはBFFを通じてサーバーとやり取りすることで、アプリケーションのロジックを簡略化し、悪意のコード実行の危険性を减少することができます。
.NET プラットフォームでBackend-For-Frontend パターンを実装する。
私たちは.NETプラットフォームでBFFの実践的な実装に進む前に、必要なコンポーネントを考慮して行動計画を練る必要があります。既に設定されたOpenID Connectサーバーを持っていると仮定し、OpenID Connectを使用した認証を実装し、BFFパターンを使用してサーバーとクライアントの部分間の対話を組織する必要があるSPAを開発することを考えます。
文書OAuth 2.0 for Browser-Based Applicationsによると、BFFアーキテクチャパターンは、バックエンドがOpenID Connectクライアントとして行動し、認可コードフローを使用して認証し、PKCEを使用して、アクセストークンとリフレッシュトークンを自身側で取得および保存し、ブラウザ内でSPA側に渡すことはないと仮定しています。BFFパターンはまた、バックエンド側にAPIが存在し、以下の4つの主要なエンドポイントで構成されていることを想定しています。
- Check Session: アクティブなユーザー認証セッションを確認するために使用されます。通常、SPAから非同期API(fetch)を使用して呼び出され、成功すると、アクティブなユーザーに関する情報を返します。したがって、CDNなど第三の源から読み込まれたSPAは、認証状態を確認し、ユーザーとの作業を続けるか、OpenID Connectサーバーを使用して認証を行うか決めることができます。
- ログイン: OpenID Connect サーバー上で認証プロセスを開始します。通常、SPAは第1ステップで Check Sessionを通じて認証されたユーザーデータを取得できない場合、このURLにブラウザをリダイレクトし、これによりOpenID Connect サーバーに完全なリクエストを形成し、ブラウザをそこにリダイレクトします。
- サインイン: 認証に成功した場合、第2ステップの後にサーバーから送られる認可コードを受け取ります。認可コードとPKCE コード検証器を交換してアクセスとリフレッシュ トークンを取得するために、OpenID Connect サーバーに直接リクエストを送信します。ユーザーに認証クッキーを発行して、クライアント側で認証されたセッションを開始します。
- ログアウト: 認証セッションを終了するための機能を提供します。通常、SPAはこのURLにブラウザをリダイレクトし、これによりOpenID Connect サーバーのEnd Session エンドポイントにリクエストを形成し、セッションを終了します。また、クライアント側のセッションと認証クッキーも削除します。
次に、.NET プラットフォームが提供しているツールを調査し、BFF パターンの実装に使用できるのを確認しましょう。.NET プラットフォームは Microsoft.AspNetCore.Authentication.OpenIdConnect
NuGet パッケージを提供しています。これは、MicrosoftがサポートするOpenID Connect クライアントの既製実装です。このパッケージは認可コードフローとPKCEを両方サポートし、すでに必要なサインイン エンドポイント機能を実装した相対パス /signin-oidc のエンドポイントを追加します。したがって、残りの3つのエンドポイントだけを実装する必要があります(上記のポイント 3 を参照)。
実用的な統合の例として、Abblix OIDC Serverライブラリーに基づくテストのOpenID Connect サーバーを取り上げます。しかし、以下に触れているすべての内容は、Facebook、Google、Appleなどの公的に利用可能なサーバーにも、OpenID Connect プロトコル仕様に準拠する他のすべてのサーバーにも適用されます。
前端侧でSPAを実装するために、React ライブラリーを使用し、後端側では.NET WebAPIを使用します。これは、本文が書かれたときの最も一般的な技術スタックの1つです。
コンポーネントとその相互作用の全体的なスchemeは以下のようになっています。
この記事の例を実行するためには、.NET SDKとNode.jsもインストールする必要があります。この記事のすべての例は、.NET 8、Node.js 22、React 18で開発およびテストされています。これらは、記事を書いたときの最新のバージョンです。
Reactを使用したClient SPAの作成と.NET上のバックエンドでの実装
クライアントアプリケーションを迅速に作成するために、既に用意されているテンプレートを使用するのは便利です。.NET 7までのバージョンで、SDKは.NET WebAPIアプリケーションとReact SPAのための内蔵テンプレートを提供していました。残念ながら、このテンプレートは.NET 8のバージョンで削除されました。この理由で、Abblixチームは自作のテンプレートを作成しました。これには.NET WebApiのバックエンド、Reactライブラリを基盤とするフロントエンドSPA、TypeScript、Viteを使用して作成されたものが含まれています。このテンプレートはAbblix.Templates
パッケージの一部として公開されていて、以下のコマンドを実行してインストールすることができます。
dotnet new install Abblix.Templates
今では、名前がabblix-react
のテンプレートを使用できます。これを使用して新しいアプリケーションBffSample
を作成してみましょう。
dotnet new abblix-react -n BffSample
このコマンドは、.NET WebApiのバックエンドとReact SPAクライアントを構成する新しいアプリケーションを作成します。SPAに関連するファイルはBffSample\ClientApp
フォルダにあります。
プロジェクトを作成した後、システムは依存関係をインストールするコマンドを実行することを求めます。
cmd /c "cd ClientApp && npm install"
この操作は、アプリケーションのクライアント部分に必要なすべての依存関係をインストールする必要があります。プロジェクトの成功のためには、このコマンドを同意して実行することを推奨します。これにはY
(はい)を入力する必要があります。
すぐにBffSample
アプリケーションが本地区域で运行するポート番号を5003に変更してみましょう。この操作は強制されるものではありませんが、OpenID Connect サーバーの構置を簡素化するためには便利です。これを行うにはBffSample\Properties\launchSettings.json
ファイルを開き、https
という名前のプロファイルを見つけ、applicationUrl
プロパティの値をhttps://localhost:5003
に変更します。
次に、BffSample
フォルダに移動し、OpenID Connect クライアントを実装する NuGet パッケージを追加するために、以下のコマンドを実行します。
dotnet add package Microsoft.AspNetCore.Authentication.OpenIdConnect
アプリケーションで、名前を Cookies
と OpenIdConnect
として2つの認証スキーマを設定し、アプリケーション設定からそれぞれの設定を読み取ります。これを行うには、BffSample\Program.cs
ファイルに変更を加えます。
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// ******************* START *******************
var configuration = builder.Configuration;
builder.Services
.AddAuthorization()
.AddAuthentication(options => configuration.Bind("Authentication", options))
.AddCookie()
.AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));
// ******************** END ********************
var app = builder.Build();
また、OpenID Connect サーバーに接続する必要な設定を BffSample\appsettings.json
ファイルに追加します。
{
// ******************* START *******************
"Authentication": {
"DefaultScheme": "Cookies",
"DefaultChallengeScheme": "OpenIdConnect"
},
"OpenIdConnect": {
"SignInScheme": "Cookies",
"SignOutScheme": "Cookies",
"SaveTokens": true,
"Scope": ["openid", "profile", "email"],
"MapInboundClaims": false,
"ResponseType": "code",
"ResponseMode": "query",
"UsePkce": true,
"GetClaimsFromUserInfoEndpoint": true
},
// ******************** END ********************
"Logging": {
"LogLevel": {
"Default": "Information",
また、BffSample\appsettings.Development.json
ファイルにも同様に追加します。
{
// ******************* START *******************
"OpenIdConnect": {
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
"ClientSecret": "secret"
},
// ******************** END ********************
"Logging": {
"LogLevel": {
"Default": "Information",
設定内容を簡単に回顾し、それぞれの設定の目的を説明します。
認証
節:DefaultScheme
属性はデフォルトでCookies
スキームを使用して認証を行い、DefaultChallengeScheme
はデフォルトのスキームで認証できない場合、OpenIdConnect
スキームを使用して認証の実行を委譲します。このように、アプリケーションに未知のユーザーがある場合、OpenID Connect サーバーに認証の要求が行われ、その後、認証されたユーザーは認証クッキーを受け取り、そのクッキーを使用して、OpenID Connect サーバーとの接触なしに、すべてのサーバーの呼び出しを認証します。OpenIdConnect
節:SignInScheme
とSignOutScheme
のプロパティは、サインイン後に使用者情報を保存するために使用されるCookies
スchemeを指定します。-
Authority
プロパティは、OpenID Connect サーバの基本 URL を含みます。ClientId
とClientSecret
は、OpenID Connect サーバに登録されたクライアントアプリケーションの識別子と秘密鍵を指定します。 -
SaveTokens
は、OpenID Connect サーバから取得されたトークンを保存する必要を示します。 -
Scope
は、BffClient
アプリケーションがアクセスを要求するスコープのリストを含みます。この場合、標準のスコープopenid
(使用者識別子)、profile
(使用者プロフィール)およびemail
(電子メール)が要求されています。 -
MapInboundClaims
は、OpenID Connect サーバからの入力CLAIMSをアプリケーションで使用するCLAIMSに変換することを責任とします。false
の値は、CLAIMSがOpenID Connect サーバから受信された形式で認証されたユーザーのセッションに保存されることを意味します。 -
ResponseType
の値code
は、クライアントがAuthorization Code Flowを使用することを示します。 -
ResponseMode
は、Authorization CodeFlowのデフォルトの方法である、Authorization Codeをクエリストリングに送信することを指定します。 -
UsePkce
プロパティは、認証过程中でAuthorization Codeの盗難を防止するためにPKCEを使用する必要を示します。 -
GetClaimsFromUserInfoEndpoint
プロパティは、UserInfo エンドポイントから使用者プロファイルデータを取得する必要を示します。
私たちのアプリケーションは、認証なしでユーザーとのやり取りがないと考えていますので、React SPA は認証成功後にだけ読み込まれることを保証します。もちろん、SPA は Content Delivery Network (CDN) のサーバーや npm start
コマンドで起動されるローカル開発サーバーなど、外部源から読み込まれる可能性があります。このような場合、SPA の読み込み前に認証状態を確認することはできません。しかし、自作の .NET バックエンドが SPAs の読み込みを責任としている場合、可能です。
これを行うために、BffSample\Program.cs
ファイルに認証と認可の責任を持つミドルウェアを追加します。
app.UseRouting();
// ******************* START *******************
app.UseAuthentication();
app.UseAuthorization();
// ******************** END ********************
BffSample\Program.cs
ファイルの末尾に、SPA の読み込みを直接行う場所に、認可の要求を追加します、.RequireAuthorization()
。
app.MapFallbackToFile("index.html").RequireAuthorization();
OpenID Connect サーバーの設定を行います。
前述のように、実用的な統合例を説明するために、Abblix OIDC Server ライブラリに基づくテストの OpenID Connect サーバーを使用する。ASP.NET Core MVC に基づくアプリケーションの基本テンプレートは、先ほどインストールした `Abblix.Templates` パッケージに含まれています。このテンプレートを使用して、新しいアプリケーション `OpenIDProviderApp` を作成しましょう。
dotnet new abblix-oidc-server -n OpenIDProviderApp
サーバーを設定するためには、`BffClient` アプリケーションを OpenID Connect サーバー上でクライアントとして登録し、テストユーザーを追加する必要があります。これを行うには、以下のブロックを `OpenIDProviderApp\Program.cs` ファイルに追加する必要があります。
var userInfoStorage = new TestUserStorage(
// ******************* START *******************
new UserInfo(
Subject: "1234567890",
Name: "John Doe",
Email: "[email protected]",
Password: "Jd!2024$3cur3")
// ******************** END ********************
);
builder.Services.AddSingleton(userInfoStorage);
// ...
// Abblix OIDC Serverを登録および設定する
builder.Services.AddOidcServices(options =>
{
// 以下の場所でOIDCサーバーの設定を行います。
// ******************* START *******************
options.Clients = new[] {
new ClientInfo("bff_sample") {
ClientSecrets = new[] {
new ClientSecret {
Sha512Hash = SHA512.HashData(Encoding.ASCII.GetBytes("secret")),
}
},
TokenEndpointAuthMethod = ClientAuthenticationMethods.ClientSecretPost,
AllowedGrantTypes = new[] { GrantTypes.AuthorizationCode },
ClientType = ClientType.Confidential,
OfflineAccessAllowed = true,
PkceRequired = true,
RedirectUris = new[] { new Uri("https://localhost:5003/signin-oidc", UriKind.Absolute) },
PostLogoutRedirectUris = new[] { new Uri("https://localhost:5003/signout-callback-oidc", UriKind.Absolute) },
}
};
// ******************** END ********************
// 以下のURLは、AuthControllerのLoginアクションにリンクしています。
options.LoginUri = new Uri($"/Auth/Login", UriKind.Relative);
// 以下の行は、トークン署名用の新しいキーを生成します。独自のキーを使用したい場合は、それを更换してください。
options.SigningKeys = new[] { JsonWebKeyFactory.CreateRsa(JsonWebKeyUseNames.Sig) };
});
詳細にこのコードを再び確認しましょう。bff_sample
という識別子でクライアントを登録し、secret
という秘密鍵(SHA512のハッシュとして保存)を設定します。これにより、トークンの取得時には、POSTメッセージで送信される秘密鍵を使用したクライアント認証を行うことを示しています(ClientAuthenticationMethods.ClientSecretPost
)。AllowedGrantTypes
は、クライアントが使用できるのはAuthorization Code Flowだけであることを指定します。ClientType
は、クライアントを機密性のあるものと定義し、これは秘密鍵を安全に保管することができることを意味します。OfflineAccessAllowed
は、クライアントがrefresh tokenを使用することを許可します。PkceRequired
は、認証过程中にPKCEを使用することを強制します。RedirectUris
とPostLogoutRedirectUris
は、認証後やセッション終了後にredirectされる許可されたURLのリストを含んでいます。
他のOpenID Connect サーバーにおいても、設定は似たようなものであり、その違いは設定方法だけに限ります。
BFF APIの基本的な実装
以前に触れたように、Microsoft.AspNetCore.Authentication.OpenIdConnect
パッケージの使用は、サンプルアプリケーションにSign In のエンドポイントの実装を自動的に追加します。今度は、BFF APIの残りの部分を実装する時間です。これらの追加のエンドポイントにはASP.NET MVC コントローラーを使用します。まず、Controllers
フォルダーを追加し、BffSample
プロジェクトにBffController.cs
というファイルを作成し、以下のコードをそこに入れます。
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Cors;
using Microsoft.AspNetCore.Mvc;
namespace BffSample.Controllers;
[ApiController]
[Route("[controller]")]
public class BffController : Controller
{
public const string CorsPolicyName = "Bff";
[HttpGet("check_session")]
[EnableCors(CorsPolicyName)]
public ActionResult> CheckSession()
{
// 401 Unauthorizedを返却してSPAに認証エンドポイントにリダイレクトするようにする
if (User.Identity?.IsAuthenticated != true)
return Unauthorized();
return User.Claims.ToDictionary(claim => claim.Type, claim => claim.Value);
}
[HttpGet("login")]
public ActionResult> Login()
{
// 認可コードフローを開始するためのロジック
return Challenge(new AuthenticationProperties { RedirectUri = Url.Content("~/") });
}
[HttpPost("logout")]
public IActionResult Logout()
{
// ユーザーのログアウトを処理するためのロジック
return SignOut();
}
}
このクラスコードを詳細に分割しましょう:
[Route("[controller]")]
属性は、コントローラ内のすべてのアクションの基本ルートを設定します。この場合、ルートはコントローラの名前にマッチするため、私たちのAPIメソッドにアクセスするすべてのパスは/bff/
から始まります。- 定数
CorsPolicyName = "Bff"
は、CORS(クロスオリジンリソース共有)ポリシーの名前を定義して、メソッド属性で使用します。後で参照します。 - 3つのメソッド
CheckSession
、Login
、Logout
は、上記の必要なBFF機能を実装します。それぞれ/bff/check_session
、/bff/login
、/bff/logout
でGETリクエスト、POSTリクエストを処理します。 CheckSession
メソッドは、ユーザーの認証状態を確認します。認証されていない場合、401 Unauthorized
コードを返却し、SPAに認証エンドポイントにリダイレクトすることを期待します。認証が成功した場合、このメソッドは証明書とその値のセットを返します。このメソッドには、認証に使用されるクッキーを含むクロスドメインの呼び出しを想定して、名前CorsPolicyName
のCORSポリシーバインディングが含まれます。Login
メソッドは、前のCheckSession
呼び出しが401 Unauthorized
を返却した場合、SPA によって呼び出されます。これは、ユーザーがまだ認証されていないことを保証し、設定されたChallenge
プロセスを開始します。これにより、OpenID Connect サーバーにリダイレクトされ、Authorization Code Flow と PKCE を使用してユーザー認証を行い、認証クッキーを発行します。これの後、コントロールは私たちのアプリケーションのルート"~/"
に返り、SPA が再読み込まれ、認証されたユーザーで始動します。Logout
メソッドも SPA によって呼び出されますが、現在の認証セッションを終了します。これは、BffSample
のサーバー側から発行された認証クッキーを削除し、OpenID Connect サーバー側の End Session エンドポイントにも呼び出します。
BFF 用 CORS の設定
上記に述べたように、CheckSession
メソッドは、SPAからの非同期呼び出し(通常は Fetch API を使用)用に設計されています。このメソッドの適切な機能は、ブラウザから認証クッキーを送信する能力に依存します。SPAが別の静的ウェブホスト(例:CDNや別のポートで稼働している開発サーバ)から読み込まれる場合、この呼び出しはクロスドメインになります。これは、CORS ポリシーの設定が必要で、その無い場合、SPAはこのメソッドを呼び出すことができません。
私たちは、Controllers\BffController.cs
ファイルのコントローラコードで、CorsPolicyName = "Bff"
という名前の CORS ポリシーを使用することを示しています。これまでには、このポリシーのパラメーターの設定を行わなかったので、今は BffSample/Program.cs
ファイルに以下のコードブロックを追加します。
// ******************* 開始 *******************
using BffSample.Controllers;
// ******************** 終了 ********************
var builder = WebApplication.CreateBuilder(args);
builder.Services.AddControllers();
// ...
builder.Services
.AddAuthorization()
.AddAuthentication(options => configuration.Bind("Authentication", options))
.AddCookie()
.AddOpenIdConnect(options => configuration.Bind("OpenIdConnect", options));
// ******************* 開始 *******************
builder.Services.AddCors(
options => options.AddPolicy(
BffController.CorsPolicyName,
policyBuilder =>
{
var allowedOrigins = configuration.GetSection("CorsSettings:AllowedOrigins").Get();
if (allowedOrigins is { Length: > 0 })
policyBuilder.WithOrigins(allowedOrigins);
policyBuilder
.WithMethods(HttpMethods.Get)
.AllowCredentials();
}));
// ******************** 終了 ********************
var app = builder.Build();
このコードでは、CORSポリシーの方法は、CorsSettings:AllowedOrigins
として指定された配置の文字列の配列からSPAを読み込むために呼び出すことができます。GET
メソッドを使用して、このコールに COOKIE を送信することができます。また、app.UseCors(...)
の呼び出しはapp.UseAuthentication()
のすぐ前に置かれることを確認してください。
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
// ******************* 開始 *******************
app.UseCors(BffController.CorsPolicyName);
// ******************** 終了 ********************
app.UseAuthentication();
app.UseAuthorization();
CORSポリシーを正しく機能させるために、BffSample\appsettings.Development.json
設定ファイルに対応する設定を追加します。
{
// ******************* 開始 *******************
"CorsSettings": {
"AllowedOrigins": [ "https://localhost:3000" ]
},
// ******************** 終了 ********************
"OpenIdConnect": {
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
例えば、https://localhost:3000
はReact SPAをnpm run dev
コマンドで起動するためのdevサーバーの場所です。このアドレスはBffSample.csproj
ファイルを開いてSpaProxyServerUrl
パラメーターの値を探すことで、おそらく見つかります。実際のアプリケーションでは、CORSポリシーにCDN(コンテントデリvery Network)またはそのようなサービスのアドレスを含めることができます。SPAがBFF APIから異なるアドレスとポートで読み込まれる場合、このアドレスをCORSポリシー設定に追加する必要があります。
BFFを使用した認証の実装
BFF APIをサーバー側で実装したので、次にReact SPAに対してこのAPIを呼び出す機能を追加することに焦点を置きましょう。まず、BffSample\ClientApp\src\
フォルダに移動し、components
フォルダを作成し、以下の内容のBff.tsx
ファイルを追加しましょう。
import React, { createContext, useContext, useEffect, useState, ReactNode, FC } from 'react';
// BFFのコンテキストの形状を定義する
interface BffContextProps {
user: any;
fetchBff: (endpoint: string, options?: RequestInit) => Promise;
checkSession: () => Promise;
login: () => void;
logout: () => Promise;
}
// BFF用のコンテキストを作成し、アプリケーション間で状態と機能を共有する
const BffContext = createContext({
user: null,
fetchBff: async () => new Response(),
checkSession: async () => {},
login: () => {},
logout: async () => {}
});
interface BffProviderProps {
baseUrl: string;
children: ReactNode;
}
export const BffProvider: FC = ({ baseUrl, children }) => {
const [user, setUser] = useState(null);
// 基本のURLを正規化し、末尾のスラッシュを除去して、不一致するURLを避ける
if (baseUrl.endsWith('/')) {
baseUrl = baseUrl.slice(0, -1);
}
const fetchBff = async (endpoint: string, options: RequestInit = {}): Promise => {
try {
// fetch関数には認証に必要なcookiesを処理するためのクレデンシャルが含まれる
return await fetch(`${baseUrl}/${endpoint}`, {
credentials: 'include',
...options
});
} catch (error) {
console.error(`Error during ${endpoint} call:`, error);
throw error;
}
};
// ログイン関数は、ユーザーに認証が必要である場合にログイン画面にリダイレクトする
const login = (): void => {
window.location.replace(`${baseUrl}/login`);
};
// checkSession関数は、最初のレンダリング時にユーザーのセッションを確認する責任を持っている
const checkSession = async (): Promise => {
const response = await fetchBff('check_session');
if (response.ok) {
// セッションが有効であれば、受信したクレームのデータでユーザーの状態を更新する
setUser(await response.json());
} else if (response.status === 401) {
// ユーザーが認証されていない場合、ログイン画面にリダイレクトする
login();
} else {
console.error('Unexpected response from checking session:', response);
}
};
// ユーザーをlogoutする関数
const logout = async (): Promise => {
const response = await fetchBff('logout', { method: 'POST' });
if (response.ok) {
// ログアウト後にホーム画面にリダイレクトする
window.location.replace('/');
} else {
console.error('Logout failed:', response);
}
};
// useEffectを使用して、コンポーネントがマウントされた後にcheckSession関数を実行する
// これにより、アプリが読み込まれたときにすぐにセッションの確認が行われる
useEffect(() => { checkSession(); }, []);
return (
// アプリケーション全体で使用するために、BFFコンテキストに関連した値と機能を提供する
{children}
);
};
// 他のコンポーネントでBFFコンテキストを簡単に使用するためのカスタムフック
export const useBff = (): BffContextProps => useContext(BffContext);
// BFFコンテキストにアクセスするためのHOCをエクスポートする
export const withBff = (Component: React.ComponentType) => (props: any) =>
{context => }
;
このファイルは以下をエクスポートしています:
- <code>BffProvider</code>コンポーネント、これはBFF用のコンテキストを作成し、アプリケーション全体に認証とセッション管理に関連する機能と状態を提供します。
- current user stateとBFFとの操作を行うための機能を持ったオブジェクトを返すカスタムフック
useBff()
。 functional React componentsで使用することが意図されている。 - class-based React componentsで使用するためのHigher-Order Component (HOC)
withBff
。
次に、認証成功後、現在のユーザーのCLAIMSを表示するUserClaims
componentを作成します。BffSample\ClientApp\src\components
フォルダにUserClaims.tsx
ファイルを作成し、以下の内容を入力してください。
import React from 'react';
import { useBff } from './Bff';
export const UserClaims: React.FC = () => {
const { user } = useBff();
if (!user)
return <div>Checking user session...</div>;
return (
<>
<h2>User Claims</h2>
{Object.entries(user).map(([claim, value]) => (
<div key={claim}>
<strong>{claim}</strong>: {String(value)}
</div>
))}
</>
);
};
このコードはuseBff()
フックを使用して認証されたユーザーを確認し、認証された場合はユーザーのCLAIMSをリスト形式で表示します。また、ユーザーのデータがまだ利用できない場合は、Checking user session...
というテキストを表示します。
次に、BffSample\ClientApp\src\App.tsx
ファイルに移行します。必要なコードに置き換えます。components/Bff.tsx
からBffProvider
をインポートし、components/UserClaims.tsx
からUserClaims
をインポートし、主要なコンポーネントのコードを插入します。
import { BffProvider, useBff } from './components/Bff';
import { UserClaims } from './components/UserClaims';
const LogoutButton: React.FC = () => {
const { logout } = useBff();
return (
<button className="logout-button" onClick={logout}>
Logout
</button>
);
};
const App: React.FC = () => (
<BffProvider baseUrl="https://localhost:5003/bff">
<div className="card">
<UserClaims/>
</div>
<div className="card">
<LogoutButton />
</div>
</BffProvider>
);
export default App;
ここで、baseUrl
パラメーターは、私のBFF APIの基本URLhttps://localhost:5003/bff
を指定しています。これは単純化されたものであり、簡略化されています。実際のアプリケーションでは、この設定を動的に提供する必要があります。これを実現する方法はいくつかありますが、その詳細はこの記事の範囲を超えます。
Logout
ボタンは、ユーザーをログアウトさせるためのボタンです。これはuseBff
フックを通じて利用可能なlogout
関数を呼び出し、ユーザーのブラウザーを/bff/logout
エンドポイントにリダイレクトし、サーバー側でユーザーのセッションを終了します。
この段階で、BffSample
アプリケーションをOpenIDProviderApp
と一緒に実行することができます。各プロジェクトにあるコマンドdotnet run -lp https
を使用して、それぞれのプロジェクトやお好みのIDEから起動することができます。どちらのアプリケーションも同時に実行する必要があります。
これの後、ブラウザーを開いてhttps://localhost:5003
に移動します。もしすべてが正しく設定されているなら、SPAが読み込まれると/bff/check_session
を呼び出します。/check_session
エンドポイントは401応答を返し、SPAがブラウザーを/bff/login
にリダイレクトすることを促します。これにより、OpenID Connect Authorization Code Flowを使用してPKCEを通じてサーバー上で認証を開始します。ブラウザーの開発コンソールを開いてNetworkタブに移動することで、このリクエストのシーケンスを確認することができます。ユーザーの情報([email protected]
, Jd!2024$3cur3
)を正しく入力した後、コントロールはSPAに返り、認証されたユーザーの声明をブラウザーで確認することができます。
sub: 1234567890
sid: V14fb1VQbAFG6JXTYQp3D3Vpa8klMLcK34RpfOvRyxQ
auth_time: 1717852776
name: John Doe
email: [email protected]
また、Logout
ボタンをクリックすると、ブラウザーは/bff/logout
にリダイレクトされ、ユーザーがログアウトされ、ログイン画面にもどり、ユーザー名とパスワードを入力する提示が表示されます。
Abblix/Oidc.Server.GettingStartedにあるGitHubリポジトリとあなたのコードを比較することができます。このリポジトリには、この例と他の実行準備ができた例が含まれています。
HTTPS証明書の信頼問題の解決
HTTPSを使用して動作するWebアプリケーションをローカルでテストする際に、SSL証明書が信頼されていないというブラウザの警告が発生することがあります。この問題は、ASP.NET Coreによって使用される開発用証明書が認識された証券発行機(CA)に発行されていないため、自署名またはシステムに存在しないために発生します。これらの警告は、以下のコマンドを一度実行することで消除できます。
dotnet dev-certs https --trust
このコマンドは、localhost
用の自署名証明書を生成し、システムにインストールし、この証明書を信頼するようにします。この証明書は、ASP.NET CoreによってローカルでWebアプリケーションの実行に使用されます。このコマンドを実行した後、変更を有効にするためにブラウザを再起動する必要があります。
Chromeユーザーの特に注意:開発証明書を信頼することでも、Chromeのいくつかのバージョンは、安全性のためにlocalhost
サイトにアクセスを制限するかもしれません。localhost
にアクセスがブロックされ、Chromeによる接続は安全でないと示されるエラーが発生した場合、以下のようにこれらの問題を回避することができます。
- エラーページのどこかにクリックし、
thisisunsafe
またはbadidea
と入力してください。これらのキーストロークシーケンスは、Chromeでの回避コマンドとして機能し、localhost
サイトにアクセスできるようにします。
開発環境にてのみこれらの回避方法を使用することが重要であることに注意してください。これらは本格的な安全风险を引き起こす可能性があります。
BFFを通じた第三者APIの呼び出し
私たちはBffSample
アプリケーションで認証を成功させました。今は、アクセストークンが必要な第三者APIの呼び出しに移行しましょう。
必要なデータを提供する別のサービスがあると考えてください。例えば、天気予報などで、アクセストークンを持っていないと访问できません。BffSample
のサーバー側は、リバースプロキシとして機能し、SPAからのデータ请求を受け取り、認証し、アクセストークンを追加し、この要求を天気サービスに転送し、サービスからの応答をSPAに返すように機能します。
ApiSampleサービスの作成
BFFを通じたリモートAPIの呼び出しを示す前に、このAPIの役割を果たすアプリケーションを作成する必要があります。
アプリケーションを作成するためには、.NETから提供されるテンプレートを使用します。OpenIDProviderApp
とBffSample
プロジェクトを含むフォルダに移動し、以下のコマンドを実行してApiSample
アプリケーションを作成します。
dotnet new webapi -n ApiSample
このASP.NET Core Minimal APIアプリケーションは、JSON形式で天気データを提供する/weatherforecast
の单一のエンドポイントをサービスします。
まず、ApiSample
应用程序在本地で使用するランダムに割り振られたポート番号を固定のポート 5004に変更します。前述の通り、このステップは必須ではありませんが、設定を簡素化するためには便利です。これを行うためには、ApiSample\Properties\launchSettings.json
ファイルを開き、名前が https
的なプロファイルを見つけ、applicationUrl
プロパティの値を https://localhost:5004
に変更します。
次に、ウェather APIをアクセストークンだけでアクセス可能にしましょう。ApiSample
プロジェクトフォルダに移動し、JWT Bearer トークン認証用のNuGetパッケージを追加してください。
dotnet add package Microsoft.AspNetCore.Authentication.JwtBearer
次に、ApiSample\Program.cs
ファイルに WeatherApi
という認証スキーマと認可ポリシーを設定しましょう。
// ******************* START *******************
using System.Security.Claims;
// ******************** END ********************
var builder = WebApplication.CreateBuilder(args);
// サービスをコンテナに追加します。
// Swagger/OpenAPIの設定方法については https://aka.ms/aspnetcore/swashbuckle を参照してください
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();
// ******************* START *******************
var configuration = builder.Configuration;
builder.Services
.AddAuthentication()
.AddJwtBearer(options => configuration.Bind("JwtBearerAuthentication", options));
const string policyName = "WeatherApi";
builder.Services.AddAuthorization(
options => options.AddPolicy(policyName, policy =>
{
policy.RequireAuthenticatedUser();
policy.RequireAssertion(context =>
{
var scopeValue = context.User.FindFirstValue("scope");
if (string.IsNullOrEmpty(scopeValue))
return false;
var scope = scopeValue.Split(' ', StringSplitOptions.RemoveEmptyEntries);
return scope.Contains("weather", StringComparer.Ordinal);
});
}));
// ******************** END ********************
var app = builder.Build();
このコードブロックは、アプリケーション設定から configuration を読み取り、JWT(JSON Web Tokens)を使用した認可を含む認証を設定し、名前が WeatherApi
の認可ポリシーを設定します。WeatherApi
認可ポリシーは以下の要求を設定します。
policy.RequireAuthenticatedUser()
: 保護されたリソースにアクセスするには、認証されたユーザーだけがアクセスできることを保証します。policy.RequireAssertion(context => ...)
: ユーザーにはscope
声明が含まれている必要があり、scope
声明の値にweather
が含まれていることが必要です。scope
声明はRFC 8693に基づいて、空白で区切られた複数の値を含むことができるため、実際のscope
の値は個別の部分に分割され、結果の配列は必要なweather
の値を含むことを確認します。
これらの条件が合致すると、weather
スコープに認証されたアクセストークンを持つユーザーだけが、このポリシーによって保護されるエンドポイントを呼び出すことができます。
私たちは/weatherforecast
エンドポイントにこのポリシーを適用する必要があります。以下のようにRequireAuthorization()
の呼び出しを追加します。
app.MapGet("/weatherforecast", () =>
{
// ...
})
.WithName("GetWeatherForecast")
// ******************* START *******************
.WithOpenApi()
.RequireAuthorization(policyName);
// ******************** END ********************
認証スキームに必要な設定をApiSample
アプリケーションのappsettings.Development.json
ファイルに追加します。
{
// ******************* START *******************
"JwtBearerAuthentication": {
"Authority": "https://localhost:5001",
"MapInboundClaims": false,
"TokenValidationParameters": {
"ValidTypes": [ "at+jwt" ],
"ValidAudience": "https://localhost:5004",
"ValidIssuer": "https://localhost:5001"
}
},
// ******************** END ********************
"Logging": {
"LogLevel": {
"Default": "Information",
設定内容を詳細に確認しましょう。
Authority
: これはJWTトークンを発行するOpenID Connect認証サーバーのURLです。ApiSample
アプリケーションで設定された認証プロバイダは、このURLを使用してトークンを検証するために必要な情報を取得します。たとえば署名キーなどです。MapInboundClaims
: この設定は、JWTトークンからの入力CLAIMがASP.NET Core内部のCLAIMにどのように対応されるかを制御します。これはfalse
に設定されており、これはCLAIMがJWTからの元の名前を使用することを意味します。TokenValidationParameters
:ValidTypes
:at+jwt
に設定されており、RFC 9068 2.1に基づいて、これはJWT形式のアクセストークンを意味します。ValidAudience
: このアプリケーションがhttps://localhost:5004
によって発行されたトークンを受け取ることを指定します。ValidIssuer
: このアプリケーションがhttps://localhost:5001
によって発行されたトークンを受け取ることを指定します。
OpenIDProviderAppの追加設定
認証サービスOpenIDProviderApp
とクライアントアプリケーションBffSample
の組み合わせは、ユーザー認証の提供に适しています。しかし、リモートAPIにアクセスするためには、OpenIDProviderApp
のリソースとしてApiSample
アプリケーションを登録する必要があります。この例では、RFC 8707: OAuth 2.0のリソース指標をサポートするAbblix OIDC Server
を使用しています。したがって、ApiSample
アプリケーションをスコープweather
としてリソースとして登録します。他のOpenID Connect サーバーを使用している場合、Resource Indicatorsをサポートしていない場合でも、このリモートAPIに独自のスコープを登録する推奨があります(この例ではweather
)。
以下のコードをOpenIDProviderApp\Program.cs
ファイルに追加します。
// Abblix OIDC Serverを登録し、構成する
builder.Services.AddOidcServices(options => {
// ******************* START *******************
options.Resources =
[
new(new Uri("https://localhost:5004", UriKind.Absolute), new ScopeDefinition("weather")),
];
// ******************** END ********************
options.Clients = new[] {
new ClientInfo("bff_sample") {
この例では、ApiSample
アプリケーションを登録し、その基本アドレス https://localhost:5004
をリソースとして指定し、名前を weather
にした特定のスコープを定義します。実際のアプリケーションでは、特に多くのエンドポイントを構成する複雑なAPIにおいて、各個別のエンドポイントまたは関連したエンドポイントのグループに個別のスコープを定義することが推奨されます。この取り組みは、より正確なアクセス制御を可能にし、アクセス権の管理を柔軟に行うことができます。たとえば、異なる操作、アプリケーションモジュール、またはユーザーアクセスレベルに独自のスコープを作成することができます。これにより、APIの特定の部分にアクセスできるユーザーにより細粒度の控制在ります。
BffSampleのリモートAPIにリクエストを代理する機能の詳細
クライアントアプリケーション BffSample
は、ApiSample
にアクセストークンを要求するだけでなく、SPAからのリクエストを処理する必要があります。これには、OpenIDProviderApp
サービスから取得したアクセストークンをこれらのリクエストに追加し、リモートサーバーに送信し、サーバーからの応答をSPAに返す必要があります。基本的に、BffSample
はリバースプロキシサーバーとして機能する必要があります。
クライアントアプリケーションでリクエストの代理を手動で実装する代わりに、マイクロソフトによって開発された既成品であるYARP (Yet Another Reverse Proxy)を使用します。YARPは.NETで書かれたリバースプロキシサーバーであり、NuGetパッケージとして利用可能です。
在 BffSample
アプリケーションでYARPを使用するには、まずNuGetパッケージを追加する必要があります。
dotnet add package Yarp.ReverseProxy
次に、BffSample\Program.cs
ファイルの先頭に以下の名前空間を追加します。
using Microsoft.AspNetCore.Authentication;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Net.Http.Headers;
using Yarp.ReverseProxy.Transforms;
呼び出し var app = builder.Build();
の前に、以下のコードを追加します:
builder.Services.AddHttpForwarder();
そして、app.MapControllerRoute()
と app.MapFallbackToFile()
の呼び出しの間に:
app.MapForwarder(
"/bff/{**catch-all}",
configuration.GetValue("OpenIdConnect:Resource") ?? throw new InvalidOperationException("Unable to get OpenIdConnect:Resource from current configuration"),
builderContext =>
{
// リクエストパスから "/bff" プレフィックスを削除する
builderContext.AddPathRemovePrefix("/bff");
builderContext.AddRequestTransform(async transformContext =>
{
// 認証プロセス中に受信したアクセストークンを取得する
var accessToken = await transformContext.HttpContext.GetTokenAsync(OpenIdConnectParameterNames.AccessToken);
// アクセストークンを含むヘッダーをプロキシリクエストに追加する
transformContext.ProxyRequest.Headers.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
});
}).RequireAuthorization();
このコードが何をするのかを分解して説明します:
builder.Services.AddHttpForwarder()
は、DIコンテナに必要なYARPサービスを登録します。app.MapForwarder
は、リクエストを別のサーバーやエンドポイントに転送するように設定します。"/bff/{**catch-all}"
は、リバースプロキシが応答するパスパターンです。すべての/bff/
で始まるリクエストはYARPによって処理されます。{**catch-all}
は、/bff/
の後のURLの残りの部分をすべてキャプチャするために使用されます。configuration.GetValue<string>("OpenIdConnect:Resource")
は、アプリケーションの設定を使用してOpenIdConnect:Resource
セクションから値を取得します。この値はリクエストが転送されるリソースアドレスを指定します。この例では、この値はhttps://localhost:5004
となり、ApiSample
アプリケーションが動作するベースURLです。builderContext => ...
は、YARPがSPAからの各要求に対して行う必要のある変換を追加します。私たちの場合、これらの変換が2つあります。builderContext.AddPathRemovePrefix("/bff")
は、元の要求パスから/bff
の接頭辞を削除します。builderContext.AddRequestTransform(async transformContext => ...)
は、以前認証時に取得したアクセストークンを含むAuthorization
HTTPヘッダを要求に追加します。このため、SPAからリモートAPIに向かう要求は、SPA自身がこのトークンにアクセスできないにも関わらず、アクセストークンを使用して認証されます。
.RequireAuthorization()
は、すべての転送要求に認証が必要であることを指定します。認証されたユーザーのみが/bff/{**catch-all}
のルートにアクセスできます。
認証時にリソース https://localhost:5004
のアクセストークンを要求するには、BffSample/appsettings.Development.json
ファイルの OpenIdConnect
設定に Resource
パラメーターを追加し、値を https://localhost:5004
に設定します。
"OpenIdConnect": {
// ******************* START *******************
"Resource": "https://localhost:5004",
// ******************** END ********************
"Authority": "https://localhost:5001",
"ClientId": "bff_sample",
また、BffSample/appsettings.json
ファイルの scope
配列に weather
を他の値と共に追加します。
{
"OpenIdConnect": {
// ...
// ******************* START *******************
"Scope": ["openid", "profile", "email", "weather"],
// ******************** END ********************
// ...
}
}
Notes: 実際のプロジェクトでは、アクセストークンの期限切れを監視する必要があります。トークンが期限切れに近づいている際、認証サービスからリフレッシュトークンを使用して新しいトークンを要求するか、リモートAPIからのアクセス拒否エラーを処理し、新しいトークンを取得して元の要求を再試行する必要があります。この記事では、この侧面を故意に省略しています。
SPA 应用程序中通过 BFF 请求天气 API
後端は準備完了です。ApiSample
应用程序はトークンベースの認可を実装した API を提供し、BffSample
应用程序はこの API に安全なアクセスを提供するための組み込まれたリバースプロキシサーバーを含みます。最後の手順は、React SPA 内でこの API を要求し、取得したデータを表示する機能を追加することです。
次の内容をBffSample\ClientApp\src\components
にあるWeatherForecast.tsx
ファイルに追加してください。
import React, { useEffect, useState } from 'react';
import { useBff } from './Bff';
interface Forecast {
date: string;
temperatureC: number;
temperatureF: number;
summary: string;
}
interface State {
forecasts: Forecast[];
loading: boolean;
}
export const WeatherForecast: React.FC = () => {
const { fetchBff } = useBff();
const [state, setState] = useState<State>({ forecasts: [], loading: true });
const { forecasts, loading } = state;
useEffect(() => {
fetchBff('weatherforecast')
.then(response => response.json())
.then(data => setState({ forecasts: data, loading: false }));
}, [fetchBff]);
const contents = loading
? <p><em>Loading...</em></p>
: (
<table className="table table-striped" aria-labelledby="tableLabel">
<thead>
<tr>
<th>Date</th>
<th>Temp. (C)</th>
<th>Temp. (F)</th>
<th>Summary</th>
</tr>
</thead>
<tbody>
{forecasts.map((forecast, index) => (
<tr key={index}>
<td>{forecast.date}</td>
<td align="center">{forecast.temperatureC}</td>
<td align="center">{forecast.temperatureF}</td>
<td>{forecast.summary}</td>
</tr>
))}
</tbody>
</table>
);
return (
<div>
<h2 id="tableLabel">Weather forecast</h2>
<p>This component demonstrates fetching data from the server.</p>
{contents}
</div>
);
};
このコードを詳細に解釈してみましょう。
Forecast
インターフェースは、日付、セルシウスとファーヘンハイトの温度、そして天気の要約を含む天気予報データの構造を定義します。State
インターフェースは、天気予報の配列と読み込み中のフラグを含むコンポーネントの状態構造を記述します。WeatherForecast
コンポーネントは、useBff
フックからfetchBff
関数を取得し、その関数を使用してサーバから天気データを取得します。このコンポーネントの状態はuseState
フックを使用して管理され、予報の空の配列と読み込み中のフラグをtrueに初期化します。useEffect
フックは、コンポーネントがマウントされるとfetchBff
関数をトリガーし、/bff/weatherforecast
エンドポイントから天気予報データをサーバから取得します。サーバの応答が受信され、JSONに変換された後、データはsetState
を通じてコンポーネントの状態に保存され、読み込み中のフラグはfalse
に更新されます。- 読み込み中のフラグの値に応じて、コンポーネントは”Loading…”メッセージを表示したり、天気予報データによるテーブルをレンダリングしたりします。テーブルには、日付、セルシウスとファーヘンハイトの温度、そして各予報の天気の要約の列が含まれます。
次に、BffSample\ClientApp\src\App.tsx
にWeatherForecast
コンポーネントを追加してください。
// ******************* 開始 *******************
import { WeatherForecast } from "./components/WeatherForecast";
// ******************** 終了 ********************
// ...
// ******************* 開始 *******************
// ******************** 終了 ********************
実行とテスト
すべてが正しく行われたら、今では3つのプロジェクトをすべて開始することができます。各アプリケーションをHTTPSで実行するには、コンソールコマンドdotnet run -lp https
を使用してください。
3つのアプリケーションを全て起動した後、ブラウザでBffSample
アプリケーションを開きます(https://localhost:5003)。そして、[email protected]
とJd!2024$3cur3
の資格情報で認証します。認証に成功すると、以前に見たように認証サーバから受信した声明のリストが表示されます。この下に、天気予報も表示されます。
天気予報は、認証サービスOpenIDProviderApp
から発行されたアクセストークンを使用している別のアプリケーションApiSample
によって提供されます。BffSample
アプリケーションの窓で天気予報を見ることができることは、SPAがBffSample
のバックエンドを成功に呼び出し、それがApiSample
にアクセストークンを追加してプロキシングすることを示しています。ApiSample
は呼び出しを認証し、天気予報を含むJSONを返信します。
完整的なソリューションはGitHubにあります。
テストプロジェクトの実装中に問題やエラーに直面した場合、GitHubリポジトリに格納されている完全なソリューションを参照することができます。この記事で説明されている完全に実装されたプロジェクトにアクセスするには、Abblix/Oidc.Server.GettingStartedのリポジトリをクローンするだけです。このリソースは、トラブルシュootingツールとしても、自作プロジェクトの立ち上げのための坚实基础としても機能します。
結論
OAuth 2.0やOpenID Connectなどの認証プロトコルの進化は、webセキュリティとブラウザ機能のより一般的なトレンドを反映しています。Implicit Flowのような過剰な方法から、PKCEを使用したAuthorization Code Flowなどのより安全性の高い方法に移行することで、安全性を大幅に向上させました。しかし、未管理な環境での操作において固有の脆弱性があるため、 modern SPAsのセキュリティを保証するのは難しい課題です。トークンをバックエンドに専用に保管し、Backend-For-Frontend (BFF) パターンを採用することは、リスク軽減と強いユーザーデータ保護を実現する効果的な戦略です。
開発者は、新しい認証方法や最新のアーキテクチャーを実装することで、常に変化する脅威のLayoutに警戒を持ち続けなければならないことに注意してください。この積極的な取り組みは、セキュリティの高いと信的なwebアプリケーションを構築するために非常に重要です。この記事では、人気の.NETとReact技術スタックを使用して、OpenID Connect、BFF、SPAを現代的な方法で統合することを調査し実装しました。この取り組みは、将来のプロジェクトにおいて強力な基础を提供することができます。
将来的に、ウェブセキュリティの持続的な進化は、認証とアーキテクチャパターンに対するさらなる革新を必要とするでしょう。私たちは、GitHubリポジトリを探索し、现代の認証ソリューションの開発に貢献し、進行を追っていくことをお勧めします。お気づきいただき、ありがとうございます!
Source:
https://dzone.com/articles/modern-authentication-on-dotnet