ASP Net Identity - Adding a second authentication cookie - c#

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
});

Related

Dynamic configuration of Azure B2C with user flows in a .NET 6 API

I have a .NET 6 API with authorization via a B2C tenant. This B2C tenant has two user flows:
B2C_1_CustomerSignUpAndSignIn
B2C_1_EmployeeSignUpAndSignIn
We also have a manager application which has to connect to the same API, which makes 3 schemes in total.
In my API I have configured the following in the Program.cs
builder
.Services
.AddAuthentication()
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cApplicationBased", options => builder.Configuration.Bind("AzureB2cApplicationBasedJwtBearerOptions", options))
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cCustomerSignUpAndSignIn", options => builder.Configuration.Bind("AzureB2cCustomerSignUpAndSignInJwtBearerOptions", options))
.AddScheme<JwtBearerOptions, CustomJwtHandler>("AzureB2cEmployeeSignUpAndSignIn", options => builder.Configuration.Bind("AzureB2cEmployeeSignUpAndSignInJwtBearerOptions", options))
.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>()); // Without this JwtBearerOptions.ConfigurationManager is null.
builder
.Services
.AddAuthorization(options =>
{
options.DefaultPolicy = new AuthorizationPolicyBuilder()
.RequireAuthenticatedUser()
.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn")
.Build();
options.AddPolicy(
"Employee",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("Employee"));
});
options.AddPolicy(
"RegisteredUser",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("RegisteredUser"));
});
options.AddPolicy(
"VerifiedCustomer",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("VerifiedCustomer"));
});
options.AddPolicy(
"CustomerOrVerifiedCustomer",
policy =>
{
// All authentication schemes must be added, otherwise an empty ClaimsPrincipal is given to the API methods.
policy.AddAuthenticationSchemes("AzureB2cApplicationBased", "AzureB2cCustomerSignUpAndSignIn", "AzureB2cEmployeeSignUpAndSignIn");
policy.Requirements.Add(new RoleRequirement("CustomerOrVerifiedCustomer"));
});
});
This code has been here for ages, it works, but I've never been happy with it.
I'm using a custom JwtBearerHandler called CustomJwtHandler which makes sure a token is only verified against the user flow it was created for. If I didn't then the code tried to authenticate against all the user flows, which slows down the authenticated requests by a great deal when you have a lot of user flows. We used to have more, so the difference is less present now, but still noticable. Is there a better way?
Currently if I add or remove a user flow, I have to manually update the Program.cs to remove or add this user flow to the AddAuthentication and AddAuthorization methods. If deletion is forgotten, then the authorized endpoints throw an error after the API is reloaded (read: after the scheme metadata cache is cleared), which is what happened today on development. Therefore, I would prefer this to be dynamic. I tried calling the Graph API (see https://learn.microsoft.com/en-us/graph/api/identitycontainer-list-b2cuserflows?view=graph-rest-beta&tabs=powershell) using the client credentials flow after granting the API IdentityUserFlow.Read.All, IdentityUserFlow.ReadWrite.All rights to build this dynamically, but calling this Graph API endpoint doesn't seem to work as I can only use the default scope (note: calling other endpoints does work). Is there a better way?
I don't like that the authorization metadata cache is only loaded the first time anyone is making an authorized request. I want to preload this. Can this be done?
I noticed that, since upgrading to .NET 6, when I remove the code adding all the authentication schemes to the different policies, I still retrieve a ClaimsPrincipal, so I'm assuming something changed here and it is no longer necessary.

How can I add more than one AddAuthentication to builder.services?

I'm new to asp core, I have implement the following to login AD identity,
builder.Services.AddAuthentication(NegotiateDefaults.AuthenticationScheme).AddNegotiate();
and also the following to login with user name / password and add cookie,
builder.Services.AddAuthentication("Auth1").AddCookie("Auth1", options => { options.Cookie.Name = "Auth1"; });
but how can I add both authentication to allow either case to login?
I have both tested working properly but cannot add both to builder.services.
Please recommend web page reference or approach to implement this.

.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();
...
}

Different type of authentication for the same controller

I have WEB API CORE 3.0 back-end application. Its controllers are protected with Azure AD.
For this I Use microsoft identity web library.
In the source code I configure it like this:
public void ConfigureServices(IServiceCollection services)
{
Trace.TraceInformation("Configuring services");
services.AddProtectedWebApi(Configuration, subscribeToJwtBearerMiddlewareDiagnosticsEvents: true)
.AddProtectedApiCallsWebApis(Configuration)
.AddInMemoryTokenCaches();
...
And to protect controller I use [Authorize].
Everything works perfect.
Now I want to add the second way to authorize users (along with Azure AD).
I want that users be able to login either with Azure AD or, say, JWT.
Is it possible to implement it for the same controller?
Is it possible to change the existing authorizing mechanism to allow non-AzureAD users to use the controller.
Sounds like you're trying to make the [Authorize] to allow multiple authentication scheme at the same time. If that's the case, you should firstly register those authentication scheme with AddAuthentication().AddMyScheme1().AddMyScheme2()...:
services.AddAuthentication()
.AddAzureAD(options => Configuration.Bind("AzureAd", options));
.AddJwtBearer(otps=>{
otps.TokenValidationParameters = new TokenValidationParameters{ ...};
});
And then change the default Authorization Policy to authenticate those authentication schemes at the same time. For example, if you want to allow Identity/JwtBearer/AzureAd at the same time, you could do it in following way
services.AddAuthorization(opts =>{
opts.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
IdentityConstants.ApplicationScheme // ASP.NET Core Identity Authentication
,JwtBearerDefaults.AuthenticationScheme // JwtBearer Authentication
// ,"AzureAD" // AzureAd Authentication
)
.RequireAuthenticatedUser()
.Build();
});
Or if you want to allow only specific user/Role further, feel free to custom it by :
opts.DefaultPolicy = new AuthorizationPolicyBuilder()
.AddAuthenticationSchemes(
IdentityConstants.ApplicationScheme // ASP.NET Core Identity Authentication
,JwtBearerDefaults.AuthenticationScheme // JwtBearer Authentication
// ,"AzureAD" // AzureAd Authentication
)
.RequireAuthenticatedUser()
.RequireRole(...)
.RequireAssertion(ctx =>{
...
return true_or_false;
})
.Build();

Categories