Deployed WebAssembly Blazor application doesn't route authentication properly, but locally working - asp.net-core

I created a 'normal' WebAssembly Blazor client and server application.
I decided later on to add authentication, so I followed the steps at this address:
https://learn.microsoft.com/en-us/aspnet/core/blazor/security/webassembly/hosted-with-identity-server?view=aspnetcore-3.1&tabs=visual-studio
ending up with this startup code in the server part of the Blazor WebAssembly application:
public class Startup
{
private readonly IWebHostEnvironment _environment;
private readonly IConfiguration _configuration;
public Startup(IWebHostEnvironment environment, IConfiguration configuration)
{
_environment = environment;
_configuration = configuration;
}
public void ConfigureServices(IServiceCollection services)
{
if (_environment.IsDevelopment())
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
_configuration.GetConnectionString("LocalEnvironment")));
}
else
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
_configuration.GetConnectionString("CloudEnvironment")));
}
services.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>(options =>
{
options.IdentityResources["openid"].UserClaims.Add("name");
options.ApiResources.Single().UserClaims.Add("name");
options.IdentityResources["openid"].UserClaims.Add("role");
options.ApiResources.Single().UserClaims.Add("role");
});
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Remove("role");
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
services.Configure<IdentityOptions>(options =>
{
options.ClaimsIdentity.UserIdClaimType = ClaimTypes.NameIdentifier;
options.User.RequireUniqueEmail = true;
options.Password.RequiredLength = 8;
options.Password.RequireNonAlphanumeric = true;
options.Password.RequireLowercase = false;
options.Password.RequireUppercase = true;
options.Password.RequireDigit = true;
});
services.AddTransient<IPasswordValidator<ApplicationUser>, CustomPasswordPolicy>();
services.AddTransient<IUserValidator<ApplicationUser>, CustomUsernameEmailPolicy>();
services.AddTransient<IProfileService, ProfileService>();
services.AddHttpContextAccessor();
services.AddHsts(options =>
{
options.Preload = true;
options.IncludeSubDomains = true;
options.MaxAge = TimeSpan.FromDays(60);
});
}
public void Configure(IApplicationBuilder app, ApplicationDbContext db,
UserManager<ApplicationUser> userManager, RoleManager<ApplicationRole> roleManager)
{
if (_environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
app.UseWebAssemblyDebugging();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
db.Database.EnsureCreated();
app.UseHttpsRedirection();
app.UseBlazorFrameworkFiles();
app.UseStaticFiles();
app.UseRouting();
app.UseIdentityServer();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapControllers();
endpoints.MapFallbackToFile("index.html");
});
IdentityDataInitializer.SeedTestData(userManager, roleManager);
}
}
The resulting application works perfectly when in the Development environment (both in Kestrel and IIS Express, but when I deploy it to an Azure Service App, the authentication part, and only that one, doesn't work properly.
For example: if I click the Login button in the home page, when I'm local there's a jump to the page:
https://localhost:5001/Identity/Account/Login?ReturnUrl=...
That's the correct path, because, moreover, after logging in, I'm redirected correctly to the home page.
But when I click the same button on the deployed application, I see the address becoming first
'.../authentication/login'
and after a few moments, going to
'.../connect/authorize?client_id=Test1.Client&redirect_uri=...'
that's a not existing page.
Personally, I don't even understand, at the moment, if it's a server or client problem, or just the configuration of the service app on Azure...
Please, feel free to ask for other code, or anything that can help.
Thank you in advance.

/connect/authorize is one of the endpoints that IdentityServer listens for and it is the first URL that the application/client should redirect to when the user is about to authenticate.
One way to tell if IdentityServer is up and running is to go to this URL https://yourdomain.com/.well-known/openid-configuration
This URL Should always succeed.
When you deploy to the cloud and Azure Service App, one thing is to make sure you understand where HTTPS is terminated, is it in your application or in Azure Service? If it is not terminated in the application it-self, then it might be that the public URL is HTTPS but what your application sees is HTTP.
Some links to follow:
https://securecloud.blog/2020/07/23/unobvious-stuff-about-azure-services-app-service-tls-termination/
https://www.hanselman.com/blog/SecuringAnAzureAppServiceWebsiteUnderSSLInMinutesWithLetsEncrypt.aspx

Related

Asp.Net Core 6 Scoped Filter inject UserManager

I'm working on linking Twilio Verify into an Asp.Net Core web site. I'm pretty sure I have to figure out how to access UserManager in the filter (constructor). But, I don't know how to access it.
My VerifyFilter:
public class VerifyFilter : IAsyncResourceFilter
{
private readonly UserManager<ApplicationUser> _manager;
public VerifyFilter(UserManager<ApplicationUser> manager)
{ _manager = manager; }
public async Task OnResourceExecutionAsync(ResourceExecutingContext context, ResourceExecutionDelegate next)
{ // cut just to make listing a bit shorter }
}
My program.cs file currently looks like this:
builder.Services.AddScoped<VerifyFilter>();
What I don't know is how I get the UserManager so I can pass it in.
I have another scoped right above it and I had to do this to get Verification to work.
Configuration.Twilio twilio = new Configuration.Twilio();
builder.Services.AddScoped<IVerification>(t => new Verification(twilio));
So I'm sure it's just a matter of figuring out how to get UserManager to pass in as a constructor, but with MinimalAPI and .Net Core 6.0, I don't know where it is at.
My entire program.cs file:
using Microsoft.AspNetCore.Identity;
using Microsoft.EntityFrameworkCore;
using test4.Data;
using test4.Filters;
var builder = WebApplication.CreateBuilder(args);
// Add services to the container.
var connectionString = builder.Configuration.GetConnectionString("DefaultConnection");
builder.Services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlite(connectionString));
builder.Services.AddDatabaseDeveloperPageExceptionFilter();
builder.Services
.AddDefaultIdentity<IdentityUser>(
options =>
{
// These will get updated to production versions later.
options.SignIn.RequireConfirmedAccount = true;
options.Password.RequiredLength = 1;
})
.AddEntityFrameworkStores<ApplicationDbContext>();
builder.Services.AddRazorPages();
builder.Services.AddScoped<VerifyFilter>();
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
var app = builder.Build();
// Configure the HTTP request pipeline.
if (app.Environment.IsDevelopment())
{
app.UseMigrationsEndPoint();
}
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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.MapRazorPages();
app.Run();
Any suggestions?
Thanks,
Nick
You could try to register the filter via the following code:
//register the Identity service
//register the custom filters.
builder.Services.AddControllers(op =>
{
op.Filters.Add<VerifyFilter>();
});
builder.Services.AddEndpointsApiExplorer();
builder.Services.AddSwaggerGen();

How to resolve Request to Long error in Asp.Net Core Azure B2C Configuraiton?

I am new to Asp.Net Core identity.
I have configured the startup as per below. When I run the code in a normal and incognito browser I get the below error.
I have cleared cookies as previous questions have suggested. What is interesting is a high number of cookies get created when loading the sign screen.
My issue is similar to those described in the below old articles. Both solutions seem outdated.
https://www.javaer101.com/en/article/18781756.html
https://blog.bitscry.com/2018/09/19/azure-ad-request-too-long/
using d365fl.DocumentGenerator.blazor_frontend.Data;
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
using Microsoft.AspNetCore.Authorization;
using Microsoft.AspNetCore.Builder;
using Microsoft.AspNetCore.Components;
using Microsoft.AspNetCore.Hosting;
using Microsoft.AspNetCore.HttpsPolicy;
using Microsoft.AspNetCore.Mvc.Authorization;
using Microsoft.Extensions.Configuration;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
using Microsoft.IdentityModel.Logging;
namespace d365fl.DocumentGenerator.blazor_frontend
{
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
// For more information on how to configure your application, visit https://go.microsoft.com/fwlink/?LinkID=398940
public void ConfigureServices(IServiceCollection services)
{
ConfigureIdentiy(services);
services.AddRazorPages();
services.AddServerSideBlazor();
services.AddSingleton<WeatherForecastService>();
}
private void ConfigureIdentiy(IServiceCollection services)
{
services.AddMicrosoftIdentityWebAppAuthentication(Configuration, "AzureAdB2C");
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.Configure<OpenIdConnectOptions>(Configuration.GetSection("AzureAdB2C"));
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
IdentityModelEventSource.ShowPII = true;
}
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.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
endpoints.MapBlazorHub();
endpoints.MapFallbackToPage("/_Host");
});
}
}
}
EDIT 1 - HTTP Request from Developer Toolbar
EDIT 2 - Screen Shot of Cookie data from Developer Toolbar / Network Tab
As we discussed in the comment, the issue is cause by too many cookies.
Please clear your cookies and modify your code to avoid endless loops and back and forth requests.
See this answer for more details.

