How to set the redirect URI when using Microsoft sign-in in a .NET 5 application? - authentication

I have created a .NET 5 application with Microsoft sign-in based on this explanation.
It is working fine when running locally. However, something is going wrong when running the application in Amazon EKS. This became clear to me after reading error message I saw in the browser and after reading the network traffic.
This is how this looks like.
What becomes clear is that there is something wrong with "redirect_uri" (containing http instead of https). This is really frustrating as my application is using https. I use https when opening the application in my browser. It is important to mention that this does not occur when running the application locally on my laptop. What I hope for is that there is a simple way to set the "redirect_uri" property that is used in my code. In this way, I can guarantee that the right redirect uri is used.
Here is the source code I would like to change:
// This method gets called by the runtime. Use this method to add services to the container.
public void ConfigureServices(IServiceCollection services)
{
var configSettings = new ConfigSettings();
Configuration.Bind("ConfigSettings", configSettings);
services.AddSingleton(configSettings);
services.AddSingleton<IAuthResponseFactory, AuthResponseFactory>();
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddRazorPages()
.AddMicrosoftIdentityUI();
services.AddHealthChecks();
services.Configure<HealthCheckPublisherOptions>(options =>
{
options.Delay = TimeSpan.FromSeconds(2);
options.Predicate = (check) => check.Tags.Contains("ready");
});
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/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.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseEndpoints(endpoints =>
{
endpoints.MapHealthChecks("/health/ready", new HealthCheckOptions()
{
Predicate = (check) => check.Tags.Contains("ready")
});
endpoints.MapHealthChecks("/health/live", new HealthCheckOptions());
});
}
So how do I change my source in a way that I can set the redirect uri correctly?

Looks like you need to enable header forwarding.
Step 1: configure the ForwardedHeadersOptions
services.Configure<ForwardedHeadersOptions>(options =>
{
options.RequireHeaderSymmetry = false;
options.ForwardedHeaders = ForwardedHeaders.XForwardedFor | ForwardedHeaders.XForwardedProto;
// TODO : it's a bit unsafe to allow all Networks and Proxies...
options.KnownNetworks.Clear();
options.KnownProxies.Clear();
});
Step 2: UseForwardedHeaders in the public void Configure(IApplicationBuilder app, IHostingEnvironment env) method
app.UseForwardedHeaders();
Step 3: Only use UseHttpsRedirection for production
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
// Forward http to https (only needed for local development because the Azure Linux App Service already enforces https)
app.UseHttpsRedirection();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
See How to set redirect_uri protocol to HTTPS in Azure Web Apps and .net Core X Forwarded Proto not working

Related

ASP.NET Core Identity: how to deploy to production?

I'm stuck with deploying a SPA (ASP.NET Core + identity + ReactJS, all built from 'ASP.NET Core With React.js' template in VS) to production. It works fine in the development environment, but when deploying to prod (non-Azure), the API starts to return 401s.
I went through Authentication and authorization for SPAs article, it says the following:
To deploy the app to production, the following resources need to be provisioned:
...
A production certificate to use for signing tokens.
I have a link to the .pfx certificate on the hosting, but I'm a bit lost on how to implement it (and whether I actually need it?). Most samples I see assume running from the local/development machine (i.e. generate a self-signed certificate and load it from the file, etc).
Below is the code (skipping some irrelevant service configuration). I'm looking for a link to a resource that explains the changes needed to be done to deploy this to production (or a code sample).
Code:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationDbContext>(options =>
options.UseSqlServer(
Configuration.GetConnectionString("DefaultConnection")));
services.AddDatabaseDeveloperPageExceptionFilter();
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>();
services.AddAuthentication()
.AddIdentityServerJwt();
services.AddControllersWithViews();
services.AddRazorPages();
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
}
// 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();
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.UseSpaStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller}/{action=Index}/{id?}");
endpoints.MapRazorPages();
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
You need to provide a signing-key to IdentityServer, so that the keys issued by it are signed by the same key each time, even across re-deployments.
The answers to this question will show you how to add it to IdentitySever.
See also this link that provides this piece of code to add the key:
services.AddIdentityServer()
// .AddTemporarySigningCredential() // Can be used for testing until a real cert is available
.AddSigningCredential(new X509Certificate2(Path.Combine(".", "certs", "IdentityServer4Auth.pfx")))

