The following post is an attempt to describe one potential solution of a simple application with an ASP.NET Web API backend and a Blazor WebAssembly frontpage with some form of authentication:
ASP.NET WebAPI & Blazor WebAssembly
This post is a compilation of topics and troubleshooting notes that might be useful for implementing a simple ASP.NET WebAPI & Blazor WebAssembly application.
Troubleshooting
JWT is not well-formed
Problem
We have a Blazor front page. After successful authentication we set the JWT token with the following code:
await jsr.InvokeVoidAsync("localStorage.setItem", "jwtToken", $"{result.email};{result.jwtBearer};{DateTime.Now}").ConfigureAwait(false);
And to check whether we have already successfully authenticated we use the following code:
var tokenStr = await jsr.InvokeAsync<string>("localStorage.getItem", "jwtToken");
if (tokenStr == null)
{
return;
}
var jsonToken = new JwtSecurityTokenHandler().ReadToken(tokenStr);
var token = jsonToken as JwtSecurityToken;
var claim = token.Claims.Where(c => c.Type == "email").FirstOrDefault();
During the loading of the affected Blazor login page, we get the following error which occurs during this call of the ReadToken() command:
Unhandled exception rendering component: IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
The token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE): 'EncodedProtectedHeader.EncodedEncryptedKey.EncodedInitializationVector.EncodedCiphertext.EncodedAuthenticationTag'.
System.ArgumentException: IDX12709: CanReadToken() returned false. JWT is not well formed: '[PII of type 'System.String' is hidden. For more details, see https://aka.ms/IdentityModel/PII.]'.
The token needs to be in JWS or JWE Compact Serialization Format. (JWS): 'EncodedHeader.EndcodedPayload.EncodedSignature'. (JWE):
Resolution
This error indicates that the token we are trying to read from the local storage is not in a valid format. The JwtSecurityTokenHandler.ReadToken() method expects a string in the JWS Format, meaning that the string should contain the encoded header, payload, and signature. But that’s not the format we are storing with localStorage.setItem
() method.
Here is a potential corrected version of the code:
We prepare the JWT Token like this on the server side:
[AllowAnonymous]
[HttpPut]
// This is the API method which is called from the frontend to login a user
public async Task<LoginResult> Put(Credential credential)
{
var user = _authenticate(credential);
if (user != null)
{
var token = _generateToken(user);
return new LoginResult { message = "Login successful.", jwtBearer = _generateToken(user), email = user.Email, success = true };
}
return new LoginResult { message = "User/password not found.", success = false };
}
// Generates and returns a JWT token
private string _generateToken(User user)
{
var securityKey = new SymmetricSecurityKey(Encoding.UTF8.GetBytes(_configiguration["Jwt:Key"]));
var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.HmacSha256);
var claims = new[]
{
new Claim(ClaimTypes.NameIdentifier,user.Login),
new Claim(ClaimTypes.Role,user.Role)
};
var token = new JwtSecurityToken(_configiguration["Jwt:Issuer"],
_configiguration["Jwt:Audience"],
claims,
expires: DateTime.Now.AddMinutes(15),
signingCredentials: credentials);
return new JwtSecurityTokenHandler().WriteToken(token);
}
And then set the received token as follows on the Blazor client side:
LoginResult result = await msg.Content.ReadFromJsonAsync<LoginResult>();
if (result.success)
{
// Serialize the token to a JWT string in the JWS Compact Serialization Format
//var jwtString = new JwtSecurityTokenHandler().WriteToken(result.jwtBearer);
// Save the JWT string to the local storage
await jsr.InvokeVoidAsync("localStorage.setItem", "jwtToken", result.jwtBearer).ConfigureAwait(false);
}