AddAuthentication causes havoc with AddDefaultIdentity in Aspnet Core 3.1 - asp.net-core

I created a aspnet core website with local identity storage.
services.AddDefaultIdentity<IdentityUser>(options => options.SignIn.RequireConfirmedAccount = true)
.AddEntityFrameworkStores<ApplicationDbContext>();
Worked fine. Then I added social authentications according to the manual, and it works fine.
Next I added api authentication as in the Xamarin.Essentials documentation. It states that before social authentication I must do AddAuthentication.
services.AddAuthentication(o =>
{
o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
}).AddCookie();
When I add those lines, the working login doesn't work at all anymore. The pages looks like they work, but the user is never in the logged in state.
What happens to AddDefaultIdentity when I AddAuthentication?

You can only have one Default authentication scheme in you application. AddDefaultIdentity is adding a cookie authentication as default authentication scheme and it's what Identity is working with.
When you add a new authentication for APIs, you are overriding the DefaultScheme here o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme; which was what Identity works with.
If you want a new authentication scheme for APIs, you should just add the authentication without setting the default authentication and give it a name,
services.AddAuthentication()
.AddCookie("A_NEW_SCHEME_NAME", ... );
and add AuthorzieAttribute for this authentication scheme at your controllers/actions.
[Authorize(AuthenticationSchemes = "A_NEW_SCHEME_NAME")]

Related

Windows authentication in blazor server with custom AuthenticationStateProvider for JWT

I have Blazor server application what uses JWT tokens for authentication. For that there is implementation of custom AuthenticationStateProvder. This works just fine but now i need to add option to use Windows authentication to the application. I have added the required code to Program.cs:
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
builder.Services.AddAuthorization(options =>
{
// By default, all incoming requests will be authorized according to the default policy.
options.FallbackPolicy = options.DefaultPolicy;
});
But since i am using custom AuthenticationStateProvider its just doesnt work. Authentication state is always asked from the custom GetAuthenticationStateAsync. If i remove that the authentication state is properly filled with windows authentication information.
Is there a way to use both of these methods same time or is the only solution to have seperate project for windows authentication?

Cookie authorization works only for Scaffolded Identity Area

I'm working on a project that uses IdentityServer for authentication. The backend app consists of:
few Razor pages that are used for admin related tasks. User is supposed to be authorized with cookies.
mobile app. User is supposed to be authorized with JWT token.
As long as I'm trying to access scaffolded Pages from Identity/Pages/Account/* area (as seen on the screenshot) like Login.cshtml or Register.cshtml, the identity cookie is used correctly and I'm able to see User Claims and use [Authorize] attribute without any problems.
But after creating new set of Razor Pages inside Pages/Admin path, I noticed the cookie is not used even though it's provided in the Request. I think this happens because by default, IdentityServerJwt scheme is used for authorization.
I managed to get this working by specyfing following attribute for my newly created pages:
[Authorize(AuthenticationSchemes = "Identity.Application")]
The cookie is then correctly used to authorize the user but this is not ideal because I'm not able to read user's claims on pages that doesn't necessarily require user authentication as these users no longer can access it because of Authorize attribute.
What can I do in order to force my pages to use cookie authorization without breaking JWT authorization for my API?
The configuration of a backend app is as follows:
public void Configure(IApplicationBuilder app, IApiVersionDescriptionProvider descriptionProvider, IWebHostEnvironment env)
{
...
app.UseAuthentication();
app.UseIdentityServer();
app.UseAuthorization();
}
public void ConfigureServices(IServiceCollection services)
{
services
.AddDefaultIdentity<ApplicationUser>()
.AddRoles<ApplicationRole>()
.AddEntityFrameworkStores<ApplicationDbContext>();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddIdentityServer()
.AddApiAuthorization<ApplicationUser, ApplicationDbContext>()
.AddProfileService<ProfileService>();
services.AddTransient<IIdentityService, IdentityService>();
services.AddAuthentication()
.AddIdentityServerJwt();
return services;
}
Personally, I think you should put IdentityServer on its own service instance, and keep ASP.NET Identity and your client applications separately. Because otherwise it will be very complex to understand and troubleshoot. When you put it all in the same service, it will be a big mess. :-)
For example there will be many cookies involved, IdentityServer issues its own cookies and your client/ASP.NET Identity issues its own cookie.

Register authentication schemes based on tenant in asp.net core 3.1

