Unable to expire a .NetCore cookie - asp.net-core

I'm using .NetCore and Cookie authentication. Account management (login, logout, etc) is all managed through an API as opposed to using the Identity UI. My understanding is that the server can't actually send anything to the client to actually remove the cookie, rather, I would need to "expire" the cookie on the server and return it to the client.
So here is my setup
Startup.cs
services.AddAuthentication("mycookie")
.AddCookie("mycookie", options => {
options.Cookie.HttpOnly = true;
options.SlidingExpiration = true;
options.ExpireTimeSpan = TimeSpan.FromMinutes(30);
options.Cookie.Expiration = TimeSpan.FromMinutes(30);
});
In my ApiController I have the following:
AccountController.cs
[HttpGet("signout")]
public async Task<IActionResult> SignOut()
{
var temp = new AuthenticationProperties()
{
ExpiresUtc = DateTime.Now.AddDays(-1)
}
await HttpContext.SignOutAsync("mycookie",temp);
return Ok();
}
However, my cookie never seems to expire when I call signout. I noticed in the browser dev console, the cookie says Expires on: Session. I would've expected to see a date/time there.
How do I get rid of the cookie or expire it when signout is called?

use the Clear-Site-Data response header. Among other things, it allows you to clear cookies on the client's browser.
In my example below, I'm clearing the user's cache and cookies for my domain.
public IActionResult Logout()
{
this.HttpContext.Response.Headers.Add("Clear-Site-Data", new StringValues(new string[] { "\"cache\"", "\"cookies\"" }));
return Ok();
}
First be sure to check browser support for this header.
If your browser does not support Clear-Site-Data, then you should be able to expire them like so:
this.Response.Cookies.Append("cookieName", "deleted", new CookieOptions()
{
Expires = DateTimeOffset.MinValue
});
When this logout response returns to the browser, I'm able to see the cookies disappear (using Chrome 73.0.3683.86, Developer Tools/Storage/Cookies/{myDomain} )

Related

How to invalidate the session after log out in IdentityServer4

We have website with SSO using IdentitySrver4. We recently tested our site for security and we found one vulnerability which is as follow,
A session token for the application remained valid (and could be used
to authenticate requests to the application) even after the logout
function had been invoked in the associated session. This indicated
that the session termination mechanism was not fully effective and
increased the possibility of unauthorised access to the application.
It should be noted that the tokens did have an effective time out
after a period of time. The logout function terminated the associated
session client-side (by removing the session cookie from the user’s
browser) but the session remained valid server-side. Requests which
were made after the logout function had been used, but which provided
the original session cookie, continued to be successful.
Following are code snippets,
IdentityServer
StartUp ConfigureServices:-
services.AddIdentityServer(.......
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, opt=> {
opt.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));
//This has time limit of 30 minutes
})
.AddOpenIdConnect("oidc", opts =>
{.......
The Login code is as follow,
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, cp);
LogOut method in IdentityServer:-
[HttpGet]
public async Task<IActionResult> Logout(string clientId, string returnUrl, string culture)
{
var clientsList = new List<Client>();
// delete authentication cookie
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var cookiesToBeDeleted = Request.Cookies.Keys;
foreach (string cookie in cookiesToBeDeleted)
{
Response.Cookies.Delete(cookie);
}
var logoutURL = _configuration["DefaultLogoutRedirectUrl"];
if (Uri.IsWellFormedUriString(returnUrl, UriKind.Absolute))
{
//TODO: validate that the return url belongs to the client who has initiated the logout request
//if the url validation fails then we should return to a pre-determined url that is mentioned in the config
logoutURL = returnUrl;
}
var vm = new LoggedOutViewModel()
{
PostLogoutRedirectUri = logoutURL,
SignOutUrls = _clients.Value
.Where(client => !string.IsNullOrWhiteSpace(client.FrontChannelLogoutUri))
.Select(client => client.FrontChannelLogoutUri),
ClientName = clientId,
AutomaticRedirectAfterSignOut = true
};
//If there is no return url then display a local logged out page
return View("LoggedOut", vm);
}
[HttpGet]
public IActionResult LoggedOut(string returnUrl)
{
var vm = new LoggedOutViewModel()
{
PostLogoutRedirectUri = returnUrl,
SignOutUrls = null,
AutomaticRedirectAfterSignOut = true
};
return View(vm);
}
We have used FrontChannel logouts,Here all the client's "FrontChannelLogoutUri" are rendered in "IFrame" in logout page of the Identity Server.
Client Code (MVC App):-
StartUp ConfigureServices:-
services.AddAuthentication(options =>
{
options.DefaultScheme =
CookieAuthenticationDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = "oidc";
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, ck =>
{
ck.Cookie.Name = "ClientCookie";
ck.ExpireTimeSpan = TimeSpan.FromMinutes(Convert.ToInt32(Configuration["CookieTimeOut"]));
//This also has value of 30 minutes.
})
.AddOpenIdConnect("oidc", opts =>
{.......
LogOut function in Client App :-
public async Task<IActionResult> Logout(string path)
{
var logoutUrl = //This is IdentityServer LogOut Method URL
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
var prop = new Microsoft.AspNetCore.Authentication.AuthenticationProperties()
{
RedirectUri = logoutUrl
};
await HttpContext.SignOutAsync("oidc", prop);
return Redirect(logoutUrl);
}
The FrontChannel method for Client App:-
[AllowAnonymous]
public async Task<IActionResult> ForcedSignout()
{
var cookiesToBeDeleted = Request.Cookies.Keys;
foreach (string cookie in cookiesToBeDeleted)
{
Response.Cookies.Delete(cookie);
}
await HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme);
return View();
}
The logout workflow is as follow:-
When logout is called from Client MVC App, we have called both
HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme) & HttpContext.SignOutAsync("oidc", prop).
Then user is redirected to the IdentityServer Logout method, which again calls the HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).
When Identity's logout page is rendered we generate the "IFrames" with Client's "FrontChannelLogoutUri" (in this case "ForcedSignout()" of Client App).
"ForcedSignout" method again deletes the cookies and call HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme).
Steps to replicate the issue:-
We captured the Client App's edit method in postman.
The postman request with changed data was run and it worked.
After that user was logged out of the Client App and the postman request was again run which ran successfully even though we have logged out.
Then we waited for 30 minutes (Inactivity) and tried the postman request but this time it did not work as the sessions had timed out.
We need to know, how can we remove/invalidate the session from server when the user logs out of the application? Thank you.

