WebSurfer's Home

トップ > Blog 1   |   ログイン
APMLフィルター

React 開発用サーバー証明書の更新

by WebSurfer 2023年1月28日 18:05

Visual Studio 2022 のテンプレートで作成する .NET 6.0 の React アプリは、Viusal Studio から実行すると React フロント側は Node.js 開発サーバーでホストされます。その Node.js 開発サーバーが HTTPS 通信で使用するサーバー証明書を更新する方法を書きます。

サーバー証明書エラー

この記事は React フロント側が Node.js 開発サーバーで実行される場合の話で、フロント側も IIS Express や Kestrel 上で実行される .NET Core 3.1 や .NET 5.0 のアプリでは関係ないので注意してください。

前置きが少々長くなりますが、まずどのような仕組みになっているかを書きます。

Visual Studio 2022 が ASP.NET Core アプリを実行して HTTPS 通信を行う際に使うサーバー証明書は、dotnet コマンドを使って発行するフレンドリ名が ASP.NET Core HTTPS development certificate という自己署名証明書を使います。

(注: ASP.NET Core アプリでも IIS Express でホストする場合は IIS Express Development Certificate が使用されます。下の画像の青枠の下のものです)

その有効期限は発効後 1 年で、それを過ぎると上の画像のようにサーバー証明書の期限切れエラーとなります。

Windows OS にインストールされている開発用サーバー証明書を更新することは dotnet コマンドを使うなどして可能です。下の画像の青枠の証明書が新たに発行して OS にインストールされた証明書です。

開発用サーバー証明書

ASP.NET Core アプリですとそれだけでサーバー証明書の期限れの問題は解決できるはずです。

しかしながら、.NET 6.0 の React アプリの場合はそれではエラーは解決しません。なぜかと言うと、Node.js 開発サーバーは独自に作成した証明書ファイルと秘密鍵ファイルを使っているからです。なので、それらが更新されない限りエラーは解決しません。

Visual Studio 2022 で作成する .NET 6.0 の React アプリで、HTTPS 通信のためにどのような設定が行われるかを以下に簡単に説明します。

まず、プロジェクトに含まれる Node.js のスクリプト aspnetcore-https.js が dotnet コマンドを使って自己署名証明書を発行し、証明書ファイルと秘密鍵ファイルを以下のフォルダに保存します。なお、ファイルが既に存在する場合はその操作はスキップされます。

%USERPROFILE%\AppData\Roaming\ASP.NET\https

ファイル名はデフォルトで証明書ファイルが <プロジェクト名>.pem、秘密鍵ファイルが <プロジェクト名>.key となります。

次に、aspnetcore-react.js がその証明書ファイルと秘密鍵ファイルを Node.js 開発サーバーがサーバー証明書として使うように設定します。

証明書ファイルと秘密鍵ファイルの場所の情報は、以下のように .env.development.local ファイルに含まれます。.env.development ファイルには PORT=44453 HTTPS=true という設定されていて、Node.js 開発サーバーが指定されたポートで HTTPS でリッスンするように指定されます。

.env.development.local

それらの設定により、Visual Studio 2022 から React アプリのプロジェクトを実行すると、Node.js 開発サーバーは .env.development.local に指定のパスにある証明書ファイルと秘密鍵ファイルを使用して HTTPS 通信を行うようになります。

そのあたりの仕組みはネットで見つけた記事「"React での ASP.NET Core" テンプレートで生成されるプロジェクトの仕組みを調べてみた」が参考になりました。詳しく知りたい方はその記事を読むことをお勧めします。

以上のようなわけで、Node.js 開発サーバーが使用する開発用サーバー証明書を更新するには、.env.development.local 指定のパスにある証明書ファイルと秘密鍵ファイルを新しいものに書き換えるということになります。

それには、ちょっとプリミティブなやり方ですが、既存の証明書ファイルと秘密鍵ファイルを削除するか名前を変えてから Visual Studio 2022 からプロジェクトを実行してやります。それにより、上に述べた Node.js のスクリプト aspnetcore-https.js が新しい証明書ファイルと秘密鍵ファイルを作成してくれます。

