/// <summary> /// Create a cookie for the browser by adding it to the HTTP Response. /// </summary> /// <param name="httpContext">HTTP context.</param> /// <param name="name">Name of the cookie.</param> /// <param name="value">Value associated with the cookie.</param> /// <param name="domain">Domain associated with the cookie.</param> /// <param name="expires">Expiration date and time for the cookie.</param> /// <param name="sameSite">Same site enforcement mode associated with the cookie.</param> /// <exception cref="ArgumentNullException">Cookie name is either null or empty.</exception> public static void CookieCreate(this HttpContextBase httpContext, string name, string value, string domain = null, DateTime?expires = null, SameSiteMode sameSite = SameSiteMode.None) { if (string.IsNullOrWhiteSpace(name)) { throw new ArgumentNullException(nameof(name)); } HttpCookie cookie = new HttpCookie(name) { SameSite = sameSite, Value = value }; if (!string.IsNullOrWhiteSpace(domain)) { cookie.Domain = domain; } if (expires.HasValue) { cookie.Expires = expires.Value; } httpContext.Response.Cookies.Add(cookie); }
/// <summary> /// Parses samesite option value. /// </summary> /// <param name="samesite">One of: None|Lax|Strict. Case insensitive.</param> /// <param name="mode">Parsed value.</param> /// <returns>Whether the value was parsed.</returns> public static bool TryParseSameSite(string samesite, out SameSiteMode mode) { if (samesite != null) { // return s_options.TryGetValue(samesite, out mode); // overhead // return Enum.TryParse<SameSiteMode>(samesite, true, out mode); // accepts numbers as well if (samesite.Equals(nameof(SameSiteMode.None), StringComparison.OrdinalIgnoreCase)) { mode = SameSiteMode.None; return(true); } if (samesite.Equals(nameof(SameSiteMode.Lax), StringComparison.OrdinalIgnoreCase)) { mode = SameSiteMode.Lax; return(true); } if (samesite.Equals(nameof(SameSiteMode.Strict), StringComparison.OrdinalIgnoreCase)) { mode = SameSiteMode.Strict; return(true); } } // default mode = SameSiteMode.Lax; return(false); }
private static string?MapSameSiteMode(SameSiteMode value) { return(value switch { SameSiteMode.None => "none", SameSiteMode.Lax => "lax", SameSiteMode.Strict => "strict", _ => null });
/// <summary> /// Register cookie authentication related actors /// </summary> /// <remarks> /// Remember to configure 'UseCookieAuthentication()' and 'UseAuthorization()' /// after 'UseRouting()', but before 'UseEndpoints()' /// </remarks> /// <param name="services"></param> /// <param name="configRoot"></param> /// <param name="mode"></param> public static AuthenticationBuilder AddCookieAuthentication( this IServiceCollection services, IConfigurationRoot configRoot, SameSiteMode mode = SameSiteMode.Lax ) { var config = configRoot .GetSection(WebConfiguration.AUTHENTICATION) .GetSection(WebConfiguration.AUTHENTICATION_COOKIE) .Get <CookieAuthenticationConfiguration>(); if (config == null) { var configPath = $"{WebConfiguration.AUTHENTICATION}:{WebConfiguration.AUTHENTICATION_COOKIE}"; throw new Exception($"Unable to find configuration for '{configPath}' <CookieAuthenticationConfiguration>"); } var cookie = new CookieBuilder { Name = config.CookieName, SameSite = mode }; var cookieEvents = new CookieAuthenticationEvents { OnRedirectToAccessDenied = context => { context.Response.StatusCode = 403; // Don't redirect, set to forbidden return(Task.CompletedTask); }, OnRedirectToLogin = context => { context.Response.StatusCode = 401; // Don't redirect, set to unauthorized return(Task.CompletedTask); } }; services.AddSingleton((sp) => config); // Register actors return(services .AddAuthentication((options) => { options.DefaultAuthenticateScheme = options.DefaultChallengeScheme = options.DefaultScheme = options.DefaultSignInScheme = config.AuthenticationScheme; }) .AddCookie(config.AuthenticationScheme, options => { options.AccessDeniedPath = new PathString(config.AccessDeniedPath); options.Cookie = cookie; options.Events = cookieEvents; options.LoginPath = new PathString(config.LoginPath); })); }
internal CookieSettings( string name, bool?secure, bool?httpOnly, SameSiteMode sameSite, string?domainExpression) { Name = name; Secure = secure; HttpOnly = httpOnly; SameSite = sameSite; DomainExpression = domainExpression; }
private void SetDefaultsFromConfig() { HttpCookiesSection config = RuntimeConfig.GetConfig().HttpCookies; _secure = config.RequireSSL; _httpOnly = config.HttpOnlyCookies; _sameSite = config.SameSite; if (config.Domain != null && config.Domain.Length > 0) { _domain = config.Domain; } }
/// <summary> /// Stores a value in a user Cookie, creating it if it doesn't exists yet. /// </summary> /// <param name="cookieName">Cookie name</param> /// <param name="cookieDomain">Cookie domain (or NULL to use default domain value)</param> /// <param name="keyName">Cookie key name (if the cookie is a keyvalue pair): if NULL or EMPTY, the cookie will be treated as a single variable.</param> /// <param name="value">Value to store into the cookie</param> /// <param name="expirationDate">Expiration Date (set it to NULL to leave default expiration date)</param> /// <param name="httpOnly">set it to TRUE to enable HttpOnly, FALSE otherwise (default: false)</param> /// <param name="sameSite">set it to 'None', 'Lax', 'Strict' or '(-1)' to not add it (default: '(-1)').</param> /// <param name="secure">set it to TRUE to enable Secure (HTTPS only), FALSE otherwise</param> public static void StoreInCookie( string cookieName, string cookieDomain, IDictionary <string, string> keyValuePairs, DateTime?expirationDate, bool httpOnly = false, SameSiteMode sameSite = (SameSiteMode)(-1), bool secure = false) { // NOTE: we have to look first in the response, and then in the request. // This is required when we update multiple keys inside the cookie. HttpCookie cookie = HttpContext.Current.Response.Cookies.AllKeys.Contains(cookieName) ? HttpContext.Current.Response.Cookies[cookieName] : HttpContext.Current.Request.Cookies[cookieName]; if (cookie == null) { cookie = new HttpCookie(cookieName); } if (keyValuePairs == null || keyValuePairs.Count == 0) { cookie.Value = null; } else { foreach (var pair in keyValuePairs) { cookie.Values.Set(pair.Key, pair.Value); } } if (expirationDate.HasValue) { cookie.Expires = expirationDate.Value; } if (!String.IsNullOrEmpty(cookieDomain)) { cookie.Domain = cookieDomain; } if (httpOnly) { cookie.HttpOnly = true; } cookie.Secure = secure; cookie.SameSite = sameSite; HttpContext.Current.Response.Cookies.Set(cookie); }
private void PersistCookie(string name, string value, TimeSpan?maxAge, SameSiteMode sameSite, bool httpOnly, bool secure) { if (value == null) { value = string.Empty; } int cookieSeriesCount = value.Length * 2 / maxCookieSizeBytes + 1; if (cookieSeriesCount > maxCookiesPerDomain) { throw new Exception("Cookie too large"); } int x; for (x = 0; x < cookieSeriesCount; x++) { string seriesName = x == 0 ? name : $"{name}-{x}"; string seriesValue = value.Substring(x * maxCookieSizeBytes / 2, Math.Min(maxCookieSizeBytes / 2, value.Length - x * maxCookieSizeBytes / 2)); context.Response.Cookies.Delete(seriesName); context.Response.Cookies.Append(seriesName, seriesValue, new CookieOptions() { Path = "/", SameSite = sameSite, Secure = secure, HttpOnly = httpOnly, MaxAge = maxAge, Expires = maxAge.HasValue ? DateTimeOffset.UtcNow.Add(maxAge.Value) : (DateTimeOffset?)null }); } for (int y = x; y < maxCookiesPerDomain; y++) { string seriesName = y == 0 ? name : $"{name}-{y}"; if (context.Request.Cookies.Keys.Contains(seriesName)) { context.Response.Cookies.Delete(seriesName); context.Response.Cookies.Append(seriesName, string.Empty, new CookieOptions() { Path = "/", MaxAge = new TimeSpan(0), Expires = DateTimeOffset.MinValue }); } } }
private static string GetStringRepresentationOfSameSite(SameSiteMode siteMode) { switch (siteMode) { case SameSiteMode.None: return("None"); case SameSiteMode.Lax: return("Lax"); case SameSiteMode.Strict: return("Strict"); default: throw new ArgumentOutOfRangeException("siteMode", string.Format(CultureInfo.InvariantCulture, "Unexpected SameSiteMode value: {0}", siteMode)); } }
private void DeleteCookie(string name, SameSiteMode sameSite, bool httpOnly, bool secure) { for (int x = 0; x < maxCookiesPerDomain; x++) { string seriesName = x == 0 ? name : $"{name}-{x}"; if (context.Request.Cookies.Keys.Contains(seriesName)) { context.Response.Cookies.Delete(seriesName); context.Response.Cookies.Append(seriesName, string.Empty, new CookieOptions() { Path = "/", MaxAge = new TimeSpan(0), Expires = DateTimeOffset.MinValue, SameSite = sameSite, HttpOnly = httpOnly, Secure = secure }); } } }
public void HandleSameSiteCookieCompatibility_Default_ExecutesSuccessfully(SameSiteMode initialSameSiteMode, SameSiteMode expectedSameSiteMode, string userAgent) { _httpContext.Request.Headers.Add(_userAgentHeaderName, userAgent); var appendCookieOptions = new CookieOptions() { SameSite = initialSameSiteMode }; var deleteCookieOptions = new CookieOptions() { SameSite = initialSameSiteMode }; var appendCookieContext = new AppendCookieContext(_httpContext, appendCookieOptions, _cookieName, _cookieValue); var deleteCookieContext = new DeleteCookieContext(_httpContext, deleteCookieOptions, _cookieName); _cookiePolicyOptions.HandleSameSiteCookieCompatibility(); Assert.Equal(SameSiteMode.Unspecified, _cookiePolicyOptions.MinimumSameSitePolicy); _cookiePolicyOptions.OnAppendCookie(appendCookieContext); Assert.Equal(expectedSameSiteMode, appendCookieOptions.SameSite); _cookiePolicyOptions.OnDeleteCookie(deleteCookieContext); Assert.Equal(expectedSameSiteMode, deleteCookieOptions.SameSite); }
/// <inheritdoc /> public IAndResponseCookieTestBuilder WithSameSite(SameSiteMode sameSite) { this.responseCookie.SameSite = sameSite; this.validations.Add((expected, actual) => expected.SameSite == actual.SameSite); return(this); }
/// <summary> /// Converts the specified string representation of an HTTP cookie to HttpCookie /// </summary> /// <param name="input"></param> /// <param name="result"></param> /// <returns></returns> public static bool TryParse(string input, out HttpCookie result) { result = null; if (string.IsNullOrEmpty(input)) { return(false); } // The substring before the first ';' is cookie-pair, with format of cookiename[=key1=val2&key2=val2&...] int dividerIndex = input.IndexOf(';'); string cookiePair = dividerIndex >= 0 ? input.Substring(0, dividerIndex) : input; HttpCookie cookie = HttpRequest.CreateCookieFromString(cookiePair.Trim()); // If there was no cookie name being created, stop parsing and return if (string.IsNullOrEmpty(cookie.Name)) { return(false); } // // Parse the collections of cookie-av // cookie-av = expires-av/max-age-av/domain-av/path-av/secure-av/httponly-av/extension-av // https://tools.ietf.org/html/rfc6265 while (dividerIndex >= 0 && dividerIndex < input.Length - 1) { int cookieAvStartIndex = dividerIndex + 1; dividerIndex = input.IndexOf(';', cookieAvStartIndex); string cookieAv = dividerIndex >= 0 ? input.Substring(cookieAvStartIndex, dividerIndex - cookieAvStartIndex).Trim() : input.Substring(cookieAvStartIndex).Trim(); int assignmentIndex = cookieAv.IndexOf('='); string attributeName = assignmentIndex >= 0 ? cookieAv.Substring(0, assignmentIndex).Trim() : cookieAv; string attributeValue = assignmentIndex >= 0 && assignmentIndex < cookieAv.Length - 1 ? cookieAv.Substring(assignmentIndex + 1).Trim() : null; // // Parse supported cookie-av Attribute // // Expires if (StringUtil.EqualsIgnoreCase(attributeName, "Expires")) { DateTime dt; if (DateTime.TryParse(attributeValue, out dt)) { cookie.Expires = dt; } } // // Domain else if (attributeValue != null && StringUtil.EqualsIgnoreCase(attributeName, "Domain")) { cookie.Domain = attributeValue; } // // Path else if (attributeValue != null && StringUtil.EqualsIgnoreCase(attributeName, "Path")) { cookie.Path = attributeValue; } // // Secure else if (StringUtil.EqualsIgnoreCase(attributeName, "Secure")) { cookie.Secure = true; } // // HttpOnly else if (StringUtil.EqualsIgnoreCase(attributeName, "HttpOnly")) { cookie.HttpOnly = true; } // // SameSite else if (StringUtil.EqualsIgnoreCase(attributeName, "SameSite")) { SameSiteMode sameSite = (SameSiteMode)(-1); if (Enum.TryParse <SameSiteMode>(attributeValue, true, out sameSite)) { cookie.SameSite = sameSite; } } } result = cookie; return(true); }
public Startup(IConfiguration configuration) { Configuration = configuration; Mode = Convert.ToBoolean(configuration["LaxCookie"]) ? SameSiteMode.Lax : SameSiteMode.None; }
public void Remove(string name, SameSiteMode sameSite = SameSiteMode.Strict, bool httpOnly = true, bool secure = true) { DeleteCookie(name, sameSite, httpOnly, secure); }
public void AddSecure(string name, string value, TimeSpan?maxAge = null, SameSiteMode sameSite = SameSiteMode.Strict, bool httpOnly = true, bool secure = true) { var encryptedValue = Encrypt(value); PersistCookie(name, encryptedValue, maxAge, sameSite, httpOnly, secure); }
public void Add(string name, string value, TimeSpan?maxAge = null, SameSiteMode sameSite = SameSiteMode.Strict, bool httpOnly = false, bool secure = false) { PersistCookie(name, value, maxAge, sameSite, httpOnly, secure); }
public Startup(IConfiguration configuration, IHostingEnvironment environment) { Environment = environment; Configuration = configuration; Mode = Convert.ToBoolean(configuration["LaxCookie"]) ? SameSiteMode.Lax : SameSiteMode.None; }
/// <summary> /// Add SAML 2.0 configuration. /// </summary> /// <param name="loginPath">Redirection target used by the handler.</param> /// <param name="slidingExpiration">If set to true the handler re-issue a new cookie with a new expiration time any time it processes a request which is more than halfway through the expiration window.</param> /// <param name="accessDeniedPath">If configured, access denied redirection target used by the handler.</param> /// <param name="sessionStore">Allow configuration of a custom ITicketStore.</param> public static IServiceCollection AddSaml2(this IServiceCollection services, string loginPath = "/Auth/Login", bool slidingExpiration = false, string accessDeniedPath = null, ITicketStore sessionStore = null, SameSiteMode cookieSameSite = SameSiteMode.Lax) { services.AddAuthentication(Saml2Constants.AuthenticationScheme) .AddCookie(Saml2Constants.AuthenticationScheme, o => { o.LoginPath = new PathString(loginPath); o.SlidingExpiration = slidingExpiration; if (!string.IsNullOrEmpty(accessDeniedPath)) { o.AccessDeniedPath = new PathString(accessDeniedPath); } if (sessionStore != null) { o.SessionStore = sessionStore; } o.Cookie.SameSite = cookieSameSite; }); return(services); }
public void HandleSameSiteCookieCompatibility_CustomFilter_ExecutesSuccessfully(SameSiteMode initialSameSiteMode, SameSiteMode expectedSameSiteMode, bool expectedEventCalled, string userAgent) { _httpContext.Request.Headers.Add(_userAgentHeaderName, userAgent); var appendCookieOptions = new CookieOptions() { SameSite = initialSameSiteMode }; var deleteCookieOptions = new CookieOptions() { SameSite = initialSameSiteMode }; var appendCookieContext = new AppendCookieContext(_httpContext, appendCookieOptions, _cookieName, _cookieValue); var deleteCookieContext = new DeleteCookieContext(_httpContext, deleteCookieOptions, _cookieName); var appendEventCalled = false; var deleteEventCalled = false; _cookiePolicyOptions.HandleSameSiteCookieCompatibility((userAgent) => { appendEventCalled = true; return(CookiePolicyOptionsExtensions.DisallowsSameSiteNone(userAgent)); }); Assert.Equal(SameSiteMode.Unspecified, _cookiePolicyOptions.MinimumSameSitePolicy); _cookiePolicyOptions.OnAppendCookie(appendCookieContext); Assert.Equal(expectedSameSiteMode, appendCookieOptions.SameSite); Assert.Equal(expectedEventCalled, appendEventCalled); _cookiePolicyOptions.HandleSameSiteCookieCompatibility((userAgent) => { deleteEventCalled = true; return(CookiePolicyOptionsExtensions.DisallowsSameSiteNone(userAgent)); }); _cookiePolicyOptions.OnDeleteCookie(deleteCookieContext); Assert.Equal(expectedSameSiteMode, deleteCookieOptions.SameSite); Assert.Equal(expectedEventCalled, deleteEventCalled); }
/// <summary> /// Configure application to use cookie authentication /// </summary> /// <param name="app"></param> /// <param name="mode"></param> public static IApplicationBuilder UseCookieAuthentication(this IApplicationBuilder app, SameSiteMode mode = SameSiteMode.Lax) => app .UseAuthentication() .UseCookiePolicy(new CookiePolicyOptions { MinimumSameSitePolicy = mode });