Reputation: 43
I'm using a custom JWT AuthenticationStateProvider from this sample both for Blazor Server and Blazor WebAssembly.
public class JwtAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly ISessionStorageService sessionStorageService;
private ClaimsPrincipal anonymous = new ClaimsPrincipal(new ClaimsIdentity());
public JwtAuthenticationStateProvider(ISessionStorageService sessionStorageService)
{
this.sessionStorageService = sessionStorageService;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
try
{
var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession");
if (userSession == null) return await Task.FromResult(new AuthenticationState(anonymous));
var claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name, userSession.UserName),
new Claim(ClaimTypes.Role, userSession.Role)
}, "JwtAuth"));
return await Task.FromResult(new AuthenticationState(claimsPrincipal));
}
catch (Exception ex)
{
//You can log exception
return await Task.FromResult(new AuthenticationState(anonymous));
}
}
public async Task UpdateAuthenticationState(UserSession? userSession)
{
ClaimsPrincipal claimsPrincipal;
if (userSession != null)
{
claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(new List<Claim>
{
new Claim(ClaimTypes.Name, userSession.UserName),
new Claim(ClaimTypes.Role, userSession.Role)
}));
userSession.ExpiryTimeStamp = DateTime.Now.AddSeconds(userSession.ExpiresIn);
await sessionStorageService.SaveItemEncryptedAsync("UserSession", userSession);
}
else
{
claimsPrincipal = anonymous;
await sessionStorageService.RemoveItemAsync("UserSession");
}
NotifyAuthenticationStateChanged(Task.FromResult(new AuthenticationState(claimsPrincipal)));
}
public async Task<string> GetToken()
{
var result = string.Empty;
try
{
var userSession = await sessionStorageService.ReadEncryptedItemAsync<UserSession>("UserSession");
if (userSession != null && DateTime.Now < userSession.ExpiryTimeStamp) result = userSession.Token;
}
catch (Exception ex)
{
//You can log exception
}
return result;
}
}
In Blazor Server I'm able to get user identity name and roles via AuthenticationState, but in Blazor WebAssembly when I call API methods of server-side Controller via HttpClient, I can't get the caller username.
[Route("api/[controller]")]
[ApiController]
public class DevController : ControllerBase
{
[HttpGet, Route("GetUserNameByUser")]
public string GetUserNameByUser()
{
return User?.Identity?.Name ?? String.Empty; //<<-- User?.Identity?.Name == null
}
[Microsoft.AspNetCore.Components.CascadingParameter] private Task<Microsoft.AspNetCore.Components.Authorization.AuthenticationState> AuthenticationState { get; set; }
[HttpGet, Route("GetUserNameByAuthenticationState")]
public async Task<ActionResult<string>> GetUserNameByAuthenticationState()
{
var currentUserName = (await AuthenticationState).User?.Identity?.Name; //<<-- AuthenticationState == null
return Ok(currentUserName);
}
}
Here is the AddAuthentication call in [myProject].Server.Program.cs:
builder.Services.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(o =>
{
o.RequireHttpsMetadata = true;
o.SaveToken = true;
o.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes(JwtAuthenticationManager.JwtSecurityKey)),
ValidateIssuer = false,
ValidateAudience = false
};
});
Is there a "standard" way to add current user authentication information in HttpClient call header or similar?
P.S. There is a similar unsolved old question here, but I hope that in two years something has changed or clarified...:-)
Upvotes: 2
Views: 537
Reputation: 11382
In your custom AuthenticationStateProvider
inject the HttpClient
and use this code to attach the bearer token in all requests:
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
Example implementation:
public class TokenAuthenticationStateProvider : AuthenticationStateProvider
{
private readonly HttpClient _httpClient;
private readonly ILocalStorageService _localStorage;
public TokenAuthenticationStateProvider(HttpClient httpClient, ILocalStorageService localStorage)
{
_httpClient = httpClient;
_localStorage = localStorage;
}
public override async Task<AuthenticationState> GetAuthenticationStateAsync()
{
var token = await GetTokenAsync();
var anonymousState = new AuthenticationState(new ClaimsPrincipal(new ClaimsIdentity()));
if (string.IsNullOrWhiteSpace(token))
{
return anonymousState;
}
var claims = ParseClaimsFromJwt(token);
var expiry = claims.FirstOrDefault(c => c.Type == "exp");
if (expiry == null)
{
return anonymousState;
}
var datetime = DateTimeOffset.FromUnixTimeSeconds(long.Parse(expiry.Value));
if (datetime.UtcDateTime <= DateTime.UtcNow)
{
return anonymousState;
}
_httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", token);
var identity = new ClaimsIdentity(claims, "jwt");
return new AuthenticationState(new ClaimsPrincipal(identity));
}
public async Task<string> GetTokenAsync()
=> await _localStorage.GetItemAsync<string>("authToken");
public async Task SetTokenAsync(string token)
{
if (token == null)
{
await _localStorage.RemoveItemAsync("authToken");
}
else
{
await _localStorage.SetItemAsync("authToken", token);
}
NotifyAuthenticationStateChanged(GetAuthenticationStateAsync());
}
private static IEnumerable<Claim> ParseClaimsFromJwt(string jwt)
{
var payload = jwt.Split('.')[1];
var jsonBytes = ParseBase64WithoutPadding(payload);
var keyValuePairs = JsonSerializer.Deserialize<Dictionary<string, object>>(jsonBytes);
return keyValuePairs.Select(kvp => new Claim(kvp.Key, kvp.Value.ToString()));
}
private static byte[] ParseBase64WithoutPadding(string base64)
{
switch (base64.Length % 4)
{
case 2: base64 += "=="; break;
case 3: base64 += "="; break;
}
return Convert.FromBase64String(base64);
}
}
Modify yours accordingly.
Upvotes: 2