how to auto-start/warm up .net core web app hosted in IIS

I have a .net core (3.1) web app which is hosted on IIS. I cannot figure out how to run a piece of code before the first request. I have done the following:
set the 'start mode' of the app pool = "AlwaysRunning" and "Idle time-out" = 0
set the 'preload enabled' = true on the web site
What i am missing is where/how i register the code/service that i would like to run before the first request comes in?
thanks in advance
What i am missing is where/how i register the code/service that i would like to run before the first request comes in?
If you want to call one of your mvc or web api after the application has start up completely to warm up your web application. You could try to use IHostApplicationLifetime's ApplicationStarted method.
This method will be called after the application started immediately.
You could inject IHostApplicationLifetime into Configure() method , then write the callback for ApplicationStarted that would be triggered when the application host has fully started.
More details, you could refer to below example:
Register httpclient service in Startup.cs ConfigureServices method
public void ConfigureServices(IServiceCollection services)
{
services.AddHttpClient();
services.AddControllersWithViews();
}
Add lifetime.ApplicationStarted.Register callback in Configure method:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, IHostApplicationLifetime lifetime)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Default}/{action=Index}/{id?}");
});
IHttpClientFactory httpClientFactory = app.ApplicationServices.GetService(typeof(IHttpClientFactory)) as IHttpClientFactory;
lifetime.ApplicationStarted.Register(onApplicationStartedAsync(httpClientFactory).Wait);
}
private async Task<Action> onApplicationStartedAsync(IHttpClientFactory httpClientFactory)
{
var httpclient = httpClientFactory.CreateClient();
var httpMessage = new HttpRequestMessage(HttpMethod.Get, "http://localhost:5000/api/values");
var httpresponse = await httpclient.SendAsync(httpMessage);
if (httpresponse.IsSuccessStatusCode)
{
string res = await httpresponse.Content.ReadAsStringAsync();
}
return null;
}
Result:

Unable to resolve service for type IEmailSender while attempting to activate RegisterModel

I'm using Identity and I have a problem that I make a new example project and with individual authentication and scaffold identity
InvalidOperationException: Unable to resolve service for type 'Microsoft.AspNetCore.Identity.UI.Services.IEmailSender' while attempting to activate 'MASQ.Areas.Identity.Pages.Account.RegisterModel'.
I am using ASP.NET Core 3.0 and had similar issue. I added the following .AddDefaultUI() to my Startup.cs & it worked.
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddDefaultTokenProviders()
.AddDefaultUI()
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddControllersWithViews();
services.AddRazorPages().AddRazorRuntimeCompilation();
}
There're two ways to do that :
remove the services.AddDefaultTokenProviders() in the ConfigurureServices() to disable two-factor authentication (2FA) :
// file: `Startup.cs` :
services.AddDefaultIdentity<IdentityUser>()
.AddEntityFrameworkStores<ApplicationDbContext>();
///.AddDefaultTokenProviders(); /// remove this line
Add your own IEmailSender and ISmsSender implementation to DI contianer if you would like to enable 2FA
// file: `Startup.cs`
services.AddTransient<IEmailSender,YourEmailSender>();
services.AddTransient<IEmailSender,YourSmsSender>();
Edit:
Both should work.
Both should work for ASP.NET Core 2.1. However, as of ASP.NET Core 3.0, the first approach doesn't work any more.
Add Default UI in the configuration service:
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders()
.AddDefaultUI();
For ASP.NET Core 5.0 you can use the following code, instead of calling AddIdentity
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<AppDbContext>()
.AddDefaultTokenProviders();
public class Startup
{
public Startup(IConfiguration configuration)
{
Configuration = configuration;
}
public IConfiguration Configuration { get; }
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
services.Configure<CookiePolicyOptions>(options =>
{
// This lambda determines whether user consent for non-essential cookies is needed for a given request.
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<ApplicationUser, ApplicationRole>(
option => {
option.Lockout.DefaultLockoutTimeSpan = TimeSpan.FromMinutes(5);
option.Lockout.MaxFailedAccessAttempts = 5;
option.Lockout.AllowedForNewUsers = false;
})
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddDefaultTokenProviders();
//services.AddDbContext<ApplicationDbContext>(options =>
// options.UseSqlServer(
// Configuration.GetConnectionString("DefaultConnection")));
//services.AddIdentity<ApplicationUser, IdentityRole>()
// .AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();
services.AddTransient<Areas.Identity.Services.IEmailSender, AuthMessageSender>();
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_1);
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
app.UseDatabaseErrorPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Index}/{id?}");
});
}
}
maybe it's my time to be usefull. Since approach with green checkmark doesn't work in core 3.0 I have an idea. Basicaly you need to disable IEmailSender service. I think that not the best aproach, but if you dont need IEmailSender and you just want to quickly setup user functionality you can go to Register.cshtml.cs
and comment out (or delete) private readonly IEmailSender _emailSender; and everywhere it is used in this controller. Hope it helps.

