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...