Blazorログインページを備えたEntityFramework

Oct 13 2020

BlazorServerでIdentityFrameworkを使用する場合、Microsoftの公式声明は、サポートされておらず、代わりにRazorログインページを使用することです。問題は、BlazorがCookieを書き込めないことです。

これにはいくつかの欠点があります。

  • HTMLレイアウトファイルを再利用できません。Razorの重複レイアウトファイルを再作成する必要があります。
  • Blazorページにログインボタンを埋め込むことはできません
  • チェックアウトエクスペリエンスの一部としてオプションでログインする必要がある場合のユーザーエクスペリエンスの低下

この男は、BlazorログインページをBlazor WebAssemblyで機能させる方法を考え出しました...彼がこの問題をどのように回避したか、または同様のアプローチがBlazorServerで機能するかどうかはわかりません。

私は別の解決策を考えていました。問題はクッキーを保存することです。ローカルストレージはBlazorで使用できますが、ローカルストレージはセキュリティトークンに対して安全ではありません。ただし、Cookieは、同様の方法でJavaScript相互運用機能を介して設定することもできます。

このアプローチは機能し、ログイン後にJavaScript相互運用機能を介してログインCookieを設定し、それ以降のページの読み込み時にCookieが送信されますか?誰かがそれをしましたか?

単一ページの管理コンテンツの場合、ログインフォームを表示し、ログイン後にChildContentを表示するGatedContentコンポーネントを作成するより簡単なアプローチを見つけました。もちろん、これはページ更新時のセッションを保持しませんが、場合によっては機能します。

回答

1 EtienneCharland Oct 14 2020 at 04:29

単一ページの管理コンテンツのソリューションは次のとおりです。GatedContent

ログインフォームが表示され、ログインに成功すると、ゲートされたコンテンツが表示されます。

SpinnerButtonはここで定義されます。

@using Microsoft.AspNetCore.Identity
@using System.ComponentModel.DataAnnotations
@inject NavigationManager navManager
@inject SignInManager<ApplicationUser> signInManager
@inject UserManager<ApplicationUser> userManager;

@if (!LoggedIn)
{
<EditForm Context="formContext" class="form-signin" OnValidSubmit="@SubmitAsync" Model="this">
    <DataAnnotationsValidator />

    <div style="margin-bottom: 1rem; margin-top: 1rem;" class="row">
        <Field For="@(() => Username)" Width="4" Required="true">
            <RadzenTextBox @bind-Value="@Username" class="form-control" Style="margin-bottom: 0px;" />
        </Field>
    </div>
    <div style="margin-bottom: 1rem" class="row">
        <Field For="@(() => Password)" Width="4" Required="true">
            <RadzenPassword @bind-Value="@Password" class="form-control" />
        </Field>
    </div>

    <SpinnerButton @ref="ButtonRef" style="width:150px" Text="Login" ButtonType="@((Radzen.ButtonType)ButtonType.Submit)" ButtonStyle="@((Radzen.ButtonStyle)ButtonStyle.Primary)" OnSubmit="LogInAsync" />
    @if (Error.HasValue())
    {
        <div class="red">@Error</div>
    }
</EditForm>
}
else
{
    @ChildContent
}
@code {
    public SpinnerButton? ButtonRef { get; set; }
    public async Task SubmitAsync() => await ButtonRef!.FormSubmitAsync().ConfigureAwait(false);

    [Required]
    public string Username { get; set; } = string.Empty;

    [Required]
    public string Password { get; set; } = string.Empty;

    string? Error { get; set; }
    bool LoggedIn { get; set; }

    [Parameter]
    public RenderFragment? ChildContent { get; set; }

    public async Task LogInAsync()
    {
        Error = null;
        try
        {
            var user = await userManager.FindByNameAsync(Username).ConfigureAwait(false);
            if (user != null)
            {
                var isAdmin = await userManager.IsInRoleAsync(user, ApplicationRole.Admin).ConfigureAwait(false);
                if (isAdmin)
                {
                    var singInResult = await signInManager.CheckPasswordSignInAsync(user, Password, true).ConfigureAwait(false);
                    if (singInResult.Succeeded)
                    {
                        LoggedIn = true;
                    }
                    else
                    {
                        Error = "Invalid password";
                    }
                }
                else
                {
                    Error = "User is not Admin";
                }
            }
            else
            {
                Error = "Username not found";
            }
        }
        catch (Exception ex)
        {
            Error = ex.Message;
        }
    }
}

このように使用します

<GatedContent>
    This is an admin page.
</GatedContent>

もちろん、このソリューションはページの再読み込み後の状態を保持しませんが、単純な管理ページでは機能します。

JoeGER94 Oct 13 2020 at 21:29

私の回避策は次のとおりです。次のようなSignInControllerを作成しました

    public class SignInController : ControllerBase
    {
        private readonly SignInManager<IdentityUser> _signInManager;

        public SignInController(SignInManager<IdentityUser> signInManager)
        {
            _signInManager = signInManager;
        }

        [HttpPost("/signin")]
        public IActionResult Index([FromForm]string username, [FromForm]string password, [FromForm]string rememberMe)
        {
            bool.TryParse(rememberMe, out bool res);
            var signInResult = _signInManager.PasswordSignInAsync(username, password, res, false);
            if (signInResult.Result.Succeeded)
            {
                return Redirect("/");
            }
            return Redirect("/login/"+ signInResult.Result.Succeeded);
        }

        [HttpPost("/signout")]
        public async Task<IActionResult> Logout()
        {
            if (_signInManager.IsSignedIn(User))
            {
                await _signInManager.SignOutAsync();
            }
            return Redirect("/");
        }
    }

そして、私はこのようなlogin.razorを持っています:

@page "/login"
    <form action="/signin" method="post">
            <div class="form-group">
                <p> Username </p>
                <input id="username" Name="username" />
            </div>
            <div class="form-group">
                <p>password<p/>
                <input type="password" id="password" />
            </div>
            <div class="form-group">
                <p> remember me? <p/>
                <input type="checkbox" id="rememberMe" />
            </div>
            <button type="Submit" Text="login" />
    </form>

この問題は、HTTP / Sリクエストを使用する場合にのみ機能するsignInManagerが原因で発生します。それ以外の場合は、例外がスローされます。GitHubのdotnet / aspcore.netリポジトリにもそれに関するいくつかのレポートがあります。

現時点では、これがBlazorサーバーで使用する唯一の方法です(私は知っています)。