Currently, I have created an Identity server 4 web application with external login providers with default client id and secrets.
But my goal is to register the authentication providers like Azure, Google, Facebook based on tenant.
I have used SaasKit multi-tenancy assembly, here I have tried app.usepertenant() middleware. But UseGoogleAuthentication() method is obsolete, so i could not achieve multi-tenant authentication using this usepertenant middleware.
Current code,
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddMicrosoftAccount(option =>
{
option.ClientId = "clientid";
option.ClientSecret = "clientsecret";
option.SaveTokens = true;
});
Expected code is like below,
var authentication = services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme);
if (tenant.hasMicrosoft)
{
authentication.AddMicrosoftAccount(option =>
{
option.ClientId = "clientid";
option.ClientSecret = "clientsecret";
option.SaveTokens = true;
});
}
if (tenant.hasGoogle)
{
authentication.AddGoogle(option =>
{
option.ClientId = "clientid";
option.ClientSecret = "clientsecret";
option.SaveTokens = true;
});
}
authentication.AddCookie( options =>
{
options.SlidingExpiration = true;
options.ExpireTimeSpan = new TimeSpan(7, 0, 0, 0);
});
See the official MS docs, Authentication providers per tenant
ASP.NET Core framework does not have a built-in solution for multi-tenant authentication. While it's certainly possible for customers to write one, using the built-in features, we recommend customers to look into Orchard Core for this purpose.
Since authentication needs to be configured during DI registration, you will have to setup all external login providers in general during registration of authentication.
During that step, you need to add all schemes. A scheme has a fixed client-id/secret, so you need to bootstrap your IdentityServer with all external login provider credentials, that you support for all of your clients. The scheme name needs to be unique.
As an example, tenant A might have a scheme "A_microsoft", tenant B might have a scheme "B_microsoft", etc.
You can then refer to those authentication schemes, when calling methods in IdentityServer. SignIn, Challenge, SignOut etc.
Be aware, that this will require bootstrapping IdentityServer a complete set of tenants. Depending on your scenario, if tenants are updated regularly, it will also require regular restarts of the IdentityServer to be aware of new authentication schemes.
If that is a problem, you can probably somehow augment the registered authentication schemes during the runtime of IdentityServer, but it's not going to be easy. It might require larger custom implementations of the authentication middleware coming with AspNetCore.
Do you mean you want to add support for multiple authenticate provider? This Document already specified how to add multiple auth provider in config service. No need to use app.UseXXX anymore to config the pipeline by yourself

Using ASP.Net Core 2.2 Auth Cookies in old .Net Web Forms (aspx) site for SSO

TLDR: How can I get an Asp.Net Core 2.2 encrypted auth cookie understood by a legacy Web Forms app?
The DR:
I find myself in a situation where I'm trying to get a legacy Web forms (aspx) site to use an auth cookie set in the browser by a new and shiny .NET Core 2.2 MVC site. The Web Forms app in question is written in Visual Basic, the .Net Core app in C#. Everything is hosted by IIS/IIS Express (from Visual Studio).
The cookie appears to be set fine, and to make things easy (for now), all things are hosted on the same machine (My Windows 10 Professional Edition Dell XPS). The encryption/machine key for cookie encryption is in a well defined location on disk. The .Net Core app (Let's call it "Auth Portal" for now) is basically a front for AWS Cognito.
In Auth Portal, the cookie is configured like so in Startup.cs:
services.Configure<CookiePolicyOptions>(options =>
{
options.CheckConsentNeeded = context => true;
options.MinimumSameSitePolicy = SameSiteMode.None;
});
var keyDir = Configuration["AuthSettings:CookieEncryptionKeyDir"];
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keyDir))
.SetApplicationName(Configuration["ApplicationName"]);
services.AddCognitoIdentity();
services.AddSingleton(typeof(IAmazonCognitoIdentityProvider),
_ => new AmazonCognitoIdentityProviderClient(RegionEndpoint.APSoutheast2)); // Oh no you know I'm using Sydney!
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie();
services
.ConfigureApplicationCookie(options =>
{
var parseSucceeded = int.TryParse(Configuration["AuthSettings:CookieExpiryHours"],
out var cookieExpiry);
if (!parseSucceeded) _log.LogError("Failed to parse from configuration auth cookie expiry value");
parseSucceeded = bool.TryParse(Configuration["AuthSettings:SSLCookieOnly"], out var secureCookieOnly);
var securePolicy = secureCookieOnly ? CookieSecurePolicy.Always : CookieSecurePolicy.SameAsRequest;
if (!parseSucceeded) _log.LogError("Failed to parse from configuration cookie security policy");
options.Cookie.HttpOnly = true;
options.ExpireTimeSpan = TimeSpan.FromHours(cookieExpiry);
options.SlidingExpiration = true;
options.LoginPath = Configuration["AuthSettings:LoginUrl"];
options.LogoutPath = Configuration["AuthSettings:LogoutUrl"];
options.Cookie.Domain = Configuration["AuthSettings:TrustedDomains"];
options.Cookie.Name = Configuration["AuthSettings:CookieName"];
options.Cookie.SecurePolicy = securePolicy;
});
services
.Configure<SecurityStampValidatorOptions>(o =>
{
o.ValidationInterval = TimeSpan.FromHours(1);
});
services.Configure<ForwardedHeadersOptions>(options =>
{
options.ForwardLimit = 2;
options.ForwardedForHeaderName = "X-Forwarded-For-Auth-Portal";
});
In the Web Forms app, I'm replacing the existing default sql auth. This is where the online resources seem to dry up. I can make a custom membership provider which I have done easily. But it does not (understandably) understand the existing cookie out of the box, even when I set the cookie name in the forms auth settings
<authentication mode="Forms">
<forms timeout="60" name="my.sso.cookie" requireSSL="false" path="/" />
</authentication>
<membership defaultProvider="CognitoMembershipProvider" userIsOnlineTimeWindow="60">
<providers>
<clear />
<add name="CognitoMembershipProvider" type="TheOldWebFormsApp.Authentication.CognitoMembershipProvider" applicationName="MyWebForms" enablePasswordRetrieval="true" enablePasswordReset="true" requiresQuestionAndAnswer="true" requiresUniqueEmail="false" passwordFormat="Clear" minRequiredNonalphanumericCharacters="0" minRequiredPasswordLength="3" maxInvalidPasswordAttempts="8" />
</providers>
</membership>
As I have found it will just overwrite the existing cookie which I'm assuming is because it deems and un-understandable cookie as a non-existent one.
I guess this a very long winded way of asking how can i get this old Web Forms app to accept and decrypt the Auth Portal cookie?
Cheers~!
Unfortunately, this is impossible. ASP.NET Core uses an entirely different methodology to encrypt cookies than ASP.NET does. The latter is based on a "machine key", but ASP.NET Core use data protection providers and a key ring. The two are fundamentally incompatible, so even if you can "share" the cookie, such that each one sees it, one will not be able to decrypt the cookie set by the other.
It is possible to successfully share cookies with ASP.NET MVC 5, but that's because it makes use of OWIN, and the data protection stuff is OWIN compatible. As such, even though MVC 5 also uses machine keys natively, it can be made to use data protection instead, and then it can share the same data protection key store with an ASP.NET Core site. However, Web Forms does not support OWIN, so the same cannot be achieved there. In short, this a complete no-go.
An alternative, perhaps, is relying on a centralized identity provider, such as Identity Server, Auth0, Azure AD, etc. With such a solution, it's not necessary to share cookies as each site authorizes independently with the provider, but the same user account can be used. The actual login happens at the identity provider, then each site authorized with that identity provider is given the identity from the identity provider and can then set their own cookies or whatever with that.