In ASP Net Core 3.1 Expiration cookie is not redirecting to login page when using ajax

In my app, when my cookie expire, I'm redirect to my Account/Login page. But When I call ajax method and cookie is expired , the action return 401 and I'm not redirecting to my Account/login page...
I add [Authorize] attribute on my controller.
The xhr.status parameter return 401.
Example ajax method :
$(document).on('click', '.ajax-modal', function (event) {
var url = $(this).data('url');
var id = $(this).attr('data-content');
if (id != null)
url = url + '/' + id;
$.get(url)
.done(
function (data) {
placeholderElement.html(data);
placeholderElement.find('.modal').modal('show');
}
)
.fail(
function (xhr, httpStatusMessage, customErrorMessage) {
selectErrorPage(xhr.status);
}
);
});
My ConfigureServices method :
public void ConfigureServices(IServiceCollection services)
{
#region Session
services.AddDistributedMemoryCache();
services.AddSession(options =>
{
// Set a short timeout for easy testing.
options.IdleTimeout = TimeSpan.FromSeconds(1000);
options.Cookie.HttpOnly = true; // permet d'empecher à du code JS d'accèder aux cookies
// Make the session cookie essential
options.Cookie.IsEssential = true;
});
#endregion
#region Cookie
services.AddAuthentication(options =>
{
options.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
})
.AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options =>
{
options.Cookie.Name = "TestCookie";
options.ExpireTimeSpan = TimeSpan.FromSeconds(10);
options.LoginPath = "/Account/login";
options.ReturnUrlParameter = CookieAuthenticationDefaults.ReturnUrlParameter;
options.Cookie.SameSite = SameSiteMode.Strict;
});
#endregion
Thanks for your help
I came across the issue where I am using cookie authentication in .NET Core 5, yet once the user is authenticated, everything BUT any initial AJAX request in the application works.
Every AJAX request would result in a 401. Even using the jQuery load feature would result in a 401, which was just a GET request to a controller with the [Authorize(Role = "My Role")]
However, I found that I could retrieve the data if I grabbed the URL directly and pasted it in the browser. Then suddenly, all my AJAX worked for the life of the cookie. I noticed the difference in some of the AJAX posts. The ones that didn't work used AspNetCore.AntiForgery in the headers, whereas the ones that did use AspNetCore.Cookies that authenticated.
My fix was to add a redirect in the OnRedirectToLogin event under cookie authentication. It works for all synchronous and asynchronous calls ensuring that AJAX redirects to the login page and authenticates as the current user. I don't know if this is the proper way to handle my issue, but here is the code.
EDIT: I should mention that all of the AJAX code worked perfectly in my .NET 4 web application. When I changed to 5, I experienced new issues.
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(o => {
o.LoginPath = "/Account/Login";
o.LogoutPath = "/Account/Logout";
o.AccessDeniedPath = "/Error/AccessDenied";
o.SlidingExpiration = true;
//add this to force and request to redirect (my purpose AJAX not going to login page on request and authenticating)
o.Events.OnRedirectToLogin = (context) => {
context.Response.Redirect(context.RedirectUri);
return Task.CompletedTask;
};
});

