I am getting an unexpected Unauthorized response from an Api when using JWT in Blazor WebAssembly. Note, I am not trying to secure anything on the WebAssembly client; just the API endpoint. I have deliberately left out expiry validation.
Server
appsettings.json
{
"JwtSecurity": {
"Key": "RANDOM_KEY_MUST_NOT_BE_SHARED",
"Issuer": "https://localhost",
"Audience": "https://localhost",
"ExpiryDays": 1
}
}
Program.cs
// Service registration
builder.Services
.AddAuthentication(auth =>
{
auth.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
auth.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuer = true,
ValidIssuer = builder.Configuration["JwtSecurity:Issuer"],
ValidateAudience = true,
ValidAudience = builder.Configuration["JwtSecurity:Audience"],
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(builder.Configuration["JwtSecurity:Key"])),
RequireExpirationTime = false,
ValidateLifetime = false
};
});
// Configure the HTTP request pipeline.
// SignalR Compression
app.UseResponseCompression();
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
app.UseHttpsRedirection();
// Logs the received token
app.UseJwtTokenHandler();
//explicitly only use blazor when the path doesn't start with api
app.MapWhen(ctx => !ctx.Request.Path.StartsWithSegments("/api"), blazor =>
{
blazor.UseBlazorFrameworkFiles();
blazor.UseStaticFiles();
blazor.UseRouting();
blazor.UseEndpoints(endpoints =>
{
endpoints.MapHub<Cosmos.App.Server.Hubs.TillSiteHub>("/tradingsessionhub");
endpoints.MapFallbackToFile("index.html");
});
});
//explicitly map api endpoints only when path starts with api
app.MapWhen(ctx => ctx.Request.Path.StartsWithSegments("/api"), api =>
{
api.UseStaticFiles();
api.UseRequestLogging();
api.UseRouting();
api.UseAuthentication();
api.UseAuthorization();
// HAVE ALSO TRIED
// api.UseAuthentication();
// api.UseRouting();
// api.UseAuthorization();
api.UseErrorHandling();
api.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
});
app.Run();
- This
Given all of the above, it seems that:
- I am generating JWT correcly.
- JWT is being stored and retrieved correctly from Local Storage
- JWT is being received in the Authorization header in the request
- The controller is secured at Controller level using Authorize attribute (no roles mentioned)
But still I get Unauthorized.
Any advice?
Attempts to Isolate
Disable validation parameters
Tried:
options.TokenValidationParameters = new TokenValidationParameters { ValidateIssuer = false, ValidateAudience = false, ValidateIssuerSigningKey = false, RequireExpirationTime = false, ValidateLifetime = false };
And added simple test controller:
using Microsoft.AspNetCore.Authorization; namespace Cosmos.App.Server.Controllers; [Route("api/[controller]")] [ApiController] [Authorize] public class TestController : ControllerBase { [HttpGet] [Route("test")] public async Task<IActionResult> TestAsync() { await Task.Delay(1); return Ok("Hello"); } }
But same issue.
CodePudding user response:
Found it!
The issue was that I was caching the returned string from login as a Token so I could quickly access claims in the client (whilst saving the string received from login in LocalStorage).
Then, when putting the token on the HttpClient, if I had a cached token, I was writing it out to string to populate the Authorization on the Http Request.
The problem is that the string received from the initial login, e.g.
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
was truncated when reading back into a token for caching. It appears to have truncated what I presume is the signing key. The following was missing:
5l9LRYIx3wXruW7BMa1DDbEoltVgP6Fbfkc2O03XAAY
This meant I had the full string stored in local storage but a cached token without the signing key.
When then used the cached token to write to string for the authorization header I got:
eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJzdWIiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJqdGkiOiI2MWYwYTRiMi0wNjQwLTRiMjgtYmM2Mi0zMDZlYTVmYmJiM2UiLCJodHRwOi8vc2NoZW1hcy54bWxzb2FwLm9yZy93cy8yMDA1LzA1L2lkZW50aXR5L2NsYWltcy9uYW1laWRlbnRpZmllciI6Ijk0ZTZhZmE3LTQ0ZjAtNGU1NS04ODEzLTExNGY0ZjU5YTY3MiIsImh0dHA6Ly9zY2hlbWFzLnhtbHNvYXAub3JnL3dzLzIwMDUvMDUvaWRlbnRpdHkvY2xhaW1zL25hbWUiOiI5NGU2YWZhNy00NGYwLTRlNTUtODgxMy0xMTRmNGY1OWE2NzIiLCJleHAiOjE2NjM5NjEwMjMsImlzcyI6Imh0dHBzOi8vbG9jYWxob3N0IiwiYXVkIjoiaHR0cHM6Ly9sb2NhbGhvc3QifQ.
without the suffix of the signing key.
This meant that the validation of that string failed authorization on the server, even though jwt.io would happily read it.
Using the full string that I'd stored in Local Storage instead of a 'written' string from the cached token solved the problem.