Server-side Blazor users logged out when signalR circuit ungracefully broken - c#

I have an asp.net core 3.1 server-side Blazor web site that uses the Microsoft Identity framework for authentication. It appears that whenever a user's SignalR circuit is ungracefully broken they lose their authentication state and have to log in again.
In these scenarios the .AspNetCore.Identity.Application cookie will still be present and unexpired. The problem doesn't occur if the circuit terminates gracefully (e.g. closing the browser); it's only in situations like a vpn disconnecting.
Here is the authentication section of Startup:
services.AddDbContext<IdentityDataContext>(options => options.UseSqlServer(Configuration.GetConnectionString("IntegrationDatabase")));
services.AddDefaultIdentity<IdentityUser>(options =>
{
options.Password.RequiredLength = 10;
options.SignIn.RequireConfirmedAccount = false;
options.SignIn.RequireConfirmedEmail = false;
})
.AddRoles<IdentityRole>()
.AddEntityFrameworkStores<IdentityDataContext>();
services.AddAuthorization(options =>
{
options.AddPolicy(CanViewDataPolicy.Name, CanViewDataPolicy.Policy);
options.AddPolicy(RequireAdminPolicy.Name, RequireAdminPolicy.Policy);
});
Does anyone know why this occurs or how to prevent it? I would like users to remain authenticated until they log out or their cookie expires.

Related

.NET 6 MVC5 ConfigureApplicationCookie for custom application cookies

I have a .NET 5 MVC5 Application.
It use MVC Areas to run 3 applications.
Each one has a main cookie and depending on wheter user is enabled or not it check for additional auth cookies/claims.
On Chrome I receive this error:
A cookie was not sent to an insecure origin from a secure context. Because this cookie would have been sent across schemes on the same site, it was not sent. This behavior enhances the SameSite attribute’s protection of user data from request forgery by network attackers.
Resolve this issue by migrating your site (as defined by the eTLD+1) entirely to HTTPS. It is also recommended to mark the cookie with the Secure attribute if that is not already the case.
On MSDN I read that you need to work with SameSite:
Working with SameSite
In this article I also read:
SameSite and Identity
ASP.NET Core Identity is largely unaffected by SameSite cookies except for advanced scenarios like IFrames or OpenIdConnect integration.
When using Identity, do not add any cookie providers or call
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme),
Identity takes care of that.
I think this is my case so I'm quite safe I guess.
Nonetheless I wanted to do it because... Knowledge.
I use a login + cookie+claim auth:
services.AddMvc(config =>
{
// Requiring authenticated users on the site globally
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
config.Filters.Add(new AuthorizeFilter(policy));
})
// Authentication
var cookiesConfig = this.Configuration.GetSection("CookiesOptions")
.Get<CookiesOptions>();
services.AddAuthentication(
CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
//Various options
});
services.AddAuthorization(options =>
{
options.AddPolicy("Require.SomePolicy1.User", policy =>
policy.RequireClaim("AAAAA_ENABLED_USER", "true")
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme)
);
options.AddPolicy("Require.SomePolicy2", policy =>
policy.RequireClaim("AAAAA_CAPA_ENABLED_USER", "true")
.AddAuthenticationSchemes(CookieAuthenticationDefaults.AuthenticationScheme)
);
And I wanted to try the SameSite thing with what I've found Here in this article
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = SameSiteMode.Strict;
options.Cookie.SecurePolicy = CookieSecurePolicy.Always;
options.Cookie.HttpOnly = true;
}
);
Now as you can see down there only the aspnet cookie is set to strict and the others aren't.
How can I set every cookie to strict and remove those chrome warnings?
Microsoft suggestion is to add the request manually to every reponse like this:
// Add the cookie to the response cookie collection
Response.Cookies.Append("MyCookie", "cookieValue", cookieOptions);
but it's quite horrible, even if I do it using an ActionFilter.
Isn't there a way to set it up for every cookie?
I see that
options.Cookie..
Has also a CookieBuilder property. Maybe that's the one to try?
Thanks

How to authenticate user in development environment in an ASP.NET Core 3.1 MVC app?