下の画像は、既存の reactnet6.pem と reactnet6.key をそれぞれ reactnet6_old.pem と reactnet6_old.key にリネームした後で (赤枠のファイル)、Visual Studio 2022 でプロジェクトを実行し、新たに reactnet6.pem と reactnet6.key を生成させた (青枠のファイル) 結果です。

証明書ファイルと秘密鍵ファイル

他にやり方は無いか探してみたのですが上記の方法以外見つかりませんでした。もっとスマートな方法がありそうな気がします。見つけたら追記します。

Tags: , , ,

React

React で日本語を使うとコンパイルエラー / 文字化け

by WebSurfer 2022年4月10日 14:26

Visual Studio 2019 / 2022 の「React.js での ASP.NET Core」のテンプレートで作ったプロジェクトで、自動生成された Home.js とか Counter.js などに以下のように <p>日本語</p> という日本語を書き加えると Unexpected character '�' というエラーメッセージが出てコンパイルエラーになります。文字によってはコンパイルは通りますが文字化けします。

Home.js に日本語追加

フレームワークを .NET Core 3.1 としてプロジェクトを作って同様なことをすると、ブラウザに空白画面が表示されるだけなので何が起こったのか分からないと思いますが、.NET 6.0 とするとコマンドラインに以下のように表示されます。(コンパイルエラーにならない場合は文字化けしてブラウザに表示されるので分かります)

コマンドラインのエラーメッセージ

ブラウザにも以下のようにエラーメッセージが表示されます。

ブラウザのエラーメッセージ

文字によってはコンパイルは通り、ブラウザには結果が表示されますが、日本語の部分は文字化けします。 (コンパイルが通るもの / 通らないものの違いは不明です)

原因は Visual Studio で編集して保存する時ファイルの文字コードが Shift_JIS になってしまうからです。下の画像は Home.js をバイナリエディタで表示したものですが「日本語」の部分が 93 FA 96 78 8C EA と Shift_JIS になっているのが分かるでしょうか。

バイナリエディタで表示

何故そんなことになってしまうかというと、Visual Studio の設定がそうなっているからです。下の画像を見てください。Home.js の「保存オプションの詳細設定」ですが、そこで[エンコード(E)]が「日本語 (シフト JIS) - コードページ 932」になっているのに注目してください。

保存オプションの詳細設定

このため日本語を書き込んで保存すると Shift_JIS になってしまい、UTF-8 を期待している React ではコンパイルエラーまたは文字化けするということです。Home.js 以外にも ClientApp/src フォルダのほとんどのファイルがその設定になっていますので注意してください。

解決策は、下の画像のように[エンコード(E)]を「Unicode (UTF-8 シグネチャなし) - コードページ 65001」として上書きすることです。

保存オプションの詳細設定

または、メモ帳で問題の .js ファイルを開いて[文字コード(E)]を BOM なし UTF-8 に設定して上書きすることでも OK です。一旦メモ帳で BOM なし UTF-8 で保存してしまえば、その後は Visual Studio で .js ファイルを開いて日本語を書き込んでも UTF-8 で保存されるのでこの問題は起こりません。(詳しい仕組みは分かりませんが、Visual Studio の「保存オプションの詳細設定」設定に反映されるようです)

なお、BOM 付き UTF-8 にすると下の画像のように Unexpected Unicode BOM (Byte Order Mark) という警告が出ますので注意してください。(「保存オプションの詳細設定」でシグネチャ付きというのは BOM 付きのことですので注意)

BOM による警告

BOM 付きでもコンパイルエラーにはならず文字化けもしませんが、そこは警告に従って BOM なし UTF-8 にするのが良さそうです。

Visual Studio を操作して[追加(D)]⇒[新しい項目(W)...]で「JavaScript ファイル」を選んで作成すると BOM 付き UTF-8 になることに注意してください。コンパイルは通りますが、やはり Unexpected Unicode BOM (Byte Order Mark) という警告が出ます。

React プロジェクトでは ClientApp/src フォルダの .js ファイルだけでなく、Program.cs や Startup.cs でも保存オプションはデフォルトで「日本語 (シフト JIS) - コードページ 932」になっています。そこは ASP.NET Core プロジェクトでも同じです。

