Customizing the AuthenticationStateProvider in Blazor Server App with Jwt Token Authentication

假装没事ソ 提交于 2020-07-02 03:05:38

问题


I've noticed that many developers subclass the AuthenticationStateProvider both in Blazor Server App and Blazor WebAssembly App wrongly, and more imprtantly for the wrong reasons.

How to do it correctly and when ?


回答1:


First off, you do not subclass the AuthenticationStateProvider for the sole purpose of adding claims to the ClaimPrincipal object. Generally speaking, claims are added after a user has been authenticated, and if you need to inspect those claims and tranform them, it should be done somewhere else, not in the AuthenticationStateProvider object. Incidentally, in Asp.Net Core there are two ways how you can do that, but this merits a question of its own.

I guess that this code sample led many to believe that this is the place to add claims to the ClaimsPrincipal object.

In the current context, implementing Jwt Token Authentication, claims should be added to the Jwt Token when it is created on the server, and extracted on the client when required, as for instance, you need the name of the current user. I've noticed that developers save the name of the user in the local storage, and retrieved it when needed. This is wrong. You should extract the name of the user from the Jwt Token.

The following code sample describes how to create a custom AuthenticationStateProvider object whose objective is to retrieve from the local storage a Jwt Token string that has newly added, parse its content, and create a ClaimsPrincipal object that is served to interested parties (subscribers to the AuthenticationStateProvider.AuthenticationStateChanged event) , such as the CascadingAuthenticationState object.

The following code sample demonstrates how you can implement a custom authenticationstateprovider properly, and for good reason.

public class TokenServerAuthenticationStateProvider : ServerAuthenticationStateProvider
    {
        private readonly IJSRuntime _jsRuntime;
       
        public TokenServerAuthenticationStateProvider(IJSRuntime jsRuntime)
        {
            _jsRuntime = jsRuntime;
           
           
        }

       public async Task<string> GetTokenAsync()
            => await _jsRuntime.InvokeAsync<string>("localStorage.getItem", "authToken");

        public async Task SetTokenAsync(string token)
        {
            if (token == null)
            {
                await _jsRuntime.InvokeAsync<object>("localStorage.removeItem", "authToken");
            }
            else
            {
                await _jsRuntime.InvokeAsync<object>("localStorage.setItem", "authToken", token);
            }
            
            NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
        }

        public override async Task<AuthenticationState> GetAuthenticationStateAsync()
        {
            var token = await GetTokenAsync();
            var identity = string.IsNullOrEmpty(token)
                ? new ClaimsIdentity()
                : new ClaimsIdentity(ServiceExtensions.ParseClaimsFromJwt(token), "jwt");
            return new AuthenticationState(new ClaimsPrincipal(identity));
        }
    }

And here's a code sample residing in the submit button of a Login page that calls a Web Api endpoint where the user credentials are validated, after which a Jwt Token is created and passed back to the calling code:

async Task SubmitCredentials()
{

    bool lastLoginFailed;

    var httpClient = clientFactory.CreateClient();
    httpClient.BaseAddress = new Uri("https://localhost:44371/");

    var requestJson = JsonSerializer.Serialize(credentials, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });


    var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Post, "api/user/login")
    {
        Content = new StringContent(requestJson, Encoding.UTF8, "application/json")
    });

    var stringContent = await response.Content.ReadAsStringAsync();

    var result = JsonSerializer.Deserialize<LoginResult>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });

    lastLoginFailed = result.Token == null;
    if (!lastLoginFailed)
    {
        // Success! Store token in underlying auth state service
        await TokenProvider.SetTokenAsync(result.Token);
        NavigationManager.NavigateTo(ReturnUrl);
        
    }
}

Point to note: TokenProvider is an instance of TokenServerAuthenticationStateProvider. 
Its name reflects its functionality: handling the recieved Jwt Token, and providing 
the Access Token when requested.

This line of code: TokenProvider.SetTokenAsync(result.Token); passes the Jwt Token 
to TokenServerAuthenticationStateProvider.SetTokenAsync in which the token is sored 
in the local storage, and then raises AuthenticationStateProvider.AuthenticationStateChanged
event by calling NotifyAuthenticationStateChanged, passing an AuthenticationState object
built from the data contained in the stored Jwt Token.


Note that the GetAuthenticationStateAsync method creates a new ClaimsIdentity object from 
the parsed Jwt Token. All the claims added to the newly created ClaimsIdentity object 
are retrieved from the Jwt Token. I cannot think of a use case where you have to create
a new claim object and add it to the ClaimsPrincipal object.

The following code is executed when an authenticated user is attempting to access
the FecthData page

@code 
{
   private WeatherForecast[] forecasts;


protected override async Task OnInitializedAsync()
{
    var token = await TokenProvider.GetTokenAsync();
   
    var httpClient = clientFactory.CreateClient();
    httpClient.BaseAddress = new Uri("https://localhost:44371/");
    httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);

    var response = await httpClient.SendAsync(new HttpRequestMessage(HttpMethod.Get, $"api/WeatherForecast?startDate={DateTime.Now}"));
    
    var stringContent = await response.Content.ReadAsStringAsync();

    forecasts = JsonSerializer.Deserialize<WeatherForecast[]>(stringContent, new JsonSerializerOptions { PropertyNamingPolicy = JsonNamingPolicy.CamelCase });
   
}

}

Note that the first line of code: var token = await TokenProvider.GetTokenAsync(); retrieves the Jwt Token stored in the local storage, and add it to the Authorization header of the request.

Hope this helps...



来源:https://stackoverflow.com/questions/62529029/customizing-the-authenticationstateprovider-in-blazor-server-app-with-jwt-token

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!