Where to store JWT Token in .net core web api?

I am using web api for accessing data and I want to authenticate and authorize web api.For that I am using JWT token authentication. But I have no idea where should I store access tokens?
What I want to do?
1)After login store the token
2)if user want to access any method of web api, check the token is valid for this user,if valid then give access.
I know two ways
1)using cookies
2)sql server database
which one is the better way to store tokens from above?
Alternatively, if you just wanted to authenticate using JWT the implementation would be slightly different
services.AddAuthentication(options =>
{
options.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
options.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
}).AddJwtBearer(options =>
{
options.Events = new JwtBearerEvents
{
OnTokenValidated = context =>
{
var user = context.Principal.Identity.Name;
//Grab the http context user and validate the things you need to
//if you are not satisfied with the validation fail the request using the below commented code
//context.Fail("Unauthorized");
//otherwise succeed the request
return Task.CompletedTask;
}
};
options.RequireHttpsMetadata = false;
options.SaveToken = true;
options.TokenValidationParameters = new TokenValidationParameters
{
ValidateIssuerSigningKey = true,
IssuerSigningKey = new SymmetricSecurityKey("MyVeryStrongKeyHiddenFromAnyone"),
ValidateIssuer = false,
ValidateAudience = false
};
});
still applying use authentication before use MVC.
[Please note these are very simplified examples and you may need to tighten your security more and implement best practices such as using strong keys, loading configs perhaps from the environment etc]
Then the actual authentication action, say perhaps in AuthenticationController would be something like
[Route("api/[controller]")]
[Authorize]
public class AuthenticationController : Controller
{
[HttpPost("authenticate")]
[AllowAnonymous]
public async Task<IActionResult> AuthenticateAsync([FromBody]LoginRequest loginRequest)
{
//LoginRequest may have any number of fields expected .i.e. username and password
//validate user credentials and if they fail return
//return Unauthorized();
var claimsIdentity = new ClaimsIdentity(new Claim[]
{
//add relevant user claims if any
}, "Cookies");
var claimsPrincipal = new ClaimsPrincipal(claimsIdentity);
await Request.HttpContext.SignInAsync("Cookies", claimsPrincipal);
return Ok();
}
}
in this instance I'm using cookies so I'm returning an HTTP result with Set Cookie. If I was using JWT, I'd return something like
[HttpPost("authenticate")]
public IActionResult Authenticate([FromBody]LoginRequest loginRequest)
{
//validate user credentials and if they validation failed return a similar response to below
//return NotFound();
var tokenHandler = new JwtSecurityTokenHandler();
var key = Encoding.ASCII.GetBytes("MySecurelyInjectedAsymKey");
var tokenDescriptor = new SecurityTokenDescriptor
{
Subject = new ClaimsIdentity(new Claim[]
{
//add my users claims etc
}),
Expires = DateTime.UtcNow.AddDays(1),//configure your token lifespan and needed
SigningCredentials = new SigningCredentials(new SymmetricSecurityKey("MyVerySecureSecreteKey"), SecurityAlgorithms.HmacSha256Signature),
Issuer = "YourOrganizationOrUniqueKey",
IssuedAt = DateTime.UtcNow
};
var token = tokenHandler.CreateToken(tokenDescriptor);
var tokenString = tokenHandler.WriteToken(token);
var cookieOptions = new CookieOptions();
cookieOptions.Expires = DateTimeOffset.UtcNow.AddHours(4);//you can set this to a suitable timeframe for your situation
cookieOptions.Domain = Request.Host.Value;
cookieOptions.Path = "/";
Response.Cookies.Append("jwt", tokenString, cookieOptions);
return Ok();
}
I'm not familiar with storing your users tokens on your back end app, I'll quickly check how does that work however if you are using dotnet core to authenticate with either cookies or with jwt, from my understanding and experience you need not store anything on your side.
If you are using cookies then you just need to to configure middleware to validate the validity of a cookie if it comes present in the users / consumer's headers and if not available or has expired or can't resolve it, you simply reject the request and the user won't even hit any of your protected Controllers and actions. Here's a very simplified approach with cookies.(I'm still in Development with it and haven't tested in production but it works perfectly fine locally for now using JS client and Postman)
services.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
options.Cookie.Name = "yourCookieName";
options.Cookie.SameSite = SameSiteMode.None;//its recommended but you can set it to any of the other 3 depending on your reqirements
options.Events = new Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationEvents
{
OnRedirectToLogin = redirectContext =>//this will be called if an unauthorized connection comes and you can do something similar to this or more
{
redirectContext.HttpContext.Response.StatusCode = 401;
return Task.CompletedTask;
},
OnValidatePrincipal = context => //if a call comes with a valid cookie, you can use this to do validations. in there you have access to the request and http context so you should have enough to work with
{
var userPrincipal = context.Principal;//I'm not doing anything with this right now but I could for instance validate if the user has the right privileges like claims etc
return Task.CompletedTask;
}
};
});
Obviously this would be placed or called in the ConfigureServices method of your startup to register authentication
and then in your Configure method of your Startup, you'd hookup Authentication like
app.UseAuthentication();
before
app.UseMvc()

