ASP.NET Core 2.2 Authentication not working after deploying the app - Nginx with mutliple apps - authentication

I'm trying deploy my application to our internal server with Nginx. It's an ASP.NET Core 2.2 Razor Pages site. I was asked to include some authentication for logging purposes. Eventually everything was working fine on my computer. I used this site to add cookie based authentication:
https://www.mikesdotnetting.com/article/335/simple-authentication-in-razor-pages-without-a-database
I did some modifications to handle a few more users within the OnPost() method. Though I don't think that would be the problem.
It might be important to mention this is not the only .net core app running on the server. The setup is similar to this:
app1: our.domain.com
app2: our.domain.com/app2 (this is the one I have problems with)
everything works properly except the login. When I try to log in, if the password and username is correct it gets redirected to the proper page, however it seems like there is no identity or it cannot find it afterwards.
On my first attempt, I found the following error in the kestrel service log:
fail: Microsoft.AspNetCore.Antiforgery.Internal.DefaultAntiforgery[7]
I could make that disappear with using services.AddDataProtection(), however the problem is still the same, I get redirected - or get the error message if the login attempt is incorrect - but still can't access the authorised folder and e.g. the HttpContext.User.Claims.FirstOrDefault(x => x.Type == ClaimTypes.GivenName)?.Value returns null or empty.
I did a quick test and added the same login page and an authorised folder, and the other dependencies to the other app. And there it works. I didn't even included services.AddDataProtection() in the startup.cs. The login works perfectly. Though it's using .net core 2.1.
So it might have to do something with the rooting? Or I don't know. I'm totally lost. I'm not a full time developer, more like a hobbyist and I'm completely stuck at this moment. Maybe I messed up something within the startup.cs? Or I should add something else? Or is it something with the cookies handling? I did make a lot of searching, no luck so far.
Here is the relevant part of my startup.cs:
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.AddEntityFrameworkNpgsql()
.AddDbContext<Models.UserAccessDbContext>(options =>
options.UseNpgsql(Configuration.GetConnectionString("appConnection")))
.BuildServiceProvider();
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;
});
// https://hanselman.com/blog/DealingWithApplicationBaseURLsAndRazorLinkGenerationWhileHostingASPNETWebAppsBehindReverseProxies.aspx
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardedHeaders = ForwardedHeaders.All;
options.AllowedHosts = Configuration.GetValue<string>("AllowedHosts")?.Split(';').ToList<string>();
});
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme).AddCookie(cookieOptions =>
{
cookieOptions.LoginPath = "/";
});
services.AddMvc().AddRazorPagesOptions(options =>
{
options.Conventions.AuthorizeFolder("/admin");
}).SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddDataProtection()
.SetApplicationName("app")
.PersistKeysToFileSystem(new DirectoryInfo(#"/var/dpkeys/"));
}
// 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("/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.Use((context, next) =>
{
context.Request.PathBase = new PathString("/app");
return next.Invoke();
});
}
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseCookiePolicy();
app.UseAuthentication();
app.UseMvc();
}
Update:
One small detail. The cookie is created and I can see it within the Chrome's inspector. But the site/app doesn't see me as an authenticated user.

Seems I had problems with the cookie settings. One more difference between the two pages, that one if it uses font-awesome, which means I have some external cookies on my site. As a wild guess, first I set
options.MinimumSameSitePolicy = SameSiteMode.Lax;
since the other cookies had that mode.
And just like that it started to work on the hosting server as well! Now the app recognises the cookie created after the log in.

Related

Windows Authentication not working in Role Handler in VS Code and ASP.NET Core

