How does cookie authentication work with Identity in .NET 5? - c#

I am developing an application, used the Identity "Individual Account" preset with .Net 5.
I manually added authentication, as I need to manually set some cookie options, sto that the app can share cookies with some other one.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => {[some settings]})
This alone makes my login not working, even upon removing any options inside the AddCookie method: after this, the application recognizes the credentials where correct, but does not recognize me as a signed in user (basically, user is never authenticated).
The settings for my cookie are nothing crazy, but here they are anyways:
options.Cookie.Name = ".TestCookie"; // tried different ones
options.Cookie.SameSite = Microsoft.AspNetCore.Http.SameSiteMode.Lax;
options.Cookie.Path = "/";
options.Cookie.HttpOnly = true;
options.Cookie.IsEssential = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.LoginPath = "/Identity/Account/Login";
options.CookieManager = new ChunkingCookieManager();
options.TicketDataFormat = new SecureDataFormat<AuthenticationTicket>(new TicketSerializer(),
DataProtectionProvider.Create(new DirectoryInfo(#"C:/someDirectory"),
(builder) => { builder.SetApplicationName("myapp"); })
.CreateProtector(
"Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
"Cookies.Application",
"v2"
));
Anyone knows how to help me?

Related

Why is the Authentication Cookie not working after page refresh

I've created simple cookie based authentication. It's working when it cames to login, and accessing page as it should. However after every page refresh performed by th user i'm rerouted to login page... cookies remain, and iam able to inspect them even after refresh.
//startup.cs-ConfigureServices
services
.AddAuthentication(o =>
{
o.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
o.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(JwtBearerDefaults.AuthenticationScheme, o =>
{
o.SaveToken = true;
o.Events = new JwtBearerEvents()
{
OnMessageReceived = context =>
{
context.Token = context.Request.Cookies["access_token"];
return Task.CompletedTask;
}
};
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "access_token";
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Cookie.HttpOnly = true;
options.Cookie.SecurePolicy = CookieSecurePolicy.None;
options.Cookie.SameSite = SameSiteMode.Lax;
options.LoginPath = "/signin";
options.LogoutPath = "/signout";
// optional
});
var multiSchemePolicy = new AuthorizationPolicyBuilder(
CookieAuthenticationDefaults.AuthenticationScheme,
JwtBearerDefaults.AuthenticationScheme)
.RequireAuthenticatedUser()
.Build();
//startup.cs - Configure
var cookiePolicyOptions = new CookiePolicyOptions
{
MinimumSameSitePolicy = SameSiteMode.None,
HttpOnly = Microsoft.AspNetCore.CookiePolicy.HttpOnlyPolicy.Always,
Secure = CookieSecurePolicy.None,
};
app.UseCookiePolicy(cookiePolicyOptions);
app.UseAuthentication();
It seems you want to use the multiple authentication for both the cookie and JWT, but I don't find how you enable the multiple authentication, I suggest you could try to add below codes into the startup.cs and try again.
services.AddAuthorization(o => o.DefaultPolicy = multiSchemePolicy);
When you set samesite=none, you must use HTTPS, otherwise those cookies will be ignored. Using HTTP and cookies in the browser today will often give you various problems.
Also, when you create a samesite=none cookie, you also need to add the secure attribute to it.
How can you find out why the browser rejected them?
In Chrome:
Open the Browser Developer Tools (F12)
Click on the network tab and reload the page
Click on the Cookies request
Select the Cookies tab
Then hover your mouse over the (I) to see the reasoning by the browser

Blazor Server Side and Azure B2C Login using invite, how to authenticate with returned token?

I have followed a few different tutorials on how to get Signup by invite working and I am very close to getting it working in blazor server side but I am having issues with the final returned token.
I have 2 authentications setup, one which is the default Microsoft Identity and my custom one which is used for sign ups via an email link.
Everything seems to work until the final step.
When you click the link, it takes you to Azure signup pages asking for Name, email etc and then it returns back to my site with the returned "id_token".
When this happens I get the following error.
InvalidOperationException: The authentication handler registered for scheme 'OpenIdConnect' is 'OpenIdConnectHandler' which cannot be used for SignInAsync. The registered sign-in schemes are: Cookies.
I set breakpoints in the OpenIdConnectEvents on event TicketReceived and I can see that the TicketReceivedContext object has a valid ClaimsPrinciple with correct claims and IsAuthenticated is true.
My return page never gets hit because of the error.
Any ideas on how to fix this?
Edit
My startup registration for the 2 authentications.
builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(options =>{
builder.Configuration.Bind("AzureAd", options);
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.Events = new CustomOpenIdConnectEvents();
options.DataProtectionProvider = protector;
}, subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);
and
string invite_policy = builder.Configuration.GetSection("AzureAdB2C")["SignUpSignInPolicyId"]; builder.Services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddOpenIdConnect(invite_policy, GetOpenIdSignUpOptions(invite_policy, builder.Configuration));
Action<OpenIdConnectOptions> GetOpenIdSignUpOptions(string policy, Microsoft.Extensions.Configuration.ConfigurationManager Configuration)
=> options =>
{
builder.Configuration.Bind("AzureAdB2C", options);
options.ResponseType = OpenIdConnectResponseType.IdToken;
string B2CDomain = Configuration.GetSection("AzureAdB2C")["B2CDomain"];
string Domain = Configuration.GetSection("AzureAdB2C")["Domain"];
options.MetadataAddress = $"https://{B2CDomain}/{Domain}/{policy}/v2.0/.well-known/openid-configuration";
options.ResponseMode = OpenIdConnectResponseMode.FormPost;
options.CallbackPath = "/LoginRedirect";
options.Events = new CustomOpenIdConnectEvents();
options.DataProtectionProvider = protector;
};
Update:
So thanks to Wolfspirit's answer I am now closer to solving the issue.
My start up registration has now changed to this.
string invite_policy = builder.Configuration.GetSection("AzureAdB2C")["SignUpSignInPolicyId"];
builder.Services.AddAuthentication(options => {
options.DefaultScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(invite_policy, GetOpenIdSignUpOptions(invite_policy, builder.Configuration))
.AddMicrosoftIdentityWebApp(options =>{
builder.Configuration.Bind("AzureAd", options);
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.Events = new CustomOpenIdConnectEvents();
options.DataProtectionProvider = protector;
}, subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true);
My issue now is that User.Identity.Name is returning a null if you login through the signup method. If you login the normal default way then everything is fine. I checked the claims and they are correct so not sure why Name is no being populated.
According to the error you got, you've set "OpenIdConnect" as the SignIn scheme inside the "AddAuthentication" call. It would be helpful to know how you've set up your Program.cs/Startup.cs but it should look similar to this for example:
https://github.com/onelogin/openid-connect-dotnet-core-sample/blob/master/Startup.cs#L32
You need to use both schemes cause one manages the "where to store the authentication after login" while the other manages the "how to log in" way. If you set both to OpenIdConnect then you can log in but asp.net core will not know how to actually "sign" you in by storing the login result into a cookie.
ok so I have figured out the answer, with credit to Wolfspirit for pointing me in the right direction.
string invite_policy = builder.Configuration.GetSection("AzureAdB2C")["SignUpSignInPolicyId"];
builder.Services.AddAuthentication(options => {
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddOpenIdConnect(invite_policy, GetOpenIdSignUpOptions(invite_policy, builder.Configuration))
.AddMicrosoftIdentityWebApp(options =>{
builder.Configuration.Bind("AzureAd", options);
options.ResponseType = OpenIdConnectResponseType.IdToken;
options.Events = new CustomOpenIdConnectEvents();
options.DataProtectionProvider = protector;
// Thanks to Nan Yu for the folowing to fix the null name after login
//https://stackoverflow.com/questions/54444747/user-identity-name-is-null-after-federated-azure-ad-login-with-aspnetcore-2-2
options.TokenValidationParameters = new TokenValidationParameters() { NameClaimType = "name" };
}, subscribeToOpenIdConnectMiddlewareDiagnosticsEvents: true); // don't need this, for debugging.

.Net Core 3.1 OpenIdConnect with AWS Cognito

I've been trying to get AWS Incognito working with ASP.NET Core 3.1 all day and am missing sime essential piece.
The code below is close because:
It Will
Let the user go through the built-in ASP.NET Identity user registration and login pages
That will result in records for the user in both the AspNetUsers table and the AspNetUserLogins table.
Appear to log the user in when they pass through the ASP.Net Identity UI ExternalLogin.cshml page's Callback method:
But in reality ASP.Net Identity is unaware of this user
In addition it will result in an infinite loop if you try to access an authorized page, bouncing between the requested page, the AWS Cognito server, and the "signin-oidc" built-in route.
The line I suspect the most is:
options.SignInScheme = IdentityConstants.ExternalScheme;
As the code comments mention, commentng out this line will break the built-in registration, but solve the infiniteloop problem (as well as let the openid claims cookie stay populated).
It's pretty clear that it's close, but there's something not quite linked up to bring it all together.
var awsCognitoRegion = "us-east-1";
var awsCognitoPoolId = "******";
var metaDataAddress = $"https://cognito-idp.{awsCognitoRegion}.amazonaws.com/{awsCognitoPoolId}/.well-known/openid-configuration";
//var awsCognitoResponseType = "code";
var awsCognitoMetaAddress = metaDataAddress;
var awsCognitoClientId = "*******************";
var awsCognitoSecret = "**********************";
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie()
.AddOpenIdConnect(options =>
{
//fixes "Error loading external login information" error on Identity Login page / callback
//after external login...but breaks identity/cookie/claims?
//
//Also Causes infinite redirect problem via signin-oidc
options.SignInScheme = IdentityConstants.ExternalScheme;
//show all claims since MS filters some out...
options.ClaimActions.Clear();
//Tell .Net Core identity where to find the "name"
//options.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/emailaddress";
options.TokenValidationParameters.NameClaimType = "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
options.ResponseType = "code";
options.MetadataAddress = metaDataAddress;
options.ClientId = awsCognitoClientId;
options.ClientSecret = awsCognitoSecret;
options.SaveTokens = true;
options.GetClaimsFromUserInfoEndpoint = true;
//I think all I need here is email
options.Scope.Add("email");
options.Scope.Add("profile");
options.Scope.Add("openid");
//options.Scope.Add("aws.cognito.signin.user.admin");
});
As these authentication things always are, it ended up being one property missing/set wrong.
In this case it was that the SigninManager was checking if the Identity had principal.Identities.Any(i => i.AuthenticationType == IdentityConstants.ApplicationScheme); (https://github.com/aspnet/Identity/blob/master/src/Identity/SignInManager.cs). This is why even though the Identity would have IsAuthentictated = true, the built-in ASP.Net Identity pages were broken, because that value was set to AuthenticationTypes.Federation.
After more digging around I discovered that in the options of the .AddOpenIdConnect method there is a way to set that:
options.TokenValidationParameters.AuthenticationType = IdentityConstants.ApplicationScheme;
That finally made ASP.Net Identity aware that I was logged in (through OpenId).

How to disable External Logins in Identity Core?

I am in the process of integrating a simplefied authentication process into a asp.net core 2.1 application, where users are logging in via the UI by default, but there is also the possibility to aquire a token and call some secured api endpoints to retrieve some data needed for reporting.
The issue I am facing is, that with the default configuration everything works, but adding the token config throws some weird errors.
If I do not add AddCookie("Identity.External"), call to the onGet method at /Identity/Account/Login throws the exception
InvalidOperationException: No sign-out authentication handler is registered for the scheme 'Identity.External'. The registered sign-out schemes are: Identity.Application. Did you forget to call AddAuthentication().AddCookies("Identity.External",...)?
If I do not specify options.DefaultScheme = "Identity.Application"; the user is not successfully signed in.
If I do not add .AddCookie("Identity.External") and .AddCookie("Identity.TwoFactorUserId") the logout process throws the same exception as above.
For the login process, this is simply rectified by removing the line await HttpContext.SignOutAsync(IdentityConstants.ExternalScheme);. If I do not use external schemes I do not need to sign out of them, right?
This brings me to my problem: How can I disable external logins and multi factor authentication in Identity Core, so I do not have to add those cookies in the first place? Furthermore, why do I have to specifiy a cookie named "Identity.Application", which is not the case in the default configuration? I'm pretty sure this is just another issue of me not thoroughly understanding the problem at hand, so I am grateful for any clarification on this.
This is my Identity config from the Startup.cs I have also scaffolded out the complete Identity UI with a custom IdentityUser class.
var jwtAppSettingOptions = Configuration.GetSection(nameof(JwtIssuerOptions));
services.Configure<JwtIssuerOptions>(options =>
{
options.Issuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.Audience = jwtAppSettingOptions[nameof(JwtIssuerOptions.Audience)];
options.SigningCredentials = new SigningCredentials(_signingKey, SecurityAlgorithms.HmacSha256);
});
var tokenValidationParameters = new TokenValidationParameters
{
/*...*/
};
services.AddAuthentication(options =>
{
options.DefaultScheme = "Identity.Application";
})
//.AddCookie("Identity.External")
//.AddCookie("Identity.TwoFactorUserId")
.AddCookie("Identity.Application", opt =>
{
opt.SlidingExpiration = true;
})
.AddJwtBearer(options =>
{
options.ClaimsIssuer = jwtAppSettingOptions[nameof(JwtIssuerOptions.Issuer)];
options.TokenValidationParameters = tokenValidationParameters;
options.SaveToken = true;
});
var builder = services.AddIdentityCore<AppUser>(o =>
{
//removed
});
builder = new IdentityBuilder(builder.UserType, typeof(IdentityRole), builder.Services);
builder.AddEntityFrameworkStores<ApplicationDbContext>().AddDefaultTokenProviders();

Cookies with a SameSite policy enforced are blocked in iOS 12 for SSO flows involving cross-origin requests

Summary: Third party login breaks in iOS / OS 12!
We have a common login that works across multiple websites. This is working fine in Firefox, Chrome and Safari on Windows, macOS and iOS. But with iOS 12 and macOS 12, it seems cookies are no longer working from auth0 login window to our login API.
It has stopped working not just in Safari, but on iOS 12 also in Chrome and Firefox (it still works in Chrome on Mac OS 12). I suspect this has to do with Intelligent Tracking Prevention 2.0, but I'm struggling to find many technical details.
Our login flow is as follows:
User clicks login which sets window.location.href to login url on the universal (different) login domain.
This calls ChallengeAsync which sends user to auth0 domain for login.
User is then sent back to the login domain, but at this point the cookies from auth0 and session cookies set in the controller are missing.
I use the following in startup:
services.AddAuthentication(options => {
options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(options =>
{
options.Cookie.Path = "/";
options.SlidingExpiration = false;
})
.AddOpenIdConnect("Auth0", options => {
// Set the authority to your Auth0 domain
options.Authority = $"https://{Configuration["Auth0:Domain"]}";
// Configure the Auth0 Client ID and Client Secret
options.ClientId = Configuration["Auth0:ClientId"];
options.ClientSecret = Configuration["Auth0:ClientSecret"];
// Set response type to code
options.ResponseType = "code";
// Configure the scope
options.Scope.Clear();
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("email");
options.Scope.Add("offline_access");
options.CallbackPath = new PathString("/signin-auth0");
options.ClaimsIssuer = "Auth0";
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnRemoteFailure = context => {
<not relevant error redirects>
},
OnRedirectToIdentityProvider = context =>
{
context.ProtocolMessage.SetParameter("audience", $"{ Configuration["Auth0:ApiIdentifier"]}");
return Task.FromResult(0);
},
OnRedirectToIdentityProviderForSignOut = (context) =>
{
<not relevant logout handling>
}
};
});
In the login controller I have a login action which just sets a session value and calls ChallengeAsync to open the Auth0 login:
await HttpContext.ChallengeAsync("Auth0", new AuthenticationProperties() { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddMinutes(Global.MAX_LOGIN_DURATION_MINUTES), RedirectUri = returnUri });
The "returnUri" parameter is the full path back to this same controller, but different action. It is when this action is hit that both cookies from the auth0 login (i.e. https://ourcompany.eu.auth0.com) and session data I set in the login action are missing in iOS 12.
Can I do it in some other way that will work on iOS 12? All help appreciated.
I have finally figured it out. The cookie set by default uses SameSiteMode.Lax. This has worked fine everywhere up until iOS 12, where it now needs to be set to SameSiteMode.None.
This is the modification I use that has got it working again:
.AddCookie(options =>
{
options.Cookie.Path = "/";
options.SlidingExpiration = false;
options.Cookie.SameSite = SameSiteMode.None;
options.Cookie.Expiration = TimeSpan.FromMinutes(Global.MAX_LOGIN_DURATION_MINUTES);
})

Categories

Resources