質問をすることでしか得られない、回答やアドバイスがある。

15分調べてもわからないことは、質問しよう!

新規登録して質問してみよう
ただいま回答率
85.34%
blazor

Blazorは、マイクロソフトが開発している.NETベースのWebアプリフレームワークです。C#でフロントエンドもバックエンドも一貫して書くことが可能。クライアントサイド(WebAssembly)とサーバーサイド形式のホスティングモデルがあります。

Q&A

解決済

1回答

108閲覧

blazor webassemblyでAuthorizeViewを使用した時の認証状態変更について

ta0101

総合スコア1

blazor

Blazorは、マイクロソフトが開発している.NETベースのWebアプリフレームワークです。C#でフロントエンドもバックエンドも一貫して書くことが可能。クライアントサイド(WebAssembly)とサーバーサイド形式のホスティングモデルがあります。

0グッド

0クリップ

投稿2025/01/14 08:23

編集2025/01/14 23:14

実現したいこと

[実現したい事]※BlazorとAsp.netが初心者です
blazor webassemblyアプリケーションで、
1.ログインしていない時、未認証状態にする。
2.ログインが成功した時にAuthorizeViewを認証状態にする。
[実装した内容]
<前準備>
1.AuthenticationStateProviderを継承したクラスを作成。(CustomeAuthticateProviderという名前で作成)
2.Program.csにbuilder.Services.AddScopedを使用して、手順1で作成したクラスを登録。
(この時、AddAuthorizationCoreやAddCascadingAuthenticationStateも呼び出しています)
<ログインから認証状態の流れ>
1.画面表示。
(この状態では未認証。最初の画面表示時、自作したCustomeAuthticateProviderクラスのGetAuthenticationStateAsyncが呼び出され、未認証状態なので、空のAuthenticationStateを返しておく)
2.ログインする。ログイン時に、自作で作成したWEB APIを呼び出し、呼び出し結果からWEB API側で作成した、JWTのトークンを取得する。
3.ログイン時に使用したユーザーIDとJWTのトークンを、ブラウザ側のセッションストレージに保存する。
4.CustomeAuthticateProviderクラスから、NotifyAuthenticationStateChangedメソッドを実行する。
この時、引数には手順3でセッションストレージに保存したユーザーIDをClaimsIdentityに含めた、AuthenticationStateを渡す。
(NotifyAuthenticationStateChangedメソッドは、継承元のAuthenticationStateProviderクラスで定義されているメソッド)
5.認証状態に変更なし(AuthorizeViewを使用しているどのページに移動しても変化なし)

発生している問題・分からないこと

[わからない事]
AuthorizeView認証状態の更新方法がわかりません。
[発生している問題]
AuthenticationStateProviderのNotifyAuthenticationStateChangedメソッドを呼び出しても、認証状態が更新されないです。
[デバッグ時の状況]
AuthenticationStateProviderクラスのNotifyAuthenticationStateChangedを何度も実行しても、AuthenticationStateProviderのGetAuthenticationStateAsyncがAuthorizeView側から呼ばれません。(この時、AuthenticationStateProviderクラスのAuthenticationStateChangedイベントは呼ばれますが、肝心のAuthorizeViewの状態は更新されません。)

該当のソースコード

C#

1using BlazorApp1; 2using Microsoft.AspNetCore.Components.Authorization; 3using Microsoft.AspNetCore.Components.Web; 4using Microsoft.AspNetCore.Components.WebAssembly.Hosting; 5using Blazored.SessionStorage; 6using Microsoft.AspNetCore.Authorization; 7using Microsoft.Extensions.Options; 8 9var builder = WebAssemblyHostBuilder.CreateDefault(args); 10builder.RootComponents.Add<App>("#app"); 11builder.RootComponents.Add<HeadOutlet>("head::after"); 12 13builder.Services.AddScoped(sp => new HttpClient { BaseAddress = new Uri(builder.HostEnvironment.BaseAddress) }); 14 15// ブラウザ側のセッションストレージを使用する為に 16// NuGetでインストールしたBlazored.SessionStorage(2.4.0)を使用 17builder.Services.AddBlazoredSessionStorage(); 18 19// 自作で作成したAuthenticationStateProviderを継承したクラスを登録 20builder.Services.AddScoped<AuthenticationStateProvider, CustomeAuthticateProvider>(); 21 22// 自作で定義したインターフェースの登録。 23builder.Services.AddScoped<ICustomeAuthticateProvider, CustomeAuthticateProvider>(); 24// 自作のサービスクラスを登録 25builder.Services.AddScoped<IAuthService, CustomeAuthService>(); 26 27builder.Services.AddAuthorizationCore(); 28 29builder.Services.AddCascadingAuthenticationState(); 30 31await builder.Build().RunAsync(); 32

