What would be the impact of changing or setting the SignInScheme when already deployed in production - asp.net-core

Our authentication system is currently seeing many "Correlation failed" errors for some users trying to connect via Google Sign In. We're using Identity Server 4 / ASP.NET identity with .NET Core 2.2. Obviously, we can't reproduce.
Though we tried to understand every aspect of what we were implementing and deploying, the whole authentication flow and the many configuration possibilities of ASP.NET Identity and IS4 still hold mystery to us. We've tried to pinpoint the true cause of those "correlation failed" errors, but cannot be sure. We do have data protection in place that prevents issues with load balancing and multiple instances. We're almost certain it's not related to HTTP vs HTTPS. But foremost, it looks like this error is more frequent lately, though we don't see what change could have triggered this.
One thing we realized, and we're not sure of the impact, is that the Google authentication is configured without an explicit SignInScheme:
services.AddAuthentication()
.AddGoogle(options =>
{
var section = Configuration.GetSection("ExternalLogin:Google");
options.ClientId = section.GetValue<string>("ClientId");
options.ClientSecret = section.GetValue<string>("ClientSecret");
});
Documentation about IS4 seems to indicate we should have set this in the first place:
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
On the other hand, articles like this one say that this scheme gets replaced by ASP.NET Identity.
Because ASP.NET Identity also changes the default authentication scheme, any instances of IdentityServerConstants.DefaultCookieAuthenticationScheme and IdentityServerConstants.ExternalCookieAuthenticationScheme, should be changed to IdentityConstants.Application and IdentityConstants.ExternalScheme respectively.
This just adds to our overall confusion, and we're not sure if it means changes to the external login callback as well.
Can we add that simple SignInScheme line to the Google options without impacting other users which still can connect with Google Sign In without any issue? Does it have a chance to fix the correlation errors we see? Otherwise, where should we look?

Related

The oauth state was missing or invalid - Unknown location

I am using both Google and Facebook authentication mechanisms in my .NET 7.0 app and they both work fine locally. When deployed in my DEV environment I am getting exceptions when coming back from Google/Facebook. So the challenge works correctly, I am able to authenticate at their side but the callback fails, saying:
Exception: The oauth state was missing or invalid.
Unknown location
I am being redirected to https://my.website.com/signin-google with the state in a querystring parameter. This is the expected behavior as I did not configure an explicit callback path and by default it's set to signin-google and signin-facebook. But somehow it seems like the RemoteAuthenticationHandler does not think this matches the callback path so it's not handling the request? Or would the issue be in the OAuthHandler.HandleRemoteAuthenticateAsync? Maybe when unprotecting the state data? But then why would this work locally?
My setup:
services.AddAuthentication()
.AddGoogle("google", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.ClientId = AppSettings.Instance.GoogleClientId;
options.ClientSecret = AppSettings.Instance.GoogleClientSecret;
})
.AddFacebook("facebook", options =>
{
options.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
options.AppId = AppSettings.Instance.FacebookAppId;
options.AppSecret = AppSettings.Instance.FacebookAppSecret;
});
Edit: Could this possibly be linked to the fact I am using 2 servers in my DEV environment and that it uses something machine-related to unprotect the state so it does not work when I land on the other machine?
It turns out this was due to the fact I am now deploying the application on multiple distributed servers, accessed via a load balancer.
This article here helped me to understand how data protection works in ASP.NET Core.
By default key-rotation system is used and the keys are generated by the machine and persisted to %LOCALAPPDATA%\ASP.NET\DataProtection-Keys. So of course this does not work anymore when you scale up your applications.
So instead I decided to store the data protection keys in a shared database so all machines will use the same key:
Add reference to Nuget Microsoft.AspNetCore.DataProtection.EntityFrameworkCore.
Create a DbContext that inherits from IDataProtectionKeyContext
Register your DbContext
Setup your data protection to use the DbContext as:
builder.Services.AddDataProtection()
.PersistKeysToDbContext<DataProtectionKeyContext>();

Suspected bug in Microsoft Identity Platform with ASP.NET Core 3.1 Razor Pages