OpenIddict - Authorization and authentication with Microservices

I have a mobile (native) and web app (SPA) that talks to backend microservices (developed in core 2.0) for authentication/authorization and other domain related functions, which has configured using Opendidict. Both apps are getting access token. What I'm struggling with is, all microservices should accept bearer access token and authentication/authorization logged in user (a central auth-service), access token generated in auth microservice (OpenIddict 2.*). So what changes I'm missing in microservices, where REST APIs are marked [Authorize]?
Code from Auth Microservice:
public void ConfigureServices(IServiceCollection services)
{
var connection = Configuration.GetConnectionString("DefaultConnection");
services.AddDbContext<IdentityDbContext>(options =>
{
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection"));
options.UseOpenIddict();
});
services.AddAuthentication().AddOAuthValidation();
services.AddOpenIddict(options =>
{
options.AddEntityFrameworkCoreStores<IdentityDbContext>();
options.AddMvcBinders();
options.EnableTokenEndpoint("/connect/token");
// Enable the password flow.
options.AllowPasswordFlow().AllowRefreshTokenFlow();
options.SetRefreshTokenLifetime(TimeSpan.FromHours(1));
options.DisableHttpsRequirement();
});
services.AddDbContext<AuthDbContext>(options => options.UseSqlServer(connection));
services.AddScoped<IUserRepository, UserRepository>();
services.AddAuthentication(options =>
{
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
});
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}
Existing code in Notification Microservice
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<MastersDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddAuthentication().AddOAuthValidation();
services.AddAuthentication(options =>
{
options.DefaultScheme = OAuthValidationDefaults.AuthenticationScheme;
});
services.AddAuthorization(options =>
{
options.AddPolicy("RequireAdministratorRole", policy => policy.RequireRole("Administrator"));
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseCors(builder =>
builder.WithOrigins("*")
.AllowAnyHeader()
.AllowAnyMethod()
.AllowAnyOrigin()
);
//app.UseAntiforgeryToken();
app.UseMvc();
app.UseAuthentication();
}
Notification Controller:
// POST api/values
[HttpPost]
[Authorize]
public IActionResult Post(Notification notification)
{
//logic
return Ok();
}
For tokens to be correctly decrypted by all your micro-services, you need to make sure that the key ring containing the master keys (that are derived by ASP.NET Core Data Protection to create encryption and validation keys) is correctly synchronized. The procedure is described here: https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview.
Here's an example of how it could be done using a shared folder:
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(#"\\server\share\directory\"))
}
You'll also need to configure the two applications to use the same "application discriminator":
public void ConfigureServices(IServiceCollection services)
{
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(#"\\server\share\directory\"))
.SetApplicationName("Your application name");
}