gettting 401 on Hangfire with LocalRequestsOnlyAuthorizationFilter

We are trying to use hangfire with LocalRequestsOnlyAuthorizationFilter. We have deployed the application to IIS. When trying to access the hangfire dashboard from same machine where IIS is deployed, we are getting 401 on hangfire dashboard URL "/jobs". All we are trying to do here is allow to view the dashboard as long as request is coming from same machine where hangfire is deployed. Below is our config on startup.cs file
public void Configure(IApplicationBuilder app, IBackgroundJobClient backgroundJobs, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Error");
app.UseHsts();
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
Authorization = new[] { new LocalRequestsOnlyAuthorizationFilter() }
});
app.UseRouting();
app.UseEndpoints(endpoints =>
{
endpoints.MapRazorPages();
endpoints.MapHangfireDashboard();
});
backgroundJobs.Enqueue(() => Console.WriteLine("Hello world from Hangfire!"));
}
We checked both remote and local ip and both are same. Is there anything else we are missing here? Just to make sure application is running or not on IIS, we are added the another page and that page is working fine.
Change your Authorization to AuthorizationFilters,
app.UseHangfireDashboard("/jobs", new DashboardOptions()
{
AuthorizationFilters = new[] { new LocalRequestsOnlyAuthorizationFilter() }
});
Also, from this post check to make sure ASP.net is correctly installed and integrated pipeline is used for your application pool
This is also another good resource to go through in validating your IIS configuration

User.Identity with empty properties when running app on Kestrel

I have two situations during windows authentication using Active Directory domain identities:
When I running my app with IIS I'm getting object WindowsPrincipal filled with information
When I running my app with Kestrel I'm getting object ClaimsPrincipal without any informatiion about the user information
What could be the problem?
My ConfigureService:
public void ConfigureServices(IServiceCollection services)
{
services.AddDbContext<ApplicationContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
services.AddIdentity<IdentityUser, IdentityRole>().AddRoleManager<RoleManager<IdentityRole>>()
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<ApplicationContext>();
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
services.AddAuthorization();
services.AddScoped<IUserManagementManager, UserManagementManager>();
services.AddScoped<IRolesManagementManager, RolesManagementManager>();
}
Configure:
public void Configure(IApplicationBuilder app, IWebHostEnvironment env, ILogger<Startup> logger)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/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.UseResponseCaching();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Authentication}/{action=Index}");
});
}
How did you configure Windows Authentication in the Kestrel? I found that the attribute of IsAuthenticated in your image is false. This may cause you can not get any information about the ClaimsPrincipal.
You can use invoke AddAuthentication and AddNegotiate in Startup.ConfigureServices to add authentication services in Kestrel.
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
More information about how to config windows Authentication in the Kestrel you can refer to this link: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/windowsauth?view=aspnetcore-5.0&tabs=visual-studio

IdentityServer - How to bypass authorization for simple debug

