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
Related
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.
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?
I am trying to set custom cookies during the cookie and openIdConnect authentication/authorization in asp.net core 3.1 but not having any success. I hope someone can point me in the right direction. Here is my middleware setup:
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
})
.AddCookie(option => {
option.Events = new CookieAuthenticationEvents {
//Tried the OnSignedIn() to set the custom cookie but no avail
}
})
.AddOpenIdConnect("Is4", options =>
{
options.SignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.Authority = "identityserver4.url";
options.RequireHttpsMetadata = false;
options.ClientId = "ClientId";
options.ClientSecret = "ClientSecret";
options.ResponseType = OpenIdConnectResponseType.Code;
options.UsePkce = true;
options.ResponseMode = "form_post";
options.CallbackPath = "/signin-oidc";
options.GetClaimsFromUserInfoEndpoint = true;
options.Scope.Add("openid");
options.Scope.Add("profile");
options.Scope.Add("offline_access");
options.Scope.Add("customer-api");
options.SaveTokens = true;
options.Events = new OpenIdConnectEvents
{
OnUserInformationReceived = (context) =>
{
var accessTokenSplit = context.ProtocolMessage.AccessToken.Split(".");
context.Response.Cookies.Append(
key: "HeaderPayload",
value: $"{accessTokenSplit[0]}.{accessTokenSplit[1]}",
options: new CookieOptions
{
Domain = "localhost:5001",
SameSite = SameSiteMode.Strict,
Expires = DateTimeOffset.UtcNow.AddMinutes(30),
Secure = true,
HttpOnly = false
}
);
context.Response.Cookies.Append(
key: "Signature",
value: $"{accessTokenSplit[2]}",
options: new CookieOptions
{
Domain = "localhost:5001",
SameSite = SameSiteMode.Strict,
Expires = DateTimeOffset.UtcNow.AddMinutes(30),
Secure = true,
HttpOnly = true
}
);
return Task.CompletedTask;
}
});
The HeaderPayload and Signature are my custom cookies I want the browser to have at the end of the authentication workflow. Instead I only see .AspnetCore.CookiesC1 and .AspnetCore.CookiesC2. I guess the cookie authentication middleware is not aware of my custom cookie I set in one of the AddCookie() or AddopenIdConnect() events. I can however set those cookies in a custom middleware with context.Response.Cookies.Append(...) to show up in the browser but I won't have access to the JWT access token there so would rather handle it in the authentication pipeline. Any thoughts or suggestions? Thanks
I found the problem and it has nothing to do with the Cookie or OpenIdConnect middleware. The Secure=true cookie option was preventing the browser from creating the cookie.
I'm using the VueJs asp.net core SPA template. Asp.net core is proxying all the calls for SPA to the Vuejs webpack dev server. The dev server is hosting the SPA on http which is NOT a secure connection but I'm configuring the cookie to be used only with https (Secure=true). Therefore, I didn't see the cookie in the browser.
Now I'm trying to configure the webpack server to use a self-signed cert so asp.net core wouldn't complain that it can't setup the ssl connection because of the failing cert validation.
this is how the logout is done within the api:
[HttpPost]
[ValidateAntiForgeryToken]
public async Task Logout(LogoutRequest logoutContext)
{
if (User?.Identity.IsAuthenticated == true)
{
var prop = new AuthenticationProperties
{
RedirectUri = logoutContext.PostLogoutRedirectUri
};
await HttpContext.SignOutAsync(IdentityConstants.ApplicationScheme, prop);
await _events.RaiseAsync(new UserLogoutSuccessEvent(User.GetSubjectId(), User.GetDisplayName()));
}
}
in the client app I have the following configuration:
services.AddAuthentication(options =>
{
options.DefaultScheme = IdentityConstants.ApplicationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(IdentityConstants.ApplicationScheme, options => { options.AccessDeniedPath = "/Home/AccessDenied"; })
.AddOpenIdConnect("oidc", options =>
{
options.Authority = model.Authority;
options.ClientId = model.ClientId;
options.SignInScheme = IdentityConstants.ApplicationScheme;
options.ResponseType = "id_token token";
options.Scope.Add("openid profile");
});
services.AddAuthorization(options =>
{
options.AddPolicy("User", p => p.RequireAuthenticatedUser().RequireRole("User"));
});
when I log out the cookie is deleted, once I navigate back to the client I am not prompted to log back in and the cookie has returned. is there something I am doing wrong?
You aren't deleting the single sign on cookie generated in your Identity Provider. Your client is redirecting to your Identity Provider and redirecting again to your client with a new authentication since your Identity Provider still maintains the Cookie. Capture the requests in Fiddler to see the automatic redirections.
You should signout from oidc too in order to delete this Cookie:
await HttpContext.SignOutAsync("oidc");
If your want to signout automatically (without the Logout view from your Identity Provider) you can set false ShowLogoutPrompt and set true AutomaticRedirectAfterSignOut in the AccountOptions.cs file.
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);
})