Program.cs や Startup.cs は Visual Studio が読んで処理を行いますので正しく文字コードが解釈され Shift_JIS でも問題は出ないはずです。ただ、ちょっと特殊な話ですが問題事例はありました。興味がありましたらTeratail のスレッド「asp.net.coreでの文字化けを解決したい。」を見てください。これはブラウザに送信する際は UTF-8 になっていたが、応答ヘッダやメタタグで文字コードの指定をしてなかったので、ブラウザが Shif_JIS として処理して文字化けを起こしたというもので、開発者のミスに近いものですが。

この記事の React の .js ファイルの話は、昔々 .NET Core 1 時代の MVC アプリで経験した「ASP.NET Core で文字化け」の .cshtml ファイルの件と似た話です。Microsoft も文字コードの問題はなかなか気が付かないのでしょうね。

Tags: , , , ,

React

VS2022 .NET 6.0 React のユーザー認証

by WebSurfer 2022年3月30日 11:37

Visual Studio 2022 の「React.js での ASP.NET Core」のテンプレートで[フレームワーク(F)]を「.NET 6.0 (長期的なサポート)」とし[認証の種類(A)]を「個別のアカウント」として作成したプロジェクトでユーザー認証に失敗する件につき自分が調べたことと解決策を備忘録として残しておきます。

2022/4/18 追記: 原因は .NET 6.0 Known Issues によるとプロキシのキャッシュとのことです。それに workaround が書いてありますが、この記事の一つ目の解決策と同じになります。詳しくはこの記事の下の方に追記します。

エラーメッセージ

有効な id と password でログインして ASP.NET Core Identity による認証が通っても、Fetch Data をクリックして Web API のデータを取得しようとすると、上の画像のように、

Unhandled Rejection (SyntaxError): Unexpected end of JSON input

・・・というエラーメッセージが出て失敗します。

理由ははっきりしませんが、エラーメッセージやいろいろ試してみた結果から想像するに、Web API のユーザー認証用の JWT トークンに含まれる Issuer と OpenId Connect を呼び出す際に使用する Authority に不整合が出るということのように思われます。

Visual Studio 2022 でフレームワークが .NET 6.0 の React プロジェクトを実行する場合、バックエンドの ASP.NET アプリ (Web API と IdentityServer を含む) に対する要求はプロキシを通るのでその影響があるのかもしれません。(フレームワークを .NET Core 3.1 とした場合は上のような問題は出ませんが、違いはプロキシが使われてないぐらいしかありませんので)

どういう状況かを詳しく書くと以下の通りです。

初期画面ではメニューバーに Home, Counter, Fetch data という 3 つのメニューが表示されており、その中の Fetch data はサーバーの ASP.NET Core Web API からデータを取得して表示するようになっています。

プロジェクトを作成する際[認証の種類(A)]に「個別のアカウント」を選んだ場合、サーバーの Web API には JWT トークンベースの認証が実装され、JWT トークンなしで Web API にアクセスすると HTTP 401 Unauthorized 応答が返ってくるように設定されます。

Visual Studio でアプリを実行し、認証を受けてない状態でメニューバーの Fetch data をクリックするとログイン画面にリダイレクトされます。ログイン画面で id と password を入力して認証を受けると JWT トークンが発行され、Fetch data が Web API にアクセスしてデータを取りに行きますが、その際 JWT トークンが要求ヘッダに含まれるようになります。(先にメニュバーの Login をクリックしてログイン画面でログインしてから Fetch data をクリックしても同様です)

Web API には JWT トークンを持ってアクセスに行くので認証が通って Web API からのデータの取得に成功し下の画像のようにデータが表示されるということになっています。

Fetch data 画面

しかし、.NET 6.0 の場合はこの記事の一番上の画像のエラーとなります。(ちなみに、.NET Core 3.1 の場合は認証が通って上の画面の通りデータが表示されます)

エラーメッセージによると FetchData.js の 60 行目でエラーとなったとのこと。デバッグしてみると HTTP 401 Unauthorized という応答が返ってきています。

デバッグ画面