c#

1using Microsoft.AspNetCore.Components.Authorization; 2using Microsoft.AspNetCore.Components; 3using System.Security.Claims; 4using System.Net.Http.Headers; 5using Blazored.SessionStorage; 6 7namespace BlazorApp1; 8 9public interface ICustomeAuthticateProvider 10{ 11 public Task MarkUserAsAuthenticated(string userID, string authToken); 12 13 public Task MarkUserAsLoggedOut(); 14 15 public void ExecuteNotifyAuthenticationStateChanged(); 16} 17 18internal class CustomeAuthticateProvider : AuthenticationStateProvider, ICustomeAuthticateProvider 19{ 20 private readonly HttpClient _httpClient; 21 22 private readonly ISessionStorageService _sessionStorageService; 23 24 public CustomeAuthticateProvider(HttpClient httpClient, ISessionStorageService sessionStorageService) 25 { 26 _httpClient = httpClient; 27 _sessionStorageService = sessionStorageService; 28 base.AuthenticationStateChanged += CustomeAuthticateProvider_AuthenticationStateChanged; 29 } 30 31 private async void CustomeAuthticateProvider_AuthenticationStateChanged(Task<AuthenticationState> task) 32 { 33 var state = await task; 34 if (state != null) 35 { 36 string name = state.User.Identity?.Name ?? ""; 37 Console.WriteLine($"呼ばれたよ!!======= name={name}"); 38 } 39 } 40 41 public async override Task<AuthenticationState> GetAuthenticationStateAsync() 42 { 43 // ローカルストレージからトークンとユーザ名を取得 44 var savedToken = await _sessionStorageService.GetItemAsync<string>("authToken"); 45 var userID = await _sessionStorageService.GetItemAsync<string>("userID"); 46 47 if (string.IsNullOrWhiteSpace(savedToken)) 48 { 49 return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity())); 50 } 51 52 _httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("bearer", savedToken); 53 54 var identity = new ClaimsIdentity( 55 [ 56 new Claim(ClaimTypes.Name, userID), 57 ], "Test"); 58 var principal = new ClaimsPrincipal(identity); 59 var authenticationState = new AuthenticationState(principal); 60 return authenticationState; 61 62 } 63 64 public async Task MarkUserAsAuthenticated(string userID, string authToken) 65 { 66 // ローカルストレージに認証情報を保持して変更通知を行う 67 await _sessionStorageService.SetItemAsync("userID", userID); 68 await _sessionStorageService.SetItemAsync("authToken", authToken); 69 70 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); 71 } 72 73 public async Task MarkUserAsLoggedOut() 74 { 75 // ローカルストレージの認証情報を削除して変更通知を行う 76 await _sessionStorageService.RemoveItemAsync("userID"); 77 await _sessionStorageService.RemoveItemAsync("authToken"); 78 if (_httpClient.DefaultRequestHeaders.Authorization != null) 79 { 80 _httpClient.DefaultRequestHeaders.Authorization = null; 81 } 82 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); 83 } 84 85 public void ExecuteNotifyAuthenticationStateChanged() 86 { 87 NotifyAuthenticationStateChanged(GetAuthenticationStateAsync()); 88 } 89}

c#

1using Microsoft.AspNetCore.Authorization; 2 3namespace BlazorApp1 4{ 5 public class TestAuthHandler : AuthorizationHandler<TestRequirement> 6 { 7 protected override Task HandleRequirementAsync(AuthorizationHandlerContext context, TestRequirement requirement) 8 { 9 //context.Succeed(requirement); 10 //context.Fail(); 11 12 var identityName = context.User.Identity?.Name; 13 if (identityName is null) 14 { 15 context.Fail(); 16 return Task.CompletedTask; 17 } 18 19 context.Succeed(requirement); 20 21 return Task.CompletedTask; 22 } 23 } 24} 25 26

試したこと・調べたこと

  • teratailやGoogle等で検索した
  • ソースコードを自分なりに変更した
  • 知人に聞いた
  • その他
上記の詳細・結果

[他に試したこと]
ポリシーを追加して、そのポリシーに対応したIAuthorizationHandlerを実装したクラスのHandleRequirementAsyncメソッドの引数のAuthorizationHandlerContextに、どのような値が入ってきているか確認
<手順>
1.前準備として、Program.csの中で、builder.Services.AddAuthorizationCoreの引数のアクション内で、"Test"という名前でconfigure.AddPolicyメソッドを実行しポリシーを作成し。
2.builder.Services.AddScoped<IAuthorizationHandler, TestAuthHandler>();という一行を追加して、TestAuthHandlerクラスが、AuthorizeViewでポリシーに"Test"を設定している場合、表示時に呼ばれるように設定。
<確認内容>
1.AuthorizeViewの初期画面表示時は、AuthorizationHandlerContextに何も設定されていないものが設定されていた。
2.ログインしてブラウザ側のセッションストレージにユーザーIDなどを保存し、AuthenticationStateProviderクラスのNotifyAuthenticationStateChangedを呼び出しても、AuthenticationStateProviderクラスのGetAuthenticationStateAsyncメソッドが呼び出されない為、AuthorizationHandlerContextの中身は空のままです。

補足

[使用しているツールやバージョン]
Microsoft Visual Studio Community 2022 (64 ビット) (Version 17.12.3)
プロジェクトの.netのターゲット:net9.0

気になる質問をクリップする

クリップした質問は、後からいつでもMYページで確認できます。

またクリップした質問に回答があった際、通知やメールを受け取ることができます。

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

guest

回答1

0

自己解決

自己解決しました。
Program.csで、
①→builder.Services.AddScoped<AuthenticationStateProvider, CustomeAuthticateProvider>();
②→builder.Services.AddScoped<ICustomeAuthticateProvider, CustomeAuthticateProvider>();
の2行の部分が間違っていました。

自作のCustomeAuthticateProviderは、AuthenticationStateProviderとICustomeAuthticateProviderを継承してます。どちらも使用する為に、①と②で登録しましたが、①と②で同一のCustomeAuthticateProviderクラスのインスタンスが設定されると勘違いしていました。

①と②で別々のCustomeAuthticateProviderクラスのインスタンスが設定されることにより、ログイン時は②のCustomeAuthticateProviderのインスタンスのNotifyAuthenticationStateChangedを呼び出していますが、AuthorizeViewは①で設定した方のインスタンスにバインドされている為、いくら②のインスタンスでNotifyAuthenticationStateChangedを呼び出しても反応しない、という状況でした。

以下に修正することにより、正しく動くようになりました。
builder.Services.AddScoped<CustomeAuthticateProvider>();←ここでインスタンスを登録
builder.Services.AddScoped<AuthenticationStateProvider>(provider =>
provider.GetRequiredService<CustomeAuthticateProvider>());←ここで同一インスタンスを取得するように修正
builder.Services.AddScoped<ICustomeAuthticateProvider>(provider =>
provider.GetRequiredService<CustomeAuthticateProvider>());←こっちも、同一インスタンスを取得するように修正。

お騒がせしてしまい、申し訳ございませんでした。
閲覧して下さり、調べてみたりしてくださった方、ありがとございました。

以上です。

投稿2025/01/15 06:48

ta0101

総合スコア1

バッドをするには、ログインかつ

こちらの条件を満たす必要があります。

あなたの回答

tips

太字

斜体

打ち消し線

見出し

引用テキストの挿入

コードの挿入

リンクの挿入

リストの挿入

番号リストの挿入

表の挿入

水平線の挿入

プレビュー

15分調べてもわからないことは
teratailで質問しよう!

ただいまの回答率
85.34%

質問をまとめることで
思考を整理して素早く解決

テンプレート機能で
簡単に質問をまとめる

質問する

関連した質問