I created a Hosted ASP.NET core Blazor WASM app using the Individual Authentication VS 2022 template which also includes Duende Identity Server configuration, apart from having to switch from Blazor UI to .cshtml for authentication views, it has done what is expected of it. A big problem I'm having is that I can't seem to access the currently logged in user from within my controllers neither using the User property from ControllerBase, nor via the IHttpContextAccessor, the Claims Principle instances all seem to be null when inspected via the debugging mode, meanwhile on the client-side WASM I can access my subject Id, email or whatever I specify in the ProfileService just fine (Which is not really useful unless I would want to fetch the user from the server via the subject ID sent from the client using a parameter or something... which would be disastrous I know..)
Here's my Client/Program.cs:
using System.Globalization;
using CurrieTechnologies.Razor.SweetAlert2;
using Microsoft.AspNetCore.Components.Web;
using Microsoft.AspNetCore.Components.WebAssembly.Authentication;
using Microsoft.AspNetCore.Components.WebAssembly.Hosting;
using Microsoft.JSInterop;
using Proj.Client;
using Proj.Client.Auth;
using Proj.Client.Helpers;
using Proj.Client.Repository;
var builder = WebAssemblyHostBuilder.CreateDefault(args);
var services = builder.Services;
builder.RootComponents.Add<App>("#app");
builder.RootComponents.Add<HeadOutlet>("head::after ");
services.AddHttpClient<IHttpService>("Proj.ServerAPI",
client => client.BaseAddress = new Uri(builder.HostEnvironment.BaseAddress))
.AddHttpMessageHandler<BaseAddressAuthorizationMessageHandler>();
// Supply HttpClient instances that include access tokens when making requests to the server project
services.AddScoped(sp => sp.GetRequiredService<IHttpClientFactory>().CreateClient("Proj.ServerAPI"));
// SERVICES
services.AddOptions();
services.AddLocalization();
// builder.Services.AddScoped<DialogService>();
// builder.Services.AddScoped<NotificationService>();
// builder.Services.AddScoped<TooltipService>();
// builder.Services.AddScoped<ContextMenuService>();
services.AddApiAuthorization()
.AddAccountClaimsPrincipalFactory<CustomUserFactory>();
var host = builder.Build();
var js = host.Services.GetRequiredService<IJSRuntime>();
var culture = await js.InvokeAsync<string>("getFromLocalStorage", "culture");
CultureInfo selectedCulture;
selectedCulture = culture == null ? new CultureInfo("en") : new CultureInfo(culture);
CultureInfo.DefaultThreadCurrentCulture = selectedCulture;
CultureInfo.DefaultThreadCurrentUICulture = selectedCulture;
await host.RunAsync();
And the Server/Program.cs:
using System.IdentityModel.Tokens.Jwt;
using Duende.IdentityServer.Services;
using Microsoft.AspNetCore.ApiAuthorization.IdentityServer;
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.AspNetCore.Identity;
using Microsoft.AspNetCore.Identity.UI.Services;
using Microsoft.EntityFrameworkCore;
using Proj.Server.Data;
using Proj.Server.DbInitializer;
using Proj.Server.Models;
using Proj.Server.Services;
using Proj.Server.Utils;
var builder = WebApplication.CreateBuilder(args);
var services = builder.Services;
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("MySQLConnectionLocal");
var serverVersion = new MySqlServerVersion(ServerVersion.AutoDetect(connectionString));
services.AddDbContext<ApplicationDbContext>(
dbContextOptions => dbContextOptions
.UseMySql(connectionString, serverVersion)
// The following three options help with debugging, but should
// be changed or removed for production.
.LogTo(Console.WriteLine, LogLevel.Information)
.EnableSensitiveDataLogging()
.EnableDetailedErrors()
);
services.AddAutoMapper(typeof(MappingConfig));
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options =>
{
options.SignIn.RequireConfirmedAccount = true;
options.Stores.MaxLengthForKeys = 80;
options.User.RequireUniqueEmail = true;
options.Tokens.ProviderMap.Add("CustomEmailConfirmation",
new TokenProviderDescriptor(
typeof(CustomEmailConfirmationTokenProvider<ApplicationUser>)));
options.Tokens.EmailConfirmationTokenProvider = "CustomEmailConfirmation";
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddTransient<CustomEmailConfirmationTokenProvider<ApplicationUser>>();
services.AddTransient<IEmailSender, EmailSender>();
services.Configure<AuthMessageSenderOptions>(builder.Configuration);
services.AddHttpContextAccessor();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddDeveloperSigningCredential();
builder.Services.AddTransient<IProfileService, ProfileService>();
builder.Services.AddTransient<IFileStorageService, InAppStorageService>();
builder.Services.AddDataProtection();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication()
.AddIdentityServerJwt()
.AddGoogle(googleOptions =>
{
googleOptions.ClientId = builder.Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
}).AddFacebook(facebookOptions =>
{
facebookOptions.AppId = builder.Configuration["Authentication:Facebook:AppId"];
facebookOptions.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"];
});
services.Configure<DataProtectionTokenProviderOptions>(o =>
o.TokenLifespan = TimeSpan.FromHours(3));
services.ConfigureApplicationCookie(o =>
{
o.ExpireTimeSpan = TimeSpan.FromDays(5);
o.SlidingExpiration = true;
});
services.AddControllersWithViews();
services.AddRazorPages();
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
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();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.MapControllers();
app.MapFallbackToFile("index.html");
app.Run();
And I think that the ProfileService Impelementation would also be Useful:
using IdentityModel;
using Duende.IdentityServer.Models;
using Duende.IdentityServer.Services;
namespace Proj.Server.Services;
public class ProfileService : IProfileService
{
public ProfileService()
{
}
public async Task GetProfileDataAsync(ProfileDataRequestContext context)
{
var nameClaim = context.Subject.FindAll(JwtClaimTypes.Name);
context.IssuedClaims.AddRange(nameClaim);
var subClaim = context.Subject.FindAll(JwtClaimTypes.Subject);
context.IssuedClaims.AddRange(subClaim);
var roleClaims = context.Subject.FindAll(JwtClaimTypes.Role);
context.IssuedClaims.AddRange(roleClaims);
await Task.CompletedTask;
}
public async Task IsActiveAsync(IsActiveContext context)
{
await Task.CompletedTask;
}
}
I'd be grateful if anyone could point me in the right direction, Thanks :)
CodePudding user response:
Set the options in AddApiAuthorization
:
builder.Services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
const string OpenId = "openid";
options.IdentityResources[OpenId].UserClaims.Add(JwtClaimTypes.Email);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Email);
options.IdentityResources[OpenId].UserClaims.Add(JwtClaimTypes.Id);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Id);
options.IdentityResources[OpenId].UserClaims.Add(JwtClaimTypes.Name);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Name);
options.IdentityResources[OpenId].UserClaims.Add(JwtClaimTypes.Role);
options.ApiResources.Single().UserClaims.Add(JwtClaimTypes.Role);
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
In your controller:
var name = User.FindFirst(ClaimTypes.Name)?.Value;
CodePudding user response:
Turns out this question also describes the same problem I have, By removing
.AddIdentityServerJwt()
From the line
services.AddAuthentication()
.AddIdentityServerJwt()
Everything started working as expected... Now being the beginner I am, I'm not certain whether or not I should've commented out that line, it may have been important, so if anyone knows a better solution by all means share... Thanks.