Fiddler で調べてみると、下の画像の青枠で示した通り JWT トークンは送信されていますが、赤枠で囲った部分に、

www-authenticate: Bearer error="invalid_token", error_description="The issuer 'https://localhost:44495' is invalid"

・・・と表示されているように Issuer が無効とのことです。

Fiddler の画面

JWT トークンに含まれる Issuer 'https://localhost:44495' と何かに不整合が出ているのは間違いないようですが、.NET Core 3.1 では問題ないのに .NET 6.0 で問題が出る理由が分かりませんでした。

調べてみると、Visual Studio 2022 で作成する .NET 6.0 の React アプリは、Viusal Studio から実行すると React 部分は Node.js 開発サーバーでホストされ (ホットリロードが可能になっています)、サーバー側の ASP.NET Core アプリ (Web API と IdentityServer を含む) は IIS Express または Kestrel でホストされるようになっています。

Node.js 開発サーバーと IIS Express (または Kestrel) では必然的にホスト名が異なりクロスドメインの問題が出ます。その問題を解決するために http-proxy-module (HPM) という Node.js のプロキシを使っていて、Visual Studio から .NET 6.0 の React アプリを起動するとプロジェクトに含まれている setupProxy.js の設定に従い以下の通り HPM を起動する画面が出ます。

http-proxy-module (HPM)

上の画像の通り Web API のコントローラーの URL 'weatherforecast' や IdentityServer の APIがプロキシの対象に含まれており、それへの要求を受けると https://localhost:44395 のサーバー (IIS Express) へ渡すように設定されています。