Odd behavior in VS Code using Kestrel. In the Claims transformer I can get the identity.Name as the logged in user...me. But in the Role Handler the claims come up null and no user name. I would think if Windows authentication weren't working then it wouldn't work at all. Works in ASP.NET Core 5 with Visual Studio 2019 but not in ASP.NET Core 6 in VS Code. Yes, I understand that Visual Studio is using IIS Express and VS Code is using Kestrel.
UPDATE: I have looked at the request in Fiddler and it seems like the second request is actually being authenticated, I see a valid Authorization header in the request with Negotiate. So that would seem to indicate that the Windows auth part of this is working. I then see a 400 error which means that the request is badly formed. I'll have to take a look at that.
Further update. The real issue was authentication. I created a service principal name and got it working.
The explicit message in Fiddler is:
Authorization Header (Negotiate) appears to contain a Kerberos ticket:
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
public Task<ClaimsPrincipal> TransformAsync(ClaimsPrincipal principal)
{
ClaimsIdentity identity = principal.Identity as ClaimsIdentity;
string userName = identity.Name.ToLower();
bool auth = identity.IsAuthenticated;
string[] roles = new string[]{"admin"};
foreach (string role in roles)
{
identity.AddClaim(new Claim(ClaimTypes.Role, role));
}
return Task.FromResult(principal);
}
public class AuthenticatedRoleHandler : AuthorizationHandler<AuthenticatedRequirement>
{
protected override Task HandleRequirementAsync(AuthorizationHandlerContext context,AuthenticatedRequirement requirement)
{
ClaimsPrincipal user = context.User;
if (user != null && user.Identity.IsAuthenticated) {
context.Succeed(requirement);
} else {
context.Fail();
}
return Task.CompletedTask;
}
}
I'm adding the Program.cs code.
string CorsPolicy = "CorsPolicy";
WebApplicationBuilder? builder = WebApplication.CreateBuilder(args);
builder.WebHost.UseKestrel();
ConfigurationManager _configuration = builder.Configuration;
// Add services to the container.
IServiceCollection? services = builder.Services;
services.AddTransient<IActiveDirectoryUserService,
ActiveDirectoryUserService>();
services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();
services.AddControllersWithViews();
services.AddScoped<IClaimsTransformation, MyClaimsTransformer>();//only
runs if authenticated, not being authenticated here
services.AddAuthorization(options =>
{
options.FallbackPolicy = options.DefaultPolicy;
options.AddPolicy("AuthenticatedOnly", policy => {
policy.Requirements.Add(new AuthenticatedRequirement(true));
});
});
services.AddSingleton<IAuthorizationHandler, AppUserRoleHandler>();
services.AddSingleton<IAuthorizationHandler, AuthenticatedRoleHandler>
();
services.AddCors(options =>
{
options.AddPolicy(CorsPolicy,
builder => builder
.WithOrigins("https://localhost:7021","https://localhost:44414") //Note: The URL must be specified without a trailing slash (/).
.AllowAnyMethod()
.AllowAnyHeader()
.AllowCredentials());
});
WebApplication app = builder.Build();
// Configure the HTTP request pipeline.
if (!app.Environment.IsDevelopment())
{
app.UseHsts();
}
app.UseAuthentication();
app.UseHttpsRedirection();
app.UseStaticFiles();
app.UseRouting();
According to my test, it works well when I am using the Kestrel(.net 6) inside the VS(its same as run the application inside the Kestrel like VS code). The only difference between the windows authentication for the IIS express and the Kestrel is if you enable the windows authentication for the IIS express, all the request which come to the application will firstly need the authentication which means all the request come to the AuthenticatedRoleHandler has already been authenticated.
But if you use services.AddAuthentication(NegotiateDefaults.AuthenticationScheme)
.AddNegotiate();`, the request will not firstly be authenticated, if the application find it doesn't contain any authentication relatede information, then it will return response to let the client put in the windows credential. This makes the AuthenticatedRoleHandler fired twice. The first one is authentication user name is null, but after the user typed in the credential, we could get the user name inside it.
My test result like below:
If this still not work at your side, please update the whole codes which related with how you register the requirement and how you use it.

Enforcing ASP .NET Core 2.1 to use HTTPS and return 400 Error

I have an older ASP .NET Core 2.1 app, which has been accepting HTTP requests.
I need now the app to Stop using HTTP and use HTTPS only and return 40x errors for any HTTP requests automatically/by default.
I have done the following:
added app.UseHttpsRedirection(); to the Configure method; also added
added "https_port": 443, to the appsettings.json
I also tried to enable "HTTPS Only" on the app itself (in Azure).
But it still accepts and returns the same responses (and 200) whether I use HTTP or HTTPS calls.
Any suggestion on what am I missing to implement here?
Here is part of my Startup.cs:
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.Use(async (context, next) =>
{
if (context.Request.Method == "OPTIONS")
{
context.Response.StatusCode = 405;
return;
}
await next.Invoke();
});
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
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.UseUsageLogging();
app.UseSerilogRequestLogging();
app.UseCoreExceptionHandling();
app.UseAuthentication();
app.UseCors("CorsPolicy");
app.UseAspNetCoreAuth();
...
Here are the requests, which both returns 200 OK, and the same data:
http://myapp.net/ping
https://myapp.net/ping
Or am I missing how it is supposed to work?
Updated: Even if I remove the HttpsRedirection part, and leave the only app.UseExceptionHandler - it is still not working as expected (all HTTP are still going thru and returns the same data as HTTPS).
As far as I know, if you used the UseHttpsRedirection, it will redirect all the http request to the HTTPS. That means your websites will not contain any http request which send to your server. There is no need to return 400 when using the http.
Your server will let all the http request redirect to https. I suggest you could try to use browser F12 develop tool to chck if this httpsredirection is working well or not.
It appeared that the best way is to migrate v2.1 to v3.1. Then also create own package to use TLS for Remote Connections, and disable the app.UseHttpsRedirection();

Asp.Net Core configure Identity authentication middleware properly

Requirement is that I have MVC & WebAPI in the project. MVC views will be delivered for initial
rendering like Login, base views of features (ex:- Users/Index, Dashboard/Index etc..) Web APIs will be used for other work within these views via AJAX with JWT.
I am using Asp.Net core Identity for user management related work running on .Net 5.0
I am confused with configuring multiple identity schemes and the proper configuration of authentication/authorization pipeline in conigureservices() & configure() in startup.
To configure multiple auth schemes I referred to https://stackoverflow.com/a/64887479/2058413 since it's done using same versions that I use. So my startup methods are below which is similar to the code in that thread.
public void ConfigureServices(IServiceCollection services)
{
string connectionString = Configuration.GetConnectionString("default");
services.AddDbContext<AppDBContext>(c => c.UseSqlServer(connectionString));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDBContext>();
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(x =>
{
x.LoginPath = "/Account/Login";
x.ExpireTimeSpan = TimeSpan.FromMinutes(10d);
x.AccessDeniedPath = "/Account/Register";
})
.AddJwtBearer(x =>
{
x.RequireHttpsMetadata = false;
x.SaveToken = true;
x.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey(Encoding.ASCII.GetBytes("123456")),
ValidateIssuer = false,
ValidateAudience = false
};
});
services.AddAuthorization(options =>
{
var defaultAuthorizationPolicyBuilder = new AuthorizationPolicyBuilder(CookieAuthenticationDefaults.AuthenticationScheme, JwtBearerDefaults.AuthenticationScheme);
defaultAuthorizationPolicyBuilder = defaultAuthorizationPolicyBuilder.RequireAuthenticatedUser();
options.DefaultPolicy = defaultAuthorizationPolicyBuilder.Build();
});
services.AddControllersWithViews();
}
My App configure method is below
public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
{
if (env.IsDevelopment())
{
app.UseDeveloperExceptionPage();
}
else
{
app.UseExceptionHandler("/Home/Error");
}
app.UseStaticFiles();
app.UseRouting();
app.UseAuthentication();
app.UseAuthorization();
app.UseEndpoints(endpoints =>
{
endpoints.MapControllerRoute(
name: "default",
pattern: "{controller=Home}/{action=Index}/{id?}");
});
}
Then the test method in controller(where user should get redirected to after authentication) is below
[Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)]
public IActionResult Index()
{
return View();
}
To my understanding the order of the pipeline configuration is correct. Now here are the problems I face.
As specified in .AddCookie(option=>) , user doesn't get redirected to login page. However, if I remove the JwtBearerDefaults.AuthenticationScheme from the services.AddAuthorization(…) it gets redirected to login page properly. Why is that?
So I remove JwtBearerDefaults.AuthenticationScheme; which takes me to login and after successful login I can see that HttpContext.User.Identity.IsAuthenticated is set to true. But it doesn't redirect to Home/Index. No errors thrown and in browser console [network tab] it shows a 302 and redirect back to login. Since I have added [Authorize(AuthenticationSchemes = CookieAuthenticationDefaults.AuthenticationScheme)] to Index method in HomeController, I tried removing the scheme and adding [Authorize] and tried again. Still it gets redirected to login page. This is another thing I am confused about.
So I removed everything related to dual scheme authentication and left the ConfigureService() with below code
string connectionString = Configuration.GetConnectionString("default");
services.AddDbContext<AppDBContext>(c => c.UseSqlServer(connectionString));
services.AddIdentity<IdentityUser, IdentityRole>()
.AddEntityFrameworkStores<AppDBContext>();
services.AddControllersWithViews();
Now everything works fine (redirection to login if not authenticated and also redirects to /Home/Index after authorization).
I went through below links as well about multi scheme authentication, but I am still confused with this pipeline configuration.
ASP.NET Core WebAPI Cookie + JWT Authentication
https://wildermuth.com/2017/08/19/Two-AuthorizationSchemes-in-ASP-NET-Core-2
https://mitchelsellers.com/blog/article/using-multiple-authentication-authorization-providers-in-asp-net-core
I need help only to this multi-scheme authentication pipeline configuration part.
Ok, after some research the main issue was;
I have mixed up old ways of registering services in StartUp (asp.net core 3.x). So had to clear up all those. Resources that helped me to do that;
Migrate from ASP.NET Core 3.1 to 5.0
ASP.NET Core Middleware
This cleaned up a lot of unnecessary code since in .Net 5 there were some shortcuts that could be used.
Order of service registrations. This may depend on what kind of services you are using, but in my case the order was something like below:
AddIdentity
setup Cookie & JWT authentication c)
My Multitenant stuff
AddCors
AddModules (will be option for some of you. I use it to load plugins dynamically)
Other stuff (However, even in these places the order might matter depending on
what you do)
The other thing was, I had to remove ConfigureApplicationCookie() since AddIdentity seems to be doing that. Also in AddAuthorization() earlier I had code to specify what are the default schemes i wanted to use (Cookie & JWT). But now I had to remove all that. Reason is Identity takes over the cookie scheme and when I specify below, JWTAuth takes over.
[Authorize(AuthenticationSchemes = JwtBearerDefaults.AuthenticationScheme)]
Generally setting up Startup properly seems to be tricky depending on different services you use. Order will matter

.NET Core Controller API call to Azure SQL Database works in localhost but not in deployed Azure Web App

Summary:
I have a .NET Core project that uses the React web app template for the front end. This app uses Entity Framework Core to connect to an Azure SQL Database. I used the Db-Scaffold command to generate my models (just one table at the moment), and created a controller to return this table. Locally, this works fine and the table (JSON) is returned at localhost/api/Users. However when I deploy the website to Azure (CD pipeline is VS 2017 - > GitHub -> DockerHub -> Azure Web App), navigating to mysite.azurewebsites.net/api/Users just renders the login page (React) of my app.
Attempts:
I have tried:
Adding a connection string as a shared value in Azure (named DefaultConnection)
Adding all the outbound IP's of the Azure Web App to the Azure SQL Whitelist
Running the following in the consoles of the web app
fetch('api/users')
This just returns:
Failed to load resource: the server responded with a status of 500 (Internal Server Error)
I have also tried changing database values and refreshing the local version to make sure it was not just a cached page and sure enough the changes were reflected locally.
I also set ASPNETCORE_ENVIRONMENT in the Web App settings in Azure to Production. Although when I go to the error message, page (through the console) I get this:
<h1 class="text-danger">Error.</h1>
<h2 class="text-danger">An error occurred while processing your request.</h2>
<p>
<strong>Request ID:</strong> <code>0HLK3RLI8HD9Q:00000001</code>
</p>
<h3>Development Mode</h3>
<p>
Swapping to the <strong>Development</strong> environment displays detailed information about the error that occurred.
</p>
<p>
<strong>The Development environment shouldn't be enabled for deployed applications.</strong>
It can result in displaying sensitive information from exceptions to end users.
For local debugging, enable the <strong>Development</strong> environment by setting the <strong>ASPNETCORE_ENVIRONMENT</strong> environment variable to <strong>Development</strong>
and restarting the app.
</p>
Code
UsersController.cs
[Route("api/[controller]")]
public class UsersController : Controller
{
private readonly AccrubalanceDbContext _context;
public UsersController(AccrubalanceDbContext context)
{
_context = context;
}
// GET: api/values
[HttpGet]
public async Task<IEnumerable<Users>> Get()
{
return await _context.Users.ToListAsync();
}
appsettings.json
{
"ConnectionStrings": {
"DefaultConnection":<MyConnectionStringGoesHere>
},
index.js (just in case React might be the routing problem)
const baseUrl = document.getElementsByTagName('base')
[0].getAttribute('href');
const rootElement = document.getElementById('root');
ReactDOM.render(
<BrowserRouter basename={baseUrl}>
<App />
</BrowserRouter>,
rootElement);
registerServiceWorker();
Startup.cs (could be potentially problem with HTTP routing in Prod?)
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.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
// In production, the React files will be served from this directory
services.AddSpaStaticFiles(configuration =>
{
configuration.RootPath = "ClientApp/build";
});
services.AddDbContext<AccrubalanceDbContext>(options =>
options.UseSqlServer(Configuration.GetConnectionString("DefaultConnection")));
}
// 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("/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.UseMvc(routes =>
{
routes.MapRoute(
name: "default",
template: "{controller}/{action=Index}/{id?}");
});
app.UseSpa(spa =>
{
spa.Options.SourcePath = "ClientApp";
if (env.IsDevelopment())
{
spa.UseReactDevelopmentServer(npmScript: "start");
}
});
}
Conclusion
In conclusion, I need this API call to work within the hosted Azure Web App like it does on my local machine. I know I am close since I got it to work locally, but I am missing something along the way to Azure. Any help or pointers you can provide would be great :)
I am still new to SO and took my time to do my best to format this correctly. I am open to constructive formatting critiques and suggestions to help me improve.
Edit:
As I mentioned before, I am using docker for CD/CI. So I ran my docker container locally and the api does not work there either. Docker throws this warning in the command window when I navigate to the apps home page.
warn: Microsoft.AspNetCore.HttpsPolicy.HttpsRedirectionMiddleware[3]
Failed to determine the https port for redirect.
Edit 1 Determination
I also found this article which points to react routing being an issue. I have looked in Kudo in my Azure app and I do not have a web.config. Could potentially try adding on but I do not have the regular Windows UI since my app is a Linux server.
The container build acts like the Azure App does, may not be an Azure issue. Still unsure why docker is acting differently than running in VS.
Solution:
There is obviously some problem with Docker. Since it was becoming more of a headache then a help, I removed it from the deployment pipeline and just followed the instructions here. Once I did this deployment method, all the API's worked. Only downside is I had to make a new app in Azure.

AspNet Core Identity - cookie not getting set in production

I have a .NET Core 2 web app and I want to use ASP.NET Identity to authenticate my users. On .NET Core 1.x, my code was working fine.
I migrated to .NET Core 2, and authentication works when running locally in Visual Studio. But when I deploy to a live environment, authentication stops working: the authentication cookie isn't being set in production.
My Startup.cs code looks like this:
public void ConfigureServices(IServiceCollection services)
{
services.AddIdentity<AppUser, RavenDB.IdentityRole>()
.AddDefaultTokenProviders();
...
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
...
app.UseAuthentication();
}
To sign in, my code looks like this:
public async Task<ActionResult> SignIn(...)
{
var user = ...; // Load the User from the database.
await this.signInManager.SignInAsync(user, isPersistent: true);
...
}
This code works locally: the ASP.NET Identity auth cookie is set. However, when I deploy this to production enviro in Azure, the cookie never gets set.
What am I missing?
I solved the problem. It boiled down to HTTPS: it appears that signInManager.SignInAsync(...) sets a cookie that is HTTPS-only. I was publishing to a non-HTTPS site initially for testing.
Once I published to an HTTPS site, the cookie started working again.
The reason it was working locally was that I was running in HTTPS locally.
Had same problem with Chrome 60+. Cookie did not want to set on HTTP site or even HTTPS and Cordova.
options.Cookie.SameSite = SameSiteMode.None;
https://github.com/aspnet/Docs/blob/master/aspnetcore/security/authentication/cookie.md
Changing from default value (Lax) to None fixed it for me.
I had similar issue. In the startup.cs file, I had to change
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.Always
});
to
app.UseCookiePolicy(new CookiePolicyOptions
{
Secure = CookieSecurePolicy.SameAsRequest
});
This allowed cookie authentication to work on both http and https.