feat: resolve role-based authorization by extracting Roles claim from JWT and storing in state provider

This commit is contained in:
2026-05-10 20:24:59 +02:00
parent 5f39f2e063
commit 0c3fccc91e
6 changed files with 55 additions and 12 deletions
@@ -23,7 +23,8 @@ public record UserProfile(
Guid TenantId,
SubscriptionPlanDto Plan,
int AverageQuizScore,
LastReadBookDto? LastReadBook)
LastReadBookDto? LastReadBook,
string[] Roles)
{
// Helper properties for UI compatibility
public string CurrentPlan => Plan?.Name ?? PlanConstants.DefaultPlanName;
@@ -104,11 +105,15 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString());
var rolesStr = string.Join(",", profile.Roles ?? Array.Empty<string>());
await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr);
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr);
}
else
{
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown");
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(email, "unknown", "");
}
return Result.Ok();
@@ -132,6 +137,7 @@ public class IdentityService : IIdentityService
await _storageService.SaveSecureString(RefreshTokenKey, "");
await _storageService.SaveSecureString(StorageKeys.UserEmail, "");
await _storageService.SaveSecureString(StorageKeys.UserTenant, "");
await _storageService.SaveSecureString(StorageKeys.UserRoles, "");
}
if (OnStateInvalidated != null) await OnStateInvalidated.Invoke();
@@ -197,7 +203,11 @@ public class IdentityService : IIdentityService
_cachedProfile = profile;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString());
var rolesStr = string.Join(",", profile.Roles ?? Array.Empty<string>());
await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr);
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr);
}
return profile;
}
@@ -246,7 +256,11 @@ public class IdentityService : IIdentityService
var profile = profileResult.Value;
await _storageService.SaveSecureString(StorageKeys.UserEmail, profile.Email);
await _storageService.SaveSecureString(StorageKeys.UserTenant, profile.TenantId.ToString());
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString());
var rolesStr = string.Join(",", profile.Roles ?? Array.Empty<string>());
await _storageService.SaveSecureString(StorageKeys.UserRoles, rolesStr);
(_authStateProvider as NexusAuthenticationStateProvider)?.NotifyUserAuthentication(profile.Email, profile.TenantId.ToString(), rolesStr);
}
return Result.Ok();
@@ -38,10 +38,15 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
{
var emailResult = await _storageService.GetSecureString(StorageKeys.UserEmail);
var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant);
var rolesResult = await _storageService.GetSecureString(StorageKeys.UserRoles);
if (emailResult.IsSuccess && !string.IsNullOrEmpty(emailResult.Value))
{
_cachedState = CreateState(emailResult.Value, tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown", "OpaqueBearer");
_cachedState = CreateState(
emailResult.Value,
tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown",
"OpaqueBearer",
rolesResult.IsSuccess ? rolesResult.Value! : "");
return _cachedState;
}
}
@@ -51,7 +56,12 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
if (storedEmailResult.IsSuccess && !string.IsNullOrEmpty(storedEmailResult.Value))
{
var tenantIdResult = await _storageService.GetSecureString(StorageKeys.UserTenant);
_cachedState = CreateState(storedEmailResult.Value, tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown", "CookieAuth");
var rolesResult = await _storageService.GetSecureString(StorageKeys.UserRoles);
_cachedState = CreateState(
storedEmailResult.Value,
tenantIdResult.IsSuccess ? tenantIdResult.Value! : "unknown",
"CookieAuth",
rolesResult.IsSuccess ? rolesResult.Value! : "");
return _cachedState;
}
@@ -67,7 +77,7 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
}
}
private AuthenticationState CreateState(string email, string tenantId, string authType)
private AuthenticationState CreateState(string email, string tenantId, string authType, string rolesStr = "")
{
var claims = new List<Claim>
{
@@ -75,13 +85,23 @@ public class NexusAuthenticationStateProvider : AuthenticationStateProvider
new Claim(ClaimTypes.Email, email),
new Claim("TenantId", tenantId)
};
if (!string.IsNullOrEmpty(rolesStr))
{
var roles = rolesStr.Split(new[] { ',' }, StringSplitOptions.RemoveEmptyEntries);
foreach (var role in roles)
{
claims.Add(new Claim(ClaimTypes.Role, role.Trim()));
}
}
var identity = new ClaimsIdentity(claims, authType);
return new AuthenticationState(new ClaimsPrincipal(identity));
}
public void NotifyUserAuthentication(string email, string tenantId)
public void NotifyUserAuthentication(string email, string tenantId, string rolesStr = "")
{
_cachedState = CreateState(email, tenantId, "OpaqueBearer");
_cachedState = CreateState(email, tenantId, "OpaqueBearer", rolesStr);
NotifyAuthenticationStateChanged(Task.FromResult(_cachedState));
}