Blazor Server cant access controller with [Authorize] attribute

Viewed 2457

I'm pretty new at websites so this might be a bit naive. Starting with the basic Blazor Server (blazorserver) template with individual user accounts for authentication, I've added a DefaultController to my Blazor app by going to Project > Add Controller, and selecting an API controller with read/write actions.

I then added the NuGet package Microsoft.AspNetCore.Blazor.HttpClient and added to Startup.cs:

services.AddScoped<HttpClient>();

I also injected HttpClient and NavigationManager into the FetchData.razor page. I then added to my FetchData.razor page a button and function:

<button @onclick="@TryIt">Try It</button>
private async Task TryIt()
{
    var x = await httpClient.GetJsonAsync<List<string>>($"{navigationManager.BaseUri}api/Default");
}

I run the site and click the Try It button and it works fine: I see the two items from the list. Great!

I then add the [Authorize] attribute to the HttpGet() action in the controller:

// GET: api/Default
[HttpGet]
[Authorize]
public IEnumerable<string> Get()
{
    return new string[] { "value1", "value2" };
}

When I run the site again and click Try It, no joy (Unhandled Exception). Okay, maybe I need to login? Run the site, login (after getting the database created and a user registered) and click Try It. Again, I get the scary exception in the console:

Microsoft.AspNetCore.Components.Server.Circuits.RemoteRenderer: Warning: Unhandled exception rendering component: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.

System.Text.Json.JsonException: '<' is an invalid start of a value. Path: $ | LineNumber: 0 | BytePositionInLine: 0.
 ---> System.Text.Json.JsonReaderException: '<' is an invalid start of a value. LineNumber: 0 | BytePositionInLine: 0.
   at System.Text.Json.ThrowHelper.ThrowJsonReaderException(Utf8JsonReader& json, ExceptionResource resource, Byte nextByte, ReadOnlySpan`1 bytes)
   at System.Text.Json.Utf8JsonReader.ConsumeValue(Byte marker)
   at System.Text.Json.Utf8JsonReader.ReadFirstToken(Byte first)
   at System.Text.Json.Utf8JsonReader.ReadSingleSegment()
   at System.Text.Json.Utf8JsonReader.Read()
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   --- End of inner exception stack trace ---
   at System.Text.Json.ThrowHelper.ReThrowWithPath(ReadStack& readStack, JsonReaderException ex)
   at System.Text.Json.JsonSerializer.ReadCore(JsonSerializerOptions options, Utf8JsonReader& reader, ReadStack& readStack)
   at System.Text.Json.JsonSerializer.ReadCore(Type returnType, JsonSerializerOptions options, Utf8JsonReader& reader)
   at System.Text.Json.JsonSerializer.Deserialize(String json, Type returnType, JsonSerializerOptions options)
   at System.Text.Json.JsonSerializer.Deserialize[TValue](String json, JsonSerializerOptions options)
   at Microsoft.AspNetCore.Components.HttpClientJsonExtensions.GetJsonAsync[T](HttpClient httpClient, String requestUri)
   at BlazorApp.Pages.FetchData.TryIt() in E:\DLS\Sandbox7\BlazorAppSolution\BlazorApp\Pages\FetchData.razor:line 57
   at Microsoft.AspNetCore.Components.ComponentBase.CallStateHasChangedOnAsyncCompletion(Task task)
   at Microsoft.AspNetCore.Components.RenderTree.Renderer.GetErrorHandledTask(Task taskToHandle)
Microsoft.AspNetCore.Components.Server.Circuits.CircuitHost: Error: Unhandled exception in circuit 'H-xRrymKJQEov3vkCpOcExCeuUA2S2b1e15y6QSjxvw'.

I've been reading that to do this I might need to add a token to the HTTP request, but I have no idea how to get the token. Am I on the right track? What do I need to add? What am I missing?

As I'm really new to this, I could use as much guidance as you can give in as much detail as you can provide.

1 Answers

First off, the exception is raised because the JsonSerializer is not capable of deserializing the content passed to it. it expects to get JSON that can be deserialize to a List, but gets a message from the server reporting of an error that has occurred. This message is provided in the form of HTML.

You shouldn't use Microsoft.AspNetCore.Blazor.HttpClient in Blazor Server App. It is intended for use in Blazor WebAssembly Apps. Use the IHttpClientFactory to employ the HttpClient service in your server-side Blazor.

Using the template with individual user accounts for authentication means that your users are authenticated by Asp.Net Core Identity system (IdentityUI), including a database, models, etc. All the goodies that come with IdentityUI. But this is only related to authentication.

Now, if you want to access Web Api end points, and want to secure these end points by decorating them with the Authorize attribute, you need to use an authorization system, such as OpenID Connect, etc.

For a start, I'd recommend you to use JWT Token authentication to issue JWT Tokens to authenticated users. You may dedicate a controller for the task of authenticating the user, creating a JWT token which contains various claims about the user. This JWT token is created only once, after the user has been authenticated, and it is passed to the calling code (your Blazor Server App), which should store it in the local storage (JavaScript local storage). Now, whenever you want to retrieve data for a given user from a secured Web Api end point, you should read the Jwt token from the local storage, and add it to the Authorization header of your HTTP request. The following is a demonstration how to make an HTTP request passing the Jwt Token in the Authorization header. Note: This is done using the package you've added, which is not recommended to be used with server-side Blazor. You may use it temporarily, but at the end of the day, you should shift to IHttpClientFactory.

@code {
User[] users;

protected override async Task OnInitializedAsync()
{
    var token = await TokenProvider.GetTokenAsync();
    users = await Http.GetJsonAsync<User[]>(
        "api/users",
        new AuthenticationHeaderValue("Bearer", token));
}

}

Note: This subject is very comprehensive, and I'd suggest you use the docs to get the basic of it. There are also answers by me and others regarding authentication and authorization from which you can learn a great deal. It is also essential to learn to use the components and objects relating to authorization in Blazor.

Hope this helps...

Related