(https://localhost:44395 は launchSettings.json に設定されている IIS Express を使った場合の URL です。Kestrel を使う場合は別のポートになりますが、Visual Studio でアプリを起動する際の設定に従い自動的に切り替わります)

Node.js 開発サーバーの URL はプロジェクトファイルに含まれている SpaProxyServerUrl の設定になるようです。下の画像の通り https://localhost:44495 になっています。

プロジェクトファイル

まとめると、開発環境での URL 'https://localhost:xxxxx' のポート xxxxx は以下のようになります。

ブラウザ (44495) ⇔ Node.js 開発サーバー (44495) ⇔ NPM ⇔ IIS Express (44395)

JWT トークンを発行するのは IIS Express でホストされる IdentityServer ですが、その際 "iss" : "https://localhost:44495" と設定されます。それは JSON Web Tokens - jwt.io で JWT トークンを解析して下の画像のようになっていることで確認しました。(44395 でなく 44495 となっている理由は不明)

JWT トークンを解析

さらに調べていると Microsoft のドキュメント「SPA の認証と承認」の Azure App Service on Linux というセクションに "Linux での Azure App Service デプロイについては、Startup.ConfigureServices に発行者を明示的に指定します" と書いてあって、その対応のためのコードが載っているのを見つけました。

自分の環境には Linux も Azure も関係ないはずですが、試しにそのコードを Program.cs に追加したら問題は解決しました。コメントで「// 追加」とした部分が追加したコードで、それ以外はテンプレートが自動生成した既存のコードです。JwtBearerOptions クラスの Authority プロパティを "https://localhost:44495" に設定しています。

using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI;
using Microsoft.EntityFrameworkCore;
using ReactNet6Identity.Data;
using ReactNet6Identity.Models;

// 追加
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;

var builder = WebApplication.CreateBuilder(args);

// 追加
builder.Services.Configure<JwtBearerOptions>(
    IdentityServerJwtConstants.IdentityServerJwtBearerScheme,
    options =>
    {
        options.Authority = "https://localhost:44495";
    });

// ・・・以下略・・・

Microsoft のドキュメント「JwtBearerOptions.Authority プロパティ」の説明によると、"OpenIdConnect を呼び出す際に使用する Authority を取得または設定します" ということで、上のコードはその Authority を "https://localhost:44495" に設定したということになるはずです。

でも無知な自分には OpenIdConnect って何? Authority って何? ・・・という感じで、なぜ問題が出なくなったのかさっぱり分かりません。(汗)

もう一つ見つけた解決策は、これもなぜ問題が出なくなるのが分かりませんが、IdentityServerOptions クラスの IssuerUri プロパティに "https://localhost:44495" を設定することです。Program.cs の既存のコードに手を加えて以下のようにします。

builder.Services.AddIdentityServer(options => 
    options.IssuerUri = "https://localhost:44495")
    .AddApiAuthorization<ApplicationUser, ApplicationDbContext>();

このような設定をしなくても、そもそもが Issuer は "https://localhost:44495" になるのに、なぜこれが解決になるのか分かりません。想像ですが、明示的に設定すると JwtBearOptions の Authority に影響があるのかもしれません。

React アプリの認証について少し調べてみましたが、Microsoft のドキュメント「SPA の認証と承認」によると、React アプリには ASP.NET Core Identity をユーザー情報のストアとして使う OpenID Connect ベースの認証サーバー Duende Identity Server を使えるように Visual Studio のテンプレートが作られているようです。

では OpenID Connect とは何かですが、「Microsoft ID プラットフォームと OpenID Connect プロトコル」によると、"OpenID Connect (OIDC) は OAuth 2.0 を基盤とした認証プロトコルであり、ユーザーをアプリケーションに安全にサインインさせるために利用できます" とのことです。

ASP.NET Core MVC などに使う ASP.NET Core Identity によるクッキーベースの認証や、先の記事「ASP.NET Core Web API と JWT」で書いたようなトークンベースの単純な認証方式とは違うようだということは何となく分かりました。勉強しなくては・・・


2022/4/18 追記

"The issuer 'https://localhost:44495' is invalid" となる原因が分かりました。.NET 6.0 Known IssuesSPA template issues with Individual authentication when running in development というセクションがあって、それに以下のように書いてありました。

"The first time SPA apps are run, the authority for the spa proxy might be incorrectly cached which results in the JWT bearer being rejected due to Invalid issuer. The workaround is to just restart the SPA app and the issue will be resolved. If restarting doesn't resolve the problem, another workaround is to specify the authority for your app in Program.cs: builder.Services.Configure<JwtBearerOptions>("IdentityServerJwtBearer", o => o.Authority = "https://localhost:44416"); where 44416 is the port for the spa proxy."

要するに、プロキシが Authority (JWT トークンの発行者 = IdentityServer) を間違ってキャッシュするため、送信されてきた JWT トークンに含まれる発行者と一致しなくて、invalid と判定されたということのようです。

.NET 6.0 Known Issues に書いてある workaround は Authority を JWT トークンに含まれる "iss" : "https://localhost:xxxxx" に合わせるということで、この記事に 2 つ書いてある解決策の一つ目と同じです。この記事の例では JWT トークンは "iss" : "https://localhost:44495" となっており、それを Authority に設定しています。

この記事の二つ目の解決策は IdentityServer 側で発行者を "https://localhost:44495" に設定するというものです。これでプロキシのキャッシュの Authority が "https://localhost:44495" になって、JWT トークンの "iss" : "https://localhost:44495" と一致するのではないかと想像してます。

ただ、まだ分からない点があります。それはテンプレートでプロジェクトを作成した状態のデフォルトで、JWT トークンに含まれる "iss" が "https://localhost:44495" になることです。この記事の例では IdentityServer は "https://localhost:44395" なのでそれが Authority になり、IdentityServer が発行した JWT トークンは "iss" : "https://localhost:44395" になるはずなのですが・・・

もう一つ、.NET 6.0 Known Issues に書いてある以下の件ですが、こちらはプロジェクト作成直後の DB が生成されてない状態で登録・ログインしようとすると表示されるはずのメッセージが、プロキシの問題で表示されないというものです。JWT トークンが invalid と判定されるというこの記事の話とは別の問題です。

"When using localdb (default when creating projects in VS), the normal database apply migrations error page will not be displayed correctly due to the spa proxy. This will result in errors when going to the fetch data page. Apply the migrations via 'dotnet ef database update' to create the database."

Tags: , ,

React

About this blog

2010年5月にこのブログを立ち上げました。主に ASP.NET Web アプリ関係の記事です。

Calendar

<<  2024年3月  >>
252627282912
3456789
10111213141516
17181920212223
24252627282930
31123456

View posts in large calendar