I'm implementing a custom AuthenticationStateProvider and using information from user claims in mainLayout. As far as I understood after executing NotifyAuthenticationStateChanged method should itself rerender all the components which use <AuthorizeView> e.t.c. But it doesn't. Moreover, I implemented my own reloader for mainLayout and I reload it using StateHasChanged after the user logs in. But for some reason it still thinks that there is no one authorized and renders block of code in <NotAuthorized> block. But if I reload the page manually GetAuthenticationStateAsync method is executed and after that block of code inside <Authorized> is rendered. Am I doing smth wrong or it's a bug?
My CustomAuthenticationStateProvider code:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ISessionStorageService _sessionStorage;
public CustomAuthenticationStateProvider(ISessionStorageService sessionStorage)
{
_sessionStorage = sessionStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var userModel = await _sessionStorage.GetItemAsync<AuthorizedModel>("userModel");
var identity = new ClaimsIdentity();
if (userModel != null)
{
identity = new ClaimsIdentity( new []
{
//Some my claims
...
}, "api");
}
else
{
identity = new ClaimsIdentity();
}
var claimsPrincipal = new ClaimsPrincipal(identity);
return new AuthenticationState(claimsPrincipal);
}
public void AuthenticateUser(AuthorizedModel model)
{
var identity = new ClaimsIdentity(new []
{
//Some my claims
...
});
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
public async Task LogUserOut()
{
await _sessionStorage.RemoveItemAsync("nickName");
var identity = new ClaimsIdentity();
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(user)));
}
}
My login:
public async Task HandleValidSubmit()
{
var authorizedUser = await loginRepository.TryLogin(_model);
...
((CustomAuthenticationStateProvider)authenticationStateProvider).AuthenticateUser(authorizedUser);
await sessionStorage.SetItemAsync("userModel", authorizedUser);
navigationManager.NavigateTo("/");
//This is for my custom page reload
authorizationState.LoggedIn = true;
}
My MainLayout:
#inherits LayoutComponentBase
...
<AuthorizeView>
<Authorized>
<UserInfo />
</Authorized>
<NotAuthorized>
//Some block of code for non-authorized
...
</NotAuthorized>
</AuthorizeView>
...
And finally UserInfo code:
#using System.Security.Claims
...
<div class="user-info">
<span class="user-name">
#userFirstName
<strong>#userSecondName</strong>
</span>
<span class="user-role">#userNickName</span>
<span class="user-status">
<i class="fa fa-circle"></i>
<span>Online</span>
</span>
</div>
#code{
[CascadingParameter]
private Task<AuthenticationState> authenticationStateTask { get; set; }
ClaimsPrincipal user;
string userFirstName;
...
protected override async Task OnInitializedAsync()
{
user = (await authenticationStateTask).User;
//Here I just get userInfo from claims
...
}
}
This method:
public void AuthenticateUser(AuthorizedModel model)
{
var identity = new ClaimsIdentity(new []
{
//Some my claims
...
});
var user = new ClaimsPrincipal(identity);
NotifyAuthenticationStateChanged(Task.FromResult(new
AuthenticationState(user)));
}
Should be:
public void AuthenticateUser()
{
// If AuthorizedModel model contains a Jwt token or whatever which you
// save in the
// local storage, then add it back as a parameter to the AuthenticateUser
// and place here the logic to save it in the local storage
// After which call NotifyAuthenticationStateChanged method like this.
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
Note: The call to StateHasChanged method has got nothing to do with the current
issue. The call to the base class's NotifyAuthenticationStateChanged is
done so that the base class, that is AuthenticationStateProvider, invokes
the AuthenticationStateChanged event, passing the AuthenticationState
object to subscribers, in that case, to the CascadingAuthenticationState
component, tell him to refresh it's data (AuthenticationState)
Note: If the issue still persists in spite of the above changes, ensure that you
add to the DI container the following:
services.AddScoped<CustomAuthenticationStateProvider>();
services.AddScoped<AuthenticationStateProvider>(provider =>
provider.GetRequiredService<CustomAuthenticationStateProvider>());
Hope this helps...
Related
I'm working on a Blazor Server project using the default Microsoft Identity Platform.
My goal is to get/create a user in my db and save it to local storage after microsoft login is completed.
In the startup I'm sucessfully able to use the OnTokenValidated event to do some action after login. However it's not possible to write to local storage in this stage since the page isn't rendered yet.
I'd like to do something like this which is possible with Webassembly.
<RemoteAuthenticatorView Action="#Action" OnLogInSucceeded="SomeCode" />
Does anyone know a way to do this without using a solution like adding OnAfterRenderAsync in the MainLayout, which will fire on each page reload. I'd like to call a method after the Identity login redirects back to my site in a state where LocalStorage is accessible.
You do need to put some code in OnAfterRenderAsync, but in this demo I've put it in App and check if the component has already rendered.
Here's a demo and some code to interact with LocalStorage. You should be able to adapt it to fit your needs.
First a service to encapsulate getting and setting to Local Storage
using Microsoft.AspNetCore.Components.Server.ProtectedBrowserStorage;
public class LocalStorageService
{
private readonly ProtectedLocalStorage _storage;
public LocalStorageService(ProtectedLocalStorage storage)
=> _storage = storage;
public async ValueTask<CommandResult> SaveAsync<TRecord>(CommandRequest<TRecord> request)
{
if (request.Record is not null)
await _storage.SetAsync(request.StorageName, request.Record);
// No return so we return success!
return CommandResult.Success();
}
public async ValueTask<RecordQueryResult<TRecord>> ReadAsync<TRecord>(RecordQueryRequest<TRecord> request)
{
// We need to cover the situation were the component calling this is in the initial page
// and Blazor server is trying to statically render the page
try
{
var result = await _storage.GetAsync<TRecord>(request.StorageName);
return new RecordQueryResult<TRecord> { Successful = result.Success, Record = result.Value, Message = $"Failed to retrieve a value for {request.StorageName}" };
}
catch
{
return new RecordQueryResult<TRecord> { Successful = false, Message = $"Failed to retrieve a value for {request.StorageName}" };
}
}
}
The CQS Request and Result objects:
public record CommandRequest<TRecord>(string StorageName, TRecord Record);
public record RecordQueryRequest<TRecord>(string StorageName);
public record CommandResult
{
public bool Successful { get; init; }
public string Message { get; init; } = string.Empty;
public static CommandResult Success()
=> new CommandResult { Successful = true };
public static CommandResult Failure(string message)
=> new CommandResult { Successful = false };
}
public record RecordQueryResult<TRecord>
{
public TRecord? Record { get; init; }
public bool Successful { get; init; }
public string Message { get; init; } = string.Empty;
public static RecordQueryResult<TRecord> Success(TRecord record)
=> new RecordQueryResult<TRecord> { Record = record, Successful = true };
public static RecordQueryResult<TRecord> Failure(string message)
=> new RecordQueryResult<TRecord> { Successful = false };
}
Registered like this:
builder.Services.AddScoped<LocalStorageService>();
My simple Data:
public record TestData( string LastSaved);
Add code to App to set as if you are getting data after login. This implements a custom after render handler.
#inject LocalStorageService Service
#implements IHandleAfterRender
<CascadingAuthenticationState>
<Router AppAssembly="#typeof(App).Assembly">
<Found Context="routeData">
<AuthorizeRouteView RouteData="#routeData" DefaultLayout="#typeof(MainLayout)" />
<FocusOnNavigate RouteData="#routeData" Selector="h1" />
</Found>
<NotFound>
<PageTitle>Not found</PageTitle>
<LayoutView Layout="#typeof(MainLayout)">
<p role="alert">Sorry, theres nothing at this address.</p>
</LayoutView>
</NotFound>
</Router>
</CascadingAuthenticationState>
#code {
private bool _hasCalledOnAfterRender;
// implements a custom IHandleAfterRender handler
async Task IHandleAfterRender.OnAfterRenderAsync()
{
// Only do if first render and the data in local storage is empty
if (!_hasCalledOnAfterRender && !await GetData())
{
var newData = new TestData($"Saved at {DateTime.Now.ToLongTimeString()}");
var result = await this.Service.SaveAsync<TestData>(new CommandRequest<TestData>("TestData", newData));
_hasCalledOnAfterRender = true;
}
}
private async Task<bool> GetData()
{
var result = await this.Service.ReadAsync<TestData>(new RecordQueryRequest<TestData>("TestData"));
return result?.Successful ?? false;
}
}
And my test route/page to display the data.
#page "/"
#inject LocalStorageService Service
#implements IDisposable
<PageTitle>Index</PageTitle>
<h1>Hello, world!</h1>
Welcome to your new app.
<SurveyPrompt Title="How is Blazor working for you?" />
<div class="bg-black text-white m-3">
Test Data Last Saved at : #this.data.LastSaved
</div>
<div class="m-3">
<button class="btn btn-primary" #onclick=SaveToLocal>Save Data to Local</button>
</div>
#code {
private TestData data = new TestData(string.Empty);
protected override async Task OnInitializedAsync()
{
await this.GetData();
this.Service.StorageChanged += this.DataChanged;
}
private async void DataChanged(object? sender, EventArgs e)
{
await this.GetData();
await this.InvokeAsync(StateHasChanged);
}
private async Task<bool> GetData()
{
var result = await this.Service.ReadAsync<TestData>(new RecordQueryRequest<TestData>("TestData"));
data = result?.Record ?? new TestData(string.Empty);
return result?.Successful ?? false;
}
private async Task SaveToLocal()
{
var newData = new TestData($"Saved at {DateTime.Now.ToLongTimeString()}");
var result = await this.Service.SaveAsync<TestData>(new CommandRequest<TestData>("TestData", newData));
await this.GetData();
}
public void Dispose()
=> this.Service.StorageChanged -= this.DataChanged;
}
I have custom AuthenticationStateProvider in my Blazor WebAssembly app and it's not calling GetAuthenticationStateAsync method on page refresh so if user is logged in and then they manually refresh page ClaimsPrincipal is AuthenticationState is not populated so user can't be Authorized.
I'm using JWT which is saved in cookie for auth, and that cookie remains after page refresh, app just doesn't call GetAuthenticationStateAsync (which it should on page refresh if I learned blazor correctly).
Here's my custom AuthenticationStateProvider:
public class CustomAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
public CustomAuthenticationStateProvider(
IHttpClientFactory httpClientFactory)
{
_httpClient = httpClientFactory.CreateClient("APIClient");
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
//TODO get token from cookie
var savedToken = "";
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt")));
}
public void MarkUserAsAuthenticated(string email)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(new[] { new Claim(ClaimTypes.Name, email) }, "apiauth"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
public void MarkUserAsLoggedOut()
{
var anonymousUser = new ClaimsPrincipal(new ClaimsIdentity());
var authState = Task.FromResult(new AuthenticationState(anonymousUser));
NotifyAuthenticationStateChanged(authState);
}
}
EDIT: It was calling GetAuthenticationStateAsync method the entire time, it was just confusing because I was in debugger and it never hit my breakpoint inside of that method, which is problem I saw other people (still not sure why is that).
It depends on where you call GetAuthenticationStateAsync.
i.e. in BlazorHero project this call is present in the MainLayout.razor:
</NotAuthorized>
<Authorized>
#(LoadDataAsync())
<MudLayout RightToLeft="#_rightToLeft">
in MainLayout.razor.cs:
private async Task LoadDataAsync()
{
var state = await _stateProvider.GetAuthenticationStateAsync();
var user = state.User;
if (user == null) return;
so I think you need to check where it is called.
The MainLayout is rendered every time you load a page.
A better solution (cleaner) is to add this code in the AfterRender like:
protected override async Task OnAfterRenderAsync(bool firstRender)
{
if (firstRender)
{
await LoadDataAsync();
}
}
I configured Web app in ASP.NET Core 3.1 to use ASP.NET Core Identity with local accounts.
Everything works correctly.
I want to force user to insert password (or use external provider) again when he go to specific page e.g. account settings.
I cannot find example with this case.
Does somebody has experience with this?
Maybe there is some article which I missed.
So far the only idea is to log out user and open login page but it's not logic because he should be able to open other pages without restrictions.
I've not tried this but I just stumbled across this blog post which sounds like it would work for you, I've kinda shortened it down to an almost TL;DR post
Setup the required password verification page
The RequirePasswordVerificationModel class implements the Razor page which requires that a user has verified a password for the identity user within the last ten minutes.
public class RequirePasswordVerificationModel : PasswordVerificationBase
{
public RequirePasswordVerificationModel(UserManager<ApplicationUser> userManager) : base(userManager)
{
}
public async Task<IActionResult> OnGetAsync()
{
var passwordVerificationOk = await ValidatePasswordVerification();
if (!passwordVerificationOk)
{
return RedirectToPage("/PasswordVerification",
new { ReturnUrl = "/DoUserChecks/RequirePasswordVerification" });
}
return Page();
}
}
The PasswordVerificationBase Razor page implements the PageModel. The ValidatePasswordVerification method checks if the user is already authenticated
public class PasswordVerificationBase : PageModel
{
public static string PasswordCheckedClaimType = "passwordChecked";
private readonly UserManager<ApplicationUser> _userManager;
public PasswordVerificationBase(UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
}
public async Task<bool> ValidatePasswordVerification()
{
if (User.Identity.IsAuthenticated)
{
if (User.HasClaim(c => c.Type == PasswordCheckedClaimType))
{
var user = await _userManager.FindByEmailAsync(User.Identity.Name);
var lastLogin = DateTime.FromFileTimeUtc(
Convert.ToInt64(user.LastLogin));
var lastPasswordVerificationClaim
= User.FindFirst(PasswordCheckedClaimType);
var lastPasswordVerification = DateTime.FromFileTimeUtc(
Convert.ToInt64(lastPasswordVerificationClaim.Value));
if (lastLogin > lastPasswordVerification)
{
return false;
}
else if (DateTime.UtcNow.AddMinutes(-10.0) > lastPasswordVerification)
{
return false;
}
return true;
}
}
return false;
}
}
If the user needs to re-enter credentials, the PasswordVerificationModel Razor page is used for this. This class was built using the identity scaffolded login Razor page from ASP.NET Core Identity. The old password verifications claims are removed using the UserManager service. A new password verification claim is created, if the user successfully re-entered the password and the sign in is refreshed with the new ClaimIdentity instance.
public class PasswordVerificationModel : PageModel
{
private readonly UserManager<ApplicationUser> _userManager;
private readonly SignInManager<ApplicationUser> _signInManager;
private readonly ILogger<PasswordVerificationModel> _logger;
public PasswordVerificationModel(SignInManager<ApplicationUser> signInManager,
ILogger<PasswordVerificationModel> logger,
UserManager<ApplicationUser> userManager)
{
_userManager = userManager;
_signInManager = signInManager;
_logger = logger;
}
[BindProperty]
public CheckModel Input { get; set; }
public IList<AuthenticationScheme> ExternalLogins { get; set; }
public string ReturnUrl { get; set; }
[TempData]
public string ErrorMessage { get; set; }
public class CheckModel
{
[Required]
[DataType(DataType.Password)]
public string Password { get; set; }
}
public async Task<IActionResult> OnGetAsync(string returnUrl = null)
{
if (!string.IsNullOrEmpty(ErrorMessage))
{
ModelState.AddModelError(string.Empty, ErrorMessage);
}
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
var hasPassword = await _userManager.HasPasswordAsync(user);
if (!hasPassword)
{
return NotFound($"User has no password'{_userManager.GetUserId(User)}'.");
}
returnUrl ??= Url.Content("~/");
ReturnUrl = returnUrl;
return Page();
}
public async Task<IActionResult> OnPostAsync(string returnUrl = null)
{
returnUrl ??= Url.Content("~/");
var user = await _userManager.GetUserAsync(User);
if (user == null)
{
return NotFound($"Unable to load user with ID '{_userManager.GetUserId(User)}'.");
}
if (ModelState.IsValid)
{
// This doesn't count login failures towards account lockout
// To enable password failures to trigger account lockout, set lockoutOnFailure: true
var result = await _signInManager.PasswordSignInAsync(user.Email, Input.Password, false, lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User password re-entered");
await RemovePasswordCheck(user);
var claim = new Claim(PasswordVerificationBase.PasswordCheckedClaimType,
DateTime.UtcNow.ToFileTimeUtc().ToString());
await _userManager.AddClaimAsync(user, claim);
await _signInManager.RefreshSignInAsync(user);
return LocalRedirect(returnUrl);
}
if (result.IsLockedOut)
{
_logger.LogWarning("User account locked out.");
return RedirectToPage("./Lockout");
}
else
{
ModelState.AddModelError(string.Empty, "Invalid login attempt.");
return Page();
}
}
// If we got this far, something failed, redisplay form
return Page();
}
private async Task RemovePasswordCheck(ApplicationUser user)
{
if (User.HasClaim(c => c.Type == PasswordVerificationBase.PasswordCheckedClaimType))
{
var claims = User.FindAll(PasswordVerificationBase.PasswordCheckedClaimType);
foreach (Claim c in claims)
{
await _userManager.RemoveClaimAsync(user, c);
}
}
}
}
The PasswordVerificationModel Razor page html template displays the user input form with the password field.
#page
#model PasswordVerificationModel
#{
ViewData["Title"] = "Password Verification";
}
<h1>#ViewData["Title"]</h1>
<div class="row">
<div class="col-md-4">
<section>
<form id="account" method="post">
<h4>Verify account using your password</h4>
<hr />
<div asp-validation-summary="All" class="text-danger"></div>
<div class="form-group">
<label asp-for="Input.Password"></label>
<input asp-for="Input.Password" class="form-control" />
<span asp-validation-for="Input.Password"
class="text-danger"></span>
</div>
<div class="form-group">
<button type="submit" class="btn btn-primary">
Re-enter password
</button>
</div>
</form>
</section>
</div>
</div>
#section Scripts {
<partial name="_ValidationScriptsPartial" />
}
The Login Razor page needs to be updated to add a login file time value for DateTime.UtcNow when the login successfully occurred. This value is used in the base Razor page to verify the password check. The LastLogin property was added for this.
var result = await _signInManager.PasswordSignInAsync(
Input.Email, Input.Password,
Input.RememberMe,
lockoutOnFailure: false);
if (result.Succeeded)
{
_logger.LogInformation("User logged in.");
var user = await _userManager.FindByEmailAsync(Input.Email);
if (user == null)
{
return NotFound("help....");
}
user.LastLogin = DateTime.UtcNow.ToFileTimeUtc().ToString();
var lastLoginResult = await _userManager.UpdateAsync(user);
return LocalRedirect(returnUrl);
}
The LastLogin property was added to the ApplicationUser which implements the IdentityUser. This value is persisted to the Entity Framework Core database.
public class ApplicationUser : IdentityUser
{
public string LastLogin { get; set; }
}
When the application is started, the user can login and will need to verify a password to access the Razor page implemented to require this feature.
I am using the .Net Core Angular template with individual accounts:
dotnet new angular -au individual
And I am adding an external Microsoft login provider thus:
services.AddAuthentication()
.AddMicrosoftAccount(config => {
config.ClientId = "***REDACTED***";
config.ClientSecret = "***REDACTED***";
config.SaveTokens = true;
})
.AddIdentityServerJwt();
Then, I create a page with the [Authorized] attribute, and I try to retrieve the Microsoft access_token but it always comes up as null. This is how the page is built:
Test.cshtml
#page
#model TestModel
<partial name="_LoginPartial" />
<h1>hi</h1>
<div>
<p>Access Token</p>
<pre>
#Model.AccessToken
</pre>
<p>ID Token</p>
<pre>
#Model.IdToken
</pre>
</div>
Test.cshtml.cs
[Authorize]
public class TestModel : PageModel {
private readonly UserManager<ApplicationUser> _userManager;
public string AccessToken;
public string IdToken;
public TestModel(UserManager<ApplicationUser> userManager) {
_userManager = userManager;
}
public async Task OnGetAsync() {
if (!User.Identity.IsAuthenticated) {
return;
}
var user = await _userManager.GetUserAsync(User);
AccessToken = await _userManager.GetAuthenticationTokenAsync(user, "Microsoft", "access_token");
IdToken = await _userManager.GetAuthenticationTokenAsync(user, "Microsoft", "id_token");
}
}
As mentioned, I only ever get null values for AccessToken and IdToken - what am I doing wrong?
P.S. I also tried await HttpContext.GetTokenAsync("Microsoft", "access_token") - it also returns null.
Answering my own question after days of research, which culminated in me cloning the repos at https://github.com/dotnet/aspnetcore and at https://github.com/identityserver/identityserver4 and tracking the target through the code...
The solution is to add a call to SigninManager.UpdateExternalAuthenticationTokensAsync to the Account/ExternalLogin callback handler (method OnGetCallbackAsync), after verifying that the external login is successful, i.e. after the call to _signInManager.ExternalLoginSignInAsync thus:
// Sign in the user with this external login provider if the user already has a login.
var result = await _signInManager.ExternalLoginSignInAsync(
info.LoginProvider,
info.ProviderKey,
isPersistent: false,
bypassTwoFactor : true);
if (result.Succeeded) {
await _signInManager.UpdateExternalAuthenticationTokensAsync(info); // <-- This
_logger.LogInformation(
"{Name} logged in with {LoginProvider} provider.",
info.Principal.Identity.Name,
info.LoginProvider);
return LocalRedirect(returnUrl);
}
I am currently learning asp.net core and blazor, and have come across an issue with little documentation. I have a Server side Blazor app and am re doing authentication to use local storage and the ServerAuthenticationStateProvider. This code is based off this guide, here is my current implementation of the state provider:
MyAuthenticationStateProvider.cs
namespace BlazorApp
{
public class MyAuthenticationStateProvider : ServerAuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public MyAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var savedToken = await _localStorage.GetItemAsync<string>("authToken");
if (string.IsNullOrWhiteSpace(savedToken))
{
return new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
}
var user = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(savedToken), "jwt"));
return new AuthenticationState(user);
}
public void MarkUserAsAuthenticated(string token)
{
var authenticatedUser = new ClaimsPrincipal(new ClaimsIdentity(ParseClaimsFromJwt(token), "jwt"));
var authState = Task.FromResult(new AuthenticationState(authenticatedUser));
NotifyAuthenticationStateChanged(authState);
}
LoginControl.cs
#page "/loginControl"
#inject IAuthService AuthService
#inject NavigationManager NavigationManager
<AuthorizeView>
<Authorized>
<b>Hello, #context.User.Identity.Name!</b>
<a class="ml-md-auto btn btn-primary"
href="logout?returnUrl=/"
target="_top">Logout</a>
</Authorized>
<Authorizing>
<b>Authentication in progress</b>
</Authorizing>
<NotAuthorized>
<input type="text"
placeholder="Email"
#bind="#email" />
<input type="password"
placeholder="Password"
#bind="#password" />
<button class="ml-md-auto btn btn-primary"
#onclick="#createSession">
Login
</button>
</NotAuthorized>
</AuthorizeView>
#code {
string email = "";
string password = "";
async void createSession()
{
var loginRequest = new LoginRequest
{
Email = email,
Password = password
};
await AuthService.Login(loginRequest);
}
}
I would expect that after the NotifyAuthenticationStateChanged(AuthState) is called, my login UI would refresh and the <Authorized> content to display. However my UI still shows the <NotAuthorized> content. Did I miss something to do with dispatching to the main thread?? I am Very new to all this but my Mentor mentioned something to do with this possibly having to do with being a background thread not telling the UI to re-render.
Really simple. All you need to do is:
StateHasChanged();
I just built a login control yesterday, so here are some bonus things you might want to know:
My login control has this:
<Login OnLogin="LoginComplete"></Login>
/// <summary>
/// This method returns the LoginResponse object
/// </summary>
/// <param name="loginResponse"></param>
private void LoginComplete(LoginResponse loginResponse)
{
// if the login was successful
if (loginResponse.Success)
{
// Set the player
player = loginResponse.Player;
// refresh the UI
this.StateHasChanged();
}
}
And in your control to invoke the LoginResponse delegate
// Get the loginResponse
LoginResponse loginResponse = await PlayerService.Login(emailAddress, password);
// if the loginResponse exists
if (NullHelper.Exists(loginResponse))
{
// set the player
player = loginResponse.Player;
// Perform the Login
await OnLogin.InvokeAsync(loginResponse);
}
I solved the problem by redirecting the user with the navigation manager and forcing the redirecting (by doing this the app is forced to change the authState).
I only did that because StateChanged() wans't working.
NaviMngr.NavigateTo("/", true);
I went about this wrong, server side blazor already implements AuthenticationStateProvider, so all I need to do is to implement something that sets the user.identity.isauthenticated, I am using cookies and a jet token to do this.