Asp.net Core Persistent Authentication - Custom Cookie Authentication

I'm trying to get a persistent connection so the users only have to use their password once. I've used this doc: https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?tabs=aspnetcore2x but the users still get disconnected after a while.
await HttpContext.SignInAsync(
CookieAuthenticationDefaults.AuthenticationScheme,
principal,
new AuthenticationProperties
{
IsPersistent = true
});
What can I do to get a really persistent connection ?
The persistence granted by IsPersistent is, according to the docs, only meant to imply that the authentication will persist through browsing sessions (that is, it is kept even when the browser is closed). You need a combination of Persistence and to set an expiration time for the cookie. The expiration of the cookie can be set via the CookieAuthenticationOptions (MSDN), using the ExpireTimeSpan option.
Without persistence, expiration of the authentication can be set using the ExpiresUtc option in AuthenticationOptions,
There are few things you should notice when implement persistent cookie authentication.
Configure sliding expiration for cookie in Startup.cs. It will be clearer if you explicitly set values that you needed and don't use default settings.
private void ConfigureAuthentication(IServiceCollection services)
{
services
.AddAuthentication(CookieAuthenticationDefaults.AuthenticationScheme)
.AddCookie(options =>
{
// true by default
options.SlidingExpiration = true;
// 14 days by default
options.ExpireTimeSpan = TimeSpan.FromMinutes(20);
});
}
When user check flag "Remember Me" configure cookie to persist across browser sessions and set absolute expiration (as long as you want). This settings will override SlidingExpiration and ExpireTimeSpan. In login action:
List<Claim> claims = new List<Claim>();
// Prepare user claims...
var userIdentity = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationScheme);
ClaimsPrincipal principal = new ClaimsPrincipal(userIdentity);
AuthenticationProperties authenticationProperties = new AuthenticationProperties() { IsPersistent = model.RememberMe };
if (model.RememberMe)
{
// One month for example
authenticationProperties.ExpiresUtc = DateTimeOffset.UtcNow.AddMonths(1);
}
await HttpContext.SignInAsync(CookieAuthenticationDefaults.AuthenticationScheme, principal, authenticationProperties);
Confugure data protection. Remember machineKey in old classic asp.net webforms. Otherwise cookie will be reset after every IIS app pool restart. You should configure data protection before authentication in Startup.cs. To store keys in root folder of your app:
private void ConfigureDataProtection(IServiceCollection services, IWebHostEnvironment environment)
{
var keysDirectoryName = "Keys";
var keysDirectoryPath = Path.Combine(environment.ContentRootPath, keysDirectoryName);
if (!Directory.Exists(keysDirectoryPath))
{
Directory.CreateDirectory(keysDirectoryPath);
}
services.AddDataProtection()
.PersistKeysToFileSystem(new DirectoryInfo(keysDirectoryPath))
.SetApplicationName("YourApplicationName");
}
From docs:
https://learn.microsoft.com/en-us/aspnet/core/security/authentication/cookie?view=aspnetcore-5.0
https://learn.microsoft.com/en-us/aspnet/core/security/data-protection/configuration/overview?view=aspnetcore-5.0

How to reach signup page of openiddict authorization server?

I have built opeiddict as separate web application as authorization server. I am stuck with small problem, that is how I can go to user registration page directly though a link from the client web application. Right now I can go to login page, as your sample example:
public ActionResult SignIn() {
// Instruct the OIDC client middleware to redirect the user agent to the identity provider.
// Note: the authenticationType parameter must match the value configured in Startup.cs
return new ChallengeResult(OpenIdConnectDefaults.AuthenticationScheme, new AuthenticationProperties {
RedirectUri = "/"
});
}
Is there a way to go to authentication server Account/Register from client app?
It looks like you can set the url in the redirect. See the following snippet:
[AllowAnonymous]
public IActionResult SignIn()
{
return new ChallengeResult(
OpenIdConnectDefaults.AuthenticationScheme,
new AuthenticationProperties
{
IsPersistent = true,
RedirectUri = Url.Action("SignInCallback", "Account")
});
}
See the docs here: Initiating the authentication flow