Whenever I implement authentication and authorization with Azure AD in an ASP.NET Core application, in development environments I require a valid and configured account to sign in and get to the protected pages.
If the environment is development, I'd like to be able to run the app as if a user was signed in by default. For example, any User.Identity.IsAuthenticated value would be true, [Authorize] attributes would be ignored, any usages of IAuthorizationService would react as if the user was fully authorized. How could this be done? Or it would be equally good if when I press to sign in with Microsoft it just automatically "authenticates" on any computer.
I looked through some SO solutions, but they all seem to disable authentication. I can't disable it because if [Authorize] attribute is bypassed but User.Identity.IsAuthenticated returns false, code in my controllers and razor views would break. I want this so that when Selenium runs automatic tests for the UI it does not have to deal with signing into Microsoft, and that anyone who wants to run the app locally isn't required to have an account.
When signing in, I use the action from MicrosoftIdentity at /MicrosoftIdentity/Account/SignIn.
If it helps, here's my ConfigureServices method in Startup.cs:
public void ConfigureServices(IServiceCollection services)
{
...
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
services.Configure<CookieAuthenticationOptions>(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.ExpireTimeSpan = TimeSpan.FromMinutes(Configuration.GetValue<double>("AzureAd:TimeoutInMinutes"));
options.SlidingExpiration = true;
});
RegisterAuthorizationPolicies(services);
services.Configure(OpenIdConnectDefaults.AuthenticationScheme, (Action<OpenIdConnectOptions>)(options =>
{
options.Events.OnTicketReceived = async context =>
{
// I run some code here that determines whether or not to allow the user to sign in.
return;
};
}));
services.AddControllersWithViews()
.AddMicrosoftIdentityUI();
...
}

ASP Net Identity - Adding a second authentication cookie

I'm trying to use a second cookie so that other applications in a subdomain can validate, if a user is logged in in my application. However i don't trust the other applications thats why I'm planning to use two Authentication Cookies. One for my own Application (IdentityCookie) and one so that the other Apps can access the login status (SubCookie).
I'm using ASP.NET Identity for cookie creation and account management.
services.AddDefaultIdentity<ApplicationUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>().AddSignInManager<AuthSignInManager<ApplicationUser>>();
services.ConfigureApplicationCookie(options =>
{
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Strict;
options.CookieManager = new CookieManager();
});
The custom CookieManager is responsible for creating and deleting both Cookies at the same time.
My issue now is that I can't find a way to validate the cookies. I attempted to use a custom Authenticationhandler for the validation of the subCookie, but if I add the following code the IdentityCookie validation stops working:
services.AddAuthentication("CookieBearer").AddScheme<BasicAuthenticationOptions, BasicAuthenticationHandler>("CookieBearer", o => {});
It seemes like after adding this line only the Custom Handler is used and the one provided by Identity is ignored. Is there any way to add multiple AuthenticationHandlers with ASP.NET Identity? I want to use the custom AuthenticationHandler just as a fallback option. So that if authentication by Identity fails the custom AuthenticationHandler is used.
I know I can chain Authentication Schemes/Methods like this, but I'm not sure how to do it in combination with Identity.
services
.AddAuthentication()
.AddJwtBearer("Custom1", options =>
{
//Configure here
})
.AddJwtBearer("Custom2", options =>
{
// Configure here
});

.Net Core 3.1 - Google Auth Cookie Not Persisting

I recently upgraded my ASP.Net Core MVC web app to .Net Core 3.1, and ever since, the Google External login cookie hasn't persisted properly as it did before the upgrade. The code itself didn't change at all, the app was just upgraded from .Net Core 2.0 to .Net Core 3.1.
The login flow works (user clicks login button, Google login screen appears, user logs in with Google Credentials/selects existing Google user and is sent back to the web app), but at random times (sometimes 30-60 seconds after login), the user's session randomly ends and the user is redirected back to the login screen. I've tested this extensively on localhost and the issue does not occur, but happens frequently on the hosting service I'm using (MochaHost).
Has anyone else had this issue with .Net Core 3.1?
The ConfigureServices method used at startup is below for reference.
public void ConfigureServices(IServiceCollection services)
{
services.AddLogging();
services.AddResponseCompression(options =>
{
options.Providers.Add<GzipCompressionProvider>();
options.EnableForHttps = true;
});
services.Configure<MvcOptions>(options =>
{
options.Filters.Add(new RequireHttpsAttribute());
});
services.AddDbContext<ApplicationDbContext>(options =>
options.UseMySql(Configuration["ConnectionStrings:MySQL"]));
services.AddIdentity<ApplicationUser, IdentityRole>()
.AddEntityFrameworkStores<ApplicationDbContext>()
.AddUserStore<UserStore>()
.AddDefaultTokenProviders();
services.AddAuthentication().AddGoogle(options =>
{
options.ClientId = Configuration["Authentication:Google:ClientId"];
options.ClientSecret = Configuration["Authentication:Google:ClientSecret"];
options.CallbackPath = new PathString("/signin-google");
});
services.ConfigureApplicationCookie(options =>
{
options.AccessDeniedPath = "/error/401";
options.Cookie.Name = "MyApp";
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(180);
options.LoginPath = "/login";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.SlidingExpiration = true;
});
services.AddMvc(option => option.EnableEndpointRouting = false);
services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
services.AddSingleton<IFileProvider>(new PhysicalFileProvider(Configuration.GetValue<string>("RootFilePath")));
var serviceAccountFilePath = GetGoogleServiceAccountCredentialPath();
var googleCredential = GoogleCredential.FromFile(serviceAccountFilePath);
services.AddSingleton(StorageClient.Create(googleCredential));
}
In my case, the issue was that Data Protection key storage was not available via user profile or HKLM registry after the .Net Core upgrade. I'm not sure if this is related to having to switch to 64-bit app pool mode after the upgrade to .Net Core 3.1 or not, but the bottom line is data protection keys were only being stored in memory. After shifting from server to server via the load balancer, the user keys were lost, causing a redirect back to the login page.
Sample Log entries:
2020-11-20 09:00:21.162 -08:00 [INF] Starting web host
2020-11-20 09:00:21.572 -08:00 [WRN] Using an in-memory repository. Keys will not be persisted to storage.
2020-11-20 09:00:21.574 -08:00 [WRN] Neither user profile nor HKLM registry available. Using an ephemeral key repository. Protected data will be unavailable when application exits.
I had to add the below code to use a folder on the hosting service to store key files so the user's session would persist:
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(Configuration.GetValue<string>("KeyStorePath")));