I have several .NET core API and I use IdentityServer 4 as a seperate service for authentication.
The problem is that in "debug" I also wish to run my API without authentication (without launching the IdentityServer).
So, I try to bypass it... I have try several solutions, but none work:
- With a AuthorizationHandler: Bypass Authorize Attribute in .Net Core for Release Version
- With a Middleware : Simple token based authentication/authorization in asp.net core for Mongodb datastore
- With a filter : ASP.NET Core with optional authentication/authorization
- With AllowAnonymousFilter : Bypass Authorize Attribute in .Net Core for Release Version
But no way, none of theses solutions work, I still got a "401 Undocumented Error: Unauthorized" !
Here is some parts of my code:
public void ConfigureServices(IServiceCollection services)
{
// JSON - setup serialization
services.AddControllers().
AddJsonOptions(options =>
{
options.JsonSerializerOptions.Converters.Add(new JsonStringEnumConverter(new TargetSpot.Core.Json.SnakeCaseNamingStrategy()));
options.JsonSerializerOptions.IgnoreNullValues = true;
});
// Force lowercase naming
services.AddRouting(options => options.LowercaseUrls = true);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
// Setup the connection to the IdentityServer to request a token to access our API
services.AddAuthentication(IdentityServer4.AccessTokenValidation.IdentityServerAuthenticationDefaults.AuthenticationScheme)
.AddIdentityServerAuthentication(options =>
{
options.Authority = Configuration.GetSection("APISettings")["AuthorityURL"];
options.RequireHttpsMetadata = false;
options.ApiName = Configuration.GetSection("APISettings")["APIName"];
});
// Add swagger
services.AddSwaggerGen(options =>
{
//options.DescribeAllEnumsAsStrings();
options.SwaggerDoc("v1", new Microsoft.OpenApi.Models.OpenApiInfo
{
Title = "HTTP API",
Version = "v1",
Description = "The Service HTTP API",
TermsOfService = new Uri("http://www.myurl.com/tos")
});
// XML Documentation
var xmlFile = $"{System.Reflection.Assembly.GetExecutingAssembly().GetName().Name}.xml";
var xmlPath = System.IO.Path.Combine(AppContext.BaseDirectory, xmlFile);
options.IncludeXmlComments(xmlPath);
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
// 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.UseRouting();
app.UseAuthorization();
app.UseAuthentication();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllers();
});
app.UseSwagger().UseSwaggerUI(c =>
{
c.SwaggerEndpoint("/swagger/v1/swagger.json", "Winamp API v1");
});
}
I had similar problem.
AllowAnonymousFilter works in ASP.NET Core 2.2 but not in ASP.NET Core 3.x.
After day of investigation I have found out that switching from UseEndpoints to UseMvc solved it and I can now disable authentication without commenting out [Authorize] attributes.
It seems that UseEndpoints does not use filter when registered by AddMvc but how to correctly register it when using UseEndpoints I do not know.
My solution
Startup.ConfigureServices:
services.AddMvc(o =>
{
o.EnableEndpointRouting = false;
o.Filters.Add(new AllowAnonymousFilter());
});
Startup.Configure:
// anonymous filter works with UseMvc but not with UseEndpoints
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
//app.UseEndpoints(endpoints =>
//{
// endpoints.MapControllerRoute(
// name: "default",
// pattern: "{controller=Home}/{action=Index}/{id?}");
//});
I found the solution in this link: https://docs.identityserver.io/_/downloads/en/latest/pdf/. Obviously I had to remove the Authorize attributes I added manually in my controllers.
app.UseEndpoints(endpoints =>
{
// Allowing Anonymous access to all controllers but only in Local environment
if (env.IsEnvironment(Constants.ApplicationConstants.LocalEnvironment))
endpoints.MapControllers();
else
endpoints.MapControllers().RequireAuthorization();
});

Run a custom method immediately after Azure AD Authentication

I have an ASP.NET Core Web App which successfully uses Azure AD Authentication. I would like to run a process immediately after a user logs in. I thought I might somehow handle the Redirect URI specified in the Azure app registration but I couldn't figure it out as much of the login process is nicely handled by the .AddAzureAd() method in my Startup.cs.
Can anyone suggest an easy way to call a method or redirect to a razor page after authentication? Preferably something which would not be circumvented by specifying a returnUrl in the initial request.
Update
Between posting the question and seeing the answers I found what might be considered a hack:
Basically I created a service and injected it into my _LoginPartial.cshtml page and then call a method on the service.
...
#inject MyService myService
...
#if (User.Identity.IsAuthenticated)
{
await MyService.MyCustomMethod();
...
}
For running code or changing the redirect url, you could configure OpenIdConnectOptions.
services.AddAuthentication(AzureADDefaults.AuthenticationScheme)
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
services.Configure<OpenIdConnectOptions>(AzureADDefaults.OpenIdScheme, options =>
{
options.Events = new OpenIdConnectEvents
{
OnTokenValidated = ctx =>
{
ctx.Properties.RedirectUri = "/Home/Privacy";
return Task.CompletedTask;
},
};
});
If you want to run code after authentication, you could place your code in the OnTokenValidated.
If you want to change the uri, you could replace /Home/Privacy.
You can define the route in Startup.cs file. I used the sample here.
public void ConfigureServices(IServiceCollection services)
{
services.AddAuthentication(sharedOptions =>
{
sharedOptions.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
sharedOptions.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddAzureAd(options => Configuration.Bind("AzureAd", options))
.AddCookie();
services.AddMvc();
}
// 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();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseAuthentication();
app.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller=Home}/{action=Contact}/{id?}");
});
}