ASP.net MVC Core and IdentityServer 4: Setting defaultScheme in AddAuthentication

I am looking at the code below. The AddAuthentication added defaultScheme with "Cookies". Does this mean the current mvc application only accept Cookie authentication but not Access Token by default.
services.AddOptions();
//services.Configure(Configuration);
services.AddDistributedMemoryCache(); // Adds a default in-memory implementation of IDistributedCache
services.AddSession();
JwtSecurityTokenHandler.DefaultInboundClaimTypeMap.Clear();
services.AddAuthentication(options =>
{
options.DefaultScheme = "Cookies";
options.DefaultChallengeScheme = "oidc";
})
.AddCookie("Cookies")
.AddOpenIdConnect("oidc", options =>
{
Currently, I wanted to have access one single page with my mobile application which authenticated with it's access token which logged in from the app itself.
I wonder how do I request the webpage inside my webview by using AccessToken instead of Cookie.
There is something called Authorize attribute with difference acceptable scheme I can pass in. I wonder is this the way to set it up.
[Authorize(AuthenticationSchemes =
JwtBearerDefaults.AuthenticationScheme)]
That is for Accesstoken only, if I need both I add cookie as well
options.DefaultScheme = "Cookies";
This means that the authentication scheme, if not specified otherwise, will be "Cookies".
options.DefaultChallengeScheme = "oidc";
This means that the default challenge authentication scheme, if not specified otherwise, will be "oidc".
This is how the OIDC and Cookie authentication schemes will usually work with each other: The application will attempt to authenticate the user using the existing cookie. If that fails (because there’s no cookie), then an authentication challenge will be made using the OIDC scheme. This will then relay the authentication to the external provider, and when that succeeds, the OIDC scheme will sign the user in using the Cookie authentication scheme. This creates the cookie, so on the next request, the cookie authentication scheme will be able to authenticate the user (without having to ask the OIDC scheme again).
If you want other authentication schemes to work, then you will have to add those too. AddAuthentication(…).AddCookie(…).AddOpenIdConnect(…) will just set this chain up. If you also want a JWT bearer authentication, you need to configure that as well.
But just because you .AddJwtBearer(…) that does not mean that anything about the normal flow will change: The Cookie scheme will still be the default, and the OIDC scheme will still be the default challenge. As I said above: Unless you specify otherwise.
So when you want to authorize the user using JWT Bearer authentication, you will need to trigger that explicitly. As you have noticed yourself, this can be done using the Authorize attribute. But in order for that to work, you will still have to set up the JWT Bearer authentication properly. But then it can work in parallel to the already set up Cookie/OIDC setup.