Microsoft authentication automatically signing in account. Causing "Correlation failed" error when user manually selects account

We've been leveraging Cookie authentication within our .NET Core 2.2 application & logging users in after verifying their identity via Microsoft external 3rd party provider. Just recently, we've observed an error in this workflow which states:
Microsoft.AspNetCore.Authentication.MicrosoftAccount.MicrosoftAccountHandler:Information: Error from RemoteAuthentication: Correlation failed.."
We've narrowed it down to the scenario where if a user is already logged in to their Microsoft account (only one account logged in/active)... and if they then log in to our website they are automatically signed in without having to select their account name or any other interaction. What's more, if the user DOES click on their account name, this fires off another sign in request & I believe is resulting in the "Correlation failed.." error due to the request/response cookies being in conflict.
I am able to reproduce the issue from localhost, though our actual hosting provider is within Azure (App Services).
I've already explored some of the solutions/approaches mentioned within the following Microsoft docs which mention configuration for load balancers and proxy servers (Forwarding Headers)... as well as enforcing HTTPS in ASP.NET Core:
https://learn.microsoft.com/en-us/aspnet/core/host-and-deploy/proxy-load-balancer?view=aspnetcore-2.1
https://learn.microsoft.com/en-us/aspnet/core/security/enforcing-ssl?view=aspnetcore-2.1&tabs=visual-studio
These do not appear to address the root of the issue which I understand to be the automatic sign-on/selection of the user account when the security Challenge is made to the Microsoft provider.
Here is a snippet from our Startup.cs service configuration for the 3rd party authentication config.
// Authentication is added via Cookie
services.ConfigureApplicationCookie(options => options.LoginPath = "/Login");
services.AddAuthentication(opts =>
{
opts.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
opts.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(opts =>
{
opts.Cookie.SecurePolicy = CookieSecurePolicy.Always;
opts.LoginPath = "/auth/login";
opts.LogoutPath = "/auth/logout";
opts.ClaimsIssuer = "<ISSUER_HERE>"; // *** redacted for privacy
})
.AddMicrosoftAccount(options =>
{
options.ClientId = Configuration["Authentication:ApplicationId"];
options.ClientSecret = Configuration["Authentication:Password"];
options.Events.OnRemoteFailure = ctx =>
{
// React to the error here. See the notes below.
ctx.Response.Redirect("/error?FailureMessage=" + UrlEncoder.Default.Encode(ctx.Failure.Message));
ctx.HandleResponse();
return Task.FromResult(0);
};
});
Ideally, the "automatic sign-on" behavior is prevented so the user must select their account from the Microsoft account selection prompt (even if they've previously signed in).
For now, we're redirecting the user to an error page, where we can clear all cookies and have the user re-attempt the login. This poses a problem if they continually select their user account during the login process & both requests are conflicting.
Any insight would be greatly appreciated!
Alrighty then, I got it.
It seems a bit hacky, but hey, it works.
.AddMicrosoftAccount(options =>
{
// Your configuration here
options.Events.OnRedirectToAuthorizationEndpoint = context =>
{
context.HttpContext.Response.Redirect(context.RedirectUri + "&prompt=select_account");
return Task.FromResult(0);
};
})

Categories