I am developing an application to be hosted in the Azure App Services environment which consists of a front-end Web App, a back-end Web API and a SQL Database (using Azure SQL). The front-end Web App is a Razor Pages app. We are trying to use the Microsoft Identity Platform (via Microsoft.Identity.Web and Microsoft.Identity.Web.UI libraries) to acquire an access token for the API when needed.
It works perfectly well the first time, but once a token has been acquired and cached - if the application is restarted it fails with this error:
IDW10502: An MsalUiRequiredException was thrown due to a challenge for the user. See https://aka.ms/ms-id-web/ca_incremental-consent.
No account or login hint was passed to the AcquireTokenSilent call.
Startup configuration is (I've tried various variants of this):
public void ConfigureServices(IServiceCollection services)
{
services.AddDistributedMemoryCache();
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.Unspecified;
options.HandleSameSiteCookieCompatibility();
});
services.AddOptions();
services.AddMicrosoftIdentityWebAppAuthentication(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(new string[] { Configuration["Api:Scopes"] })
.AddInMemoryTokenCaches();
services.AddControllersWithViews(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
}).AddMicrosoftIdentityUI();
services.AddRazorPages().AddRazorRuntimeCompilation().AddMvcOptions(options =>
{
var policy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.Build();
options.Filters.Add(new AuthorizeFilter(policy));
});
services.AddMvc();
//Other stuff...
}
I have tried for many days trying to find either a resolution workaround for this. I can catch the
error, but there is no action we can take programmatically that seems to clear the problem (the ITokenAcquisition interface does not offer the option to force an interactive login).
I have found that it is ONLY a problem in a Razor Pages application - a controller-based MVC Web App with almost identical startup code does not exhibit the problem.
I have also found that, by creating a controller-based test MVC Web App and configuring it with the same client id, tenant id etc. as the app we're having problems with, then starting it up (within the Visual Studio development environment) as soon as the main app gets the problem, I can clear the error condition reliably every time. However this is obviously not a viable long-term solution.
I have searched for this problem on every major technical forum and seen a number of similar sorts of issues raised, but none provides a solution to this precise problem.
To replicate:
Create an ASP.NET Core 3.1 Web API.
Create an ASP.NET Core 3.1 Razor Pages Web App that calls the API.
Register both with Azure Active Directory and configure the App to request a token to access the API (as per various MS documents).
Run - if everything is set up correctly the login screen will appear and all will work correctly.
Stop the Web App, wait a couple of minutes and re-start. The error above will now appear.
I have raised a Microsoft support request for it - has anybody else come across this and found a solution for it?
I have finally got to the bottom of this, largely thanks to this: https://github.com/Azure-Samples/active-directory-aspnetcore-webapp-openidconnect-v2/issues/216#issuecomment-560150172
To summarise - for anyone else having this issue:
On the first invocation of the web app you are not signed in, and so get redirected to the Microsoft Identity Platform login, which logs you in and issues an access token.
The access token is stored in the In-Memory token cache through the callback.
All then works as expected because the token is in the cache.
When you stop, and then re-start the web app within a reasonably short time, it uses the authentication cookies to pick up the still-current login, and so it does not access the Identity Platform and you do NOT get an access token.
When you ask for a token the cache is empty - so it throws the MsalUiRequiredException.
What isn't really made clear in any of the documentation is that this is supposed to happen - and that exception is picked up by the "AuthorizeForScopes" attribute but only if you allow the exception to fall all the way through and don't try to handle it.
The other issue is that in a Razor Pages app the normal AuthorizeForScopes attribute has to go above the model class definition for every page - and if you miss one it may trigger the above problem.
The solution proposed by "jasonshave" in the linked article solves that problem by replacing the attribute with a filter - so it will apply to all pages.
Maybe I'm a bit old-school, but the idea of using an unhandled exception as part of a planned program control flow doesn't sit right with me - at the very least it should be made clear that that's the intention. Anyway - problem now solved.

Any luck in using AddMicrosoftIdentityWebApp in combination with IdentityServer4?

I'm trying to get Microsoft configured as an external login provider in Identityserver4.
I succeeded by following identity server's documentation with using AddMicrosoftAccount:
services.AddAuthentication().AddMicrosoftAccount(microsoftOptions =>
{
microsoftOptions.SignInScheme = IdentityServerConstants.ExternalCookieAuthenticationScheme;
microsoftOptions.ClientId = configuration["MicrosoftLoginProvider:ClientId"];
microsoftOptions.ClientSecret = configuration["MicrosoftLoginProvider:ClientSecret"];
});
However, I didn't have luck with getting single sign-out to work. The documentation is in line with Microsoft's documentation at https://learn.microsoft.com/en-us/aspnet/core/security/authentication/social/microsoft-logins?view=aspnetcore-5.0.
However, if you follow the instructions to create an app in Microsoft Developer Portal (portal.azure.com), the sample code on that portal suggests a different way. The sample application that the portal generated for me (WebApp-OpenIDConnect-DotNet) is using AddMicrosoftIdentityWebApp:
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
Since this application is working out-of-the-box including single sign-out, Iam wondering if this is the way I have to continue.
To my surprise, however, I can't find any doc/blogs about how to integrate this approach in IdentityServer4. I almost got it to work myself, but there are a few weird issues.
Can someone clarify if using AddMicrosoftIdentityWebApp is the way to go to add Microsoft as an external identity provider to Identityserver4?
Has someone succeeded in getting AddMicrosoftIdentityWebApp to work with IdentityServer4?
THanks for your help!
I figured how to get it to work.
Actually, only two things I had to do.
First, I had to remove OpenIdConnectDefaults.AuthenticationScheme in the call to AddAuthentication in the example code that Microsoft generated. So the code becomes:
services.AddAuthentication()
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"));
Then, in the code that reads the external identity from a temporary cookie, I had to use CookieAuthenticationDefaults.AuthenticationScheme. So, that code now reads as follows:
var authenticationResult = await HttpContext.AuthenticateAsync(CookieAuthenticationDefaults.AuthenticationScheme);
That was all.
After digging around I found this statement here:
Microsoft.Identity.Web is a simpler way to use Azure AD in ASP.NET Core web apps and web APIs.
It doesn't replace ASP.NET Identity in any way, it doesn't replace AddJwtBearer or AddCookie or any of the lower level primitives, but it does use and configure them correctly for Azure AD.
It doesn't work with non-Azure identity providers. It replaces AzureAD.UI and AzureADB2C.UI which are obsolete in .NET 5.0
So, the conclusion is that Microsoft.Identity.Web does not work outside Azure AD and hence not with IdentityServer.
If you do get it to work then let me know!

identity server multiple issues after deployment

My current setup is like this. The entire project was built using the official docs here - https://identityserver4.readthedocs.io/en/latest/
API Server
Auth Server with local login, google login and github login
Console based c# client
JS based client
MVC based client.
(all of it, as described in the official docs)
Locally, all of them work beautifully. Able to login, access api endpoints, logout, redirect, the whole thing works smooth.
I have deployed all 5 of them to five different azure web apps. They all have the standard xyz.azurewebsites.net domains ready to use. Now, I have run into some problems.
the console C# client is able to talk to the deployed auth server, collect token using a local account on the auth server and make calls to the deployed API server. Based on this, I assume that both the api server and the auth server working hand in hand, as they should.
Problem #1 - the JS client keeps saying
'The login is blocked because of CORS Missing Allow Origin '
Problem #2 - the MVC client loads the auth server, and then the auth server gives me this error.
Sorry, there was an error : unauthorized_client
Request Id: 80005c0f-0000-eb00-b63f-84710c7967bb
Note : I have set the CORS policy on the auth server, both these clients, under client definition as follows. I am not too concerned about keeping the auth server open, so dont mind if any and every domain can call the auth server.
AllowedCorsOrigins = { "*.*" },
Also Note : I have set the URLS in the code before deployment. all loclahost:port number lines have been replaced correctly with the corresponding now published URLs.
So, what am I missing out here?
Update 1
I was able to solve the CORS issue. Have posted a answer here on another question.
Not able to enable CORS for identity server 4 in asp.net core
Update 2
So, now, both the JS client and the MVC client, are giving identical errors.
Sorry, there was an error : unauthorized_client
Request Id: 80005c0f-0000-eb00-b63f-84710c7967bb
Update 3
I have opened an issue which has log details.
https://github.com/IdentityServer/IdentityServer4/issues/4691
I am not sure if this counts as an answer, but posting for my own question, as it might might help others. Also, this is only a guess at this point.
I found out that the redirects were permanently stored in the database I used with EF migrations. That mean, local in memory redirects were being overwritten anyway by the database stored migrations. I believe this is the issue.
I also realized that the console app is working fine for it does not depend on redirect URLs where as the JS and MVC based clients dont work because they do depend on redirect URLs.
At this point, the best thing to do and for you (if you used EF migrations to store your auth server configuration) on database would be start over and switch to in memory only. Alternatively, you can try and update the database to suit your deployment requirements.
Ultimately, I believe, unless it is absolutely necessary, keep the auth server config (like redirects and CORS settings) in memory as they dont take up much value and are rarely changed.

ASP.Net core antiforgery denying form submission from Iframe

I have an asp.net core application. One of the forms of the application is embedded inside a iframe in a differnt application, running on a different. In my configuration I have supressed the same origin X-Frame header so I can submit the form the iframe.
services.AddAntiforgery(options =>
{
options.SuppressXFrameOptionsHeader = true;
});
However when I submit the form via the iframe I get a bad request error, although I can see that the CSRF token is sent properly. If I remove the
[ValidateAntiForgeryToken]
attribute from the controller action I can submit the form via the iframe. What am I doing wrong?
I found an answer to this
https://stackoverflow.com/a/52709829/9931213
You need to add
options.Cookie.SameSite = SameSiteMode.None;
to your AddAntiforgery options.
I believe you're confusing CSRF attack prevention (using ValidateAntiForgeryToken attribute) with clickjacking attack prevention (using X-Frame-Options HTTP header).
Please read those articles first and try to understand what they are and how they work. And most importantly, what risks are you taking when disabling these protections. Most of the times people are just too eager to see their web app up and running, so they disable most of protections like these, but later on they fail to get back to these issues and fix them properly, which usually ends up with that web app being vulnerable to these basic attacks, data being stolen, leaked, abused, etc.
It can be frustrating to slow down the development in order to first read the stuff and try to understand it before the continuation of the development, but, it usually pays off every time and you always learn something new in the process, becoming a better developer.