I have a .Net WASM project that I am migrating to .Net 8 from .Net6. I want to take advantage of some of the advanced render modes.
I used the template in VS2022 with Individual accounts, With 'Auto (Server and WebAssemby) and per page/component as the interactivity location. (I included the sample pages).
I have been struggling so have reduced my app to some simple elements to aid testing. The login etc are all working fine with the template pages. But once 'logged in' I need to get the user details and do other things. Presently via webAPI using WebAssembly (I will move to combined rendering with Interfaces later).
I need to be able to access controllers using both [Authorize] and [AllowAnonymous] controller attributes, but at present I am struggling to get to [Authorize] d controllers with the server side sending back the login page when not logged in (intead 4010 not authroized.
In .net 6 I had to use httpclientfactor on the client to creat two different 'http' objects - one for authorized and onw for not. I haven't got to that yet, but I have seen some documentation that I think will sort it when I do. (But any tips welcome).
This is my backend 'Program.cs:
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
builder.Services.AddRazorComponents()
.AddInteractiveServerComponents()
.AddInteractiveWebAssemblyComponents();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddScoped<IdentityUserAccessor>();
builder.Services.AddScoped<IdentityRedirectManager>();
builder.Services.AddScoped<AuthenticationStateProvider, PersistingRevalidatingAuthenticationStateProvider>();
builder.Services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultSignInScheme = IdentityConstants.ExternalScheme;
})
.AddIdentityCookies();
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection") ?? throw new InvalidOperationException("Connection string 'DefaultConnection' not found.");
builder.Services.AddDbContext<RGDbContext>(options =>
options.UseSqlServer(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
//builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
// .AddEntityFrameworkStores<RGDbContext>()
// .AddSignInManager()
// .AddDefaultTokenProviders();
builder.Services.AddIdentityCore<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<RGDbContext>()
.AddSignInManager()
.AddDefaultTokenProviders();
//builder.Services.AddScoped(sp => new HttpClient
//{
// BaseAddress = new Uri("https://localhost:7274")
//});
string baseURI = builder.Configuration["applicationUrl"];
if (!string.IsNullOrEmpty(baseURI))
{
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(baseURI)
});
// Add a CORS policy for the client
// Add .AllowCredentials() for apps that use an Identity Provider for authn/z
builder.Services.AddCors(
options => options.AddDefaultPolicy(
policy => policy.WithOrigins(baseURI,
baseURI)
.AllowAnyMethod()
.AllowAnyHeader()));
}
//builder.Services.AddControllers(); // For web Api
// The options are to stop it enforcing null value checking in json packets to and from the server.
builder.Services.AddControllersWithViews(
options => options.SuppressImplicitRequiredAttributeForNonNullableReferenceTypes = true
);
builder.Services.AddAntiforgery(options =>
{
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
});
/// My additions
///
/// BLAZORISE
builder.Services
.AddBlazorise(options =>
{
options.Immediate = true;
})
.AddBootstrap5Providers()
.AddFontAwesomeIcons();
#region ****My services and configs****
//AzureHelpers
builder.Services.AddScoped<IAzureHelpers, AzureHelpers>();
//builder.Services.AddSingleton<IEmailSender<ApplicationUser>, IdentityNoOpEmailSender>();
builder.Services.AddTransient<EmailSender>();
// Auto Mapper Configurations
var mappingConfig = new MapperConfiguration(mc =>
{
mc.AddProfile(new MappingProfile());
mc.AddCollectionMappers();
});
IMapper mapper = mappingConfig.CreateMapper();
builder.Services.AddSingleton(mapper);
builder.Services.AddScoped<IUserLib, UserLib>();
builder.Services.AddScoped<UserService>();
//PrizeHelpers
builder.Services.AddScoped<IPrizeHelpers, PrizeHelpers>();
//RaffleHelpers
builder.Services.AddScoped<IRaffleGameHelpers, RaffleGameHelpers>();
//SystemHelpers
builder.Services.AddScoped<ISystemHelpers, SystemHelpers>();
//AccountsHelpers
builder.Services.AddScoped<IAccountsHelpers, AccountsHelpers>();
//OrganisationHelpers
builder.Services.AddScoped<IOrgHelpers, OrgHelpers>();
var blobbconnectionString = builder.Configuration.GetConnectionString("DefaultConnection");
var blobconnection = builder.Configuration["ConnectionStrings: RaffleGames_Account:blob"];
var queueconnection = builder.Configuration["ConnectionStrings:RaffleGames_Account:queue"];
builder.Services.AddAzureClients(builder =>
{
builder.AddBlobServiceClient(blobconnection);
builder.AddQueueServiceClient(queueconnection);
});
#endregion ***********************************
////////////APP Section
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseWebAssemblyDebugging();
app.UseMigrationsEndPoint();
}
else
{
app.UseExceptionHandler("/Error", createScopeForErrors: true);
// The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
app.UseHsts();
}
// Activate the CORS policy
app.UseCors();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseAntiforgery();
app.MapRazorComponents<App>()
.AddInteractiveServerRenderMode()
.AddInteractiveWebAssemblyRenderMode()
.AddAdditionalAssemblies(typeof(RaffleGames.Client._Imports).Assembly);
// Add additional endpoints required by the Identity /Account Razor components.
app.MapAdditionalIdentityEndpoints();
app.MapControllers();
app.Run();
This is the code for the Client program.cs:
var builder = WebAssemblyHostBuilder.CreateDefault(args);
builder.Services.AddAuthorizationCore();
builder.Services.AddCascadingAuthenticationState();
builder.Services.AddSingleton<AuthenticationStateProvider, PersistentAuthenticationStateProvider>();
builder.Services.AddScoped(sp => new HttpClient
{
BaseAddress = new Uri(builder.HostEnvironment.BaseAddress)
});
builder.Services.AddScoped<UserService>();
builder.Services
.AddBlazorise(options =>
{
options.Immediate = true;
})
.AddBootstrap5Providers()
.AddFontAwesomeIcons();
await builder.Build().RunAsync();
I have created 2 simple controllers Test and Values - identical but for the Authorise attibute. This is TestController:
using Microsoft.AspNetCo
[Route("api/[controller]")]
[ApiController]
public class TestController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<string>> Get()
{
await Task.Delay(1000);
return Ok("Yes");
}
}
}
... and this is ValuesController:
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Mvc;
namespace RaffleGames.Controllers
{
[Authorize]
[Route("api/[controller]")]
[ApiController]
public class ValuesController : ControllerBase
{
[HttpGet]
public async Task<ActionResult<string>> Get()
{
await Task.Delay(1000);
return Ok("Yes");
}
}
}
When not logged in, the TestController works fine, but the ValuesController returns a login page as a string. It would seem trying to re-sirect to login. Once logged in, both controllers work correctly, and indeed I have another controller that returns a userDTO that also works fine once logged in.I would like to isolate the 'url/api/*' to make it so it doesn't re-direct but (if possible) retain the ability of the normal pages to re-direct- however that is not a must, as the current app doesnt have that.)
I have trawled the docs, but sadly can't find a scenario like this (which surprises me as I thought it might be quite a common one).
EDIT
The AI assist did help (maybe with the dual http clients - not unlike I have it now on the .net6 version). However, I would still prefer to get an 'unauthorized' result rather than a login string from the apis when not logged in. The situation isn't always avoidable.
Thanks in advance.