.ASPNET.Cookies cookie is not creating during Azure AD authentication [duplicate] - asp.net-mvc

I have a strange problem with using Owin cookie authentication.
When I start my IIS server authentication works perfectly fine on IE/Firefox and Chrome.
I started doing some testing with Authentication and logging in on different platforms and I have come up with a strange error. Sporadically the Owin framework / IIS just doesn't send any cookies to the browsers. I will type in a username and password which is correct the code runs but no cookie gets delivered to the browser at all. If I restart the server it starts working then at some point I will try login and again cookies stop getting delivered. Stepping over the code does nothing and throws no errors.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
AuthenticationMode = AuthenticationMode.Active,
CookieHttpOnly = true,
AuthenticationType = "ABC",
LoginPath = new PathString("/Account/Login"),
CookiePath = "/",
CookieName = "ABC",
Provider = new CookieAuthenticationProvider
{
OnApplyRedirect = ctx =>
{
if (!IsAjaxRequest(ctx.Request))
{
ctx.Response.Redirect(ctx.RedirectUri);
}
}
}
});
And within my login procedure I have the following code:
IAuthenticationManager authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
authenticationManager.SignOut(DefaultAuthenticationTypes.ExternalCookie);
var authentication = HttpContext.Current.GetOwinContext().Authentication;
var identity = new ClaimsIdentity("ABC");
identity.AddClaim(new Claim(ClaimTypes.Name, user.Username));
identity.AddClaim(new Claim(ClaimTypes.NameIdentifier, user.User_ID.ToString()));
identity.AddClaim(new Claim(ClaimTypes.Role, role.myRole.ToString()));
authentication.AuthenticationResponseGrant =
new AuthenticationResponseGrant(identity, new AuthenticationProperties()
{
IsPersistent = isPersistent
});
authenticationManager.SignIn(new AuthenticationProperties() {IsPersistent = isPersistent}, identity);
Update 1: It seems that one cause of the problem is when I add items to session the problems start. Adding something simple like Session.Content["ABC"]= 123 seems to create the problem.
What I can make out is as follows:
1) (Chrome)When I login I get ASP.NET_SessionId + my authentication cookie.
2) I go to a page that sets a session.contents...
3) Open a new browser (Firefox) and try login and it does not receive an ASP.NET_SessionId nor does it get a Authentication Cookie
4) Whilst the first browser has the ASP.NET_SessionId it continues to work. The minute I remove this cookie it has the same problem as all the other browsers
I am working on ip address (10.x.x.x) and localhost.
Update 2: Force creation of ASPNET_SessionId first on my login_load page before authentication with OWIN.
1) before I authenticate with OWIN I make a random Session.Content value on my login page to start the ASP.NET_SessionId
2) then I authenticate and make further sessions
3) Other browsers seem to now work
This is bizarre. I can only conclude that this has something to do with ASP and OWIN thinking they are in different domains or something like that.
Update 3 - Strange behaviour between the two.
Additional strange behaviour identified - Timeout of Owin and ASP session is different. What I am seeing is that my Owin sessions are staying alive longer than my ASP sessions through some mechanism. So when logging in:
1.) I have a cookied based auth session
2.) I set a few session variables
My session variables(2) "die" before the owin cookie session variable forces re-login, which causes unexpected behaviour throughout my entire application. (Person is logged in but is not really logged in)
Update 3B
After some digging I saw some comments on a page that say the "forms" authentication timeout and session timeout need to match. I am thinking normally the two are in sync but for whatever reason the two are not in sync.
Summary of Workarounds
1) Always create a Session first before authentication. Basically create session when you start the application Session["Workaround"] = 0;
2) [Experimental] if you persist cookies make sure your OWIN timeout / length is longer than your sessionTimeout in your web.config (in testing)

I have encountered the same problem and traced the cause to OWIN ASP.NET hosting implementation. I would say it's a bug.
Some background
My findings are based on these assembly versions:
Microsoft.Owin, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
Microsoft.Owin.Host.SystemWeb, Version=2.0.2.0, Culture=neutral, PublicKeyToken=31bf3856ad364e35
System.Web, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b03f5f7f11d50a3a
OWIN uses it's own abstraction to work with response Cookies (Microsoft.Owin.ResponseCookieCollection). This implementation directly wraps response headers collection and accordingly updates Set-Cookie header. OWIN ASP.NET host (Microsoft.Owin.Host.SystemWeb) just wraps System.Web.HttpResponse and it's headers collection. So when new cookie is created through OWIN, response Set-Cookie header is changed directly.
But ASP.NET also uses it's own abstraction to work with response Cookies. This is exposed to us as System.Web.HttpResponse.Cookies property and implemented by sealed class System.Web.HttpCookieCollection. This implementation does not wrap response Set-Cookie header directly but uses some optimizations and handful of internal notifications to manifest it's changed state to response object.
Then there is a point late in request lifetime where HttpCookieCollection changed state is tested (System.Web.HttpResponse.GenerateResponseHeadersForCookies()) and cookies are serialized to Set-Cookie header. If this collection is in some specific state, whole Set-Cookie header is first cleared and recreated from cookies stored in collection.
ASP.NET session implementation uses System.Web.HttpResponse.Cookies property to store it's ASP.NET_SessionId cookie. Also there is some basic optimization in ASP.NET session state module (System.Web.SessionState.SessionStateModule) implemented through static property named s_sessionEverSet which is quite self explanatory. If you ever store something to session state in your application, this module will do a little more work for each request.
Back to our login problem
With all these pieces your scenarios can be explained.
Case 1 - Session was never set
System.Web.SessionState.SessionStateModule, s_sessionEverSet property is false. No session id's are generated by session state module and System.Web.HttpResponse.Cookies collection state is not detected as changed. In this case OWIN cookies are sent correctly to the browser and login works.
Case 2 - Session was used somewhere in application, but not before user tries to authenticate
System.Web.SessionState.SessionStateModule, s_sessionEverSet property is true. Session Id's are generated by SessionStateModule, ASP.NET_SessionId is added to System.Web.HttpResponse.Cookies collection but it's removed later in request lifetime as user's session is in fact empty. In this case System.Web.HttpResponse.Cookies collection state is detected as changed and Set-Cookie header is first cleared before cookies are serialized to header value.
In this case OWIN response cookies are "lost" and user is not authenticated and is redirected back to login page.
Case 3 - Session is used before user tries to authenticate
System.Web.SessionState.SessionStateModule, s_sessionEverSet property is true. Session Id's are generated by SessionStateModule, ASP.NET_SessionId is added to System.Web.HttpResponse.Cookies. Due to internal optimization in System.Web.HttpCookieCollection and System.Web.HttpResponse.GenerateResponseHeadersForCookies() Set-Cookie header is NOT first cleared but only updated.
In this case both OWIN authentication cookies and ASP.NET_SessionId cookie are sent in response and login works.
More general problem with cookies
As you can see the problem is more general and not limited to ASP.NET session. If you are hosting OWIN through Microsoft.Owin.Host.SystemWeb and you/something is directly using System.Web.HttpResponse.Cookies collection you are at risk.
For example this works and both cookies are correctly sent to browser...
public ActionResult Index()
{
HttpContext.GetOwinContext()
.Response.Cookies.Append("OwinCookie", "SomeValue");
HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
return View();
}
But this does not and OwinCookie is "lost"...
public ActionResult Index()
{
HttpContext.GetOwinContext()
.Response.Cookies.Append("OwinCookie", "SomeValue");
HttpContext.Response.Cookies["ASPCookie"].Value = "SomeValue";
HttpContext.Response.Cookies.Remove("ASPCookie");
return View();
}
Both tested from VS2013, IISExpress and default MVC project template.

In short, the .NET cookie manager will win over the OWIN cookie manager and overwrite cookies set on the OWIN layer. The fix is to use the SystemWebCookieManager class, provided as a solution on the Katana Project here. You need to use this class or one similar to it, which will force OWIN to use the .NET cookie manager so there are no inconsistencies:
public class SystemWebCookieManager : ICookieManager
{
public string GetRequestCookie(IOwinContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
var cookie = webContext.Request.Cookies[key];
return cookie == null ? null : cookie.Value;
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
bool expiresHasValue = options.Expires.HasValue;
var cookie = new HttpCookie(key, value);
if (domainHasValue)
{
cookie.Domain = options.Domain;
}
if (pathHasValue)
{
cookie.Path = options.Path;
}
if (expiresHasValue)
{
cookie.Expires = options.Expires.Value;
}
if (options.Secure)
{
cookie.Secure = true;
}
if (options.HttpOnly)
{
cookie.HttpOnly = true;
}
webContext.Response.AppendCookie(cookie);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
AppendResponseCookie(
context,
key,
string.Empty,
new CookieOptions
{
Path = options.Path,
Domain = options.Domain,
Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
});
}
}
In your application startup, just assign it when you create your OWIN dependencies:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new SystemWebCookieManager()
...
});
A similar answer has been provided here but it does not include all of the code-base required to solve the problem, so I see a need to add it here because the external link to the Katana Project may go down and this should be fully chronicled as a solution here as well.

Starting with the great analysis by #TomasDolezal, I had a look at both the Owin and the System.Web source.
The problem is that System.Web has its own master source of cookie information and that isn't the Set-Cookie header. Owin only knows about the Set-Cookie header. A workaround is to make sure that any cookies set by Owin are also set in the HttpContext.Current.Response.Cookies collection.
I've made a small middleware (source, nuget) that does exactly that, which is intended to be placed immediately above the cookie middleware registration.
app.UseKentorOwinCookieSaver();
app.UseCookieAuthentication(new CookieAuthenticationOptions());

Katana team answered to the issue Tomas Dolezar raised, and posted documentation about workarounds:
Workarounds fall into two categories. One is to re-configure
System.Web so it avoids using the Response.Cookies collection and
overwriting the OWIN cookies. The other approach is to re-configure
the affected OWIN components so they write cookies directly to
System.Web's Response.Cookies collection.
Ensure session is established prior to authentication: The conflict between System.Web and Katana cookies is per request, so it may be
possible for the application to establish the session on some request
prior to the authentication flow. This should be easy to do when the
user first arrives, but it may be harder to guarantee later when the
session or auth cookies expire and/or need to be refreshed.
Disable the SessionStateModule - If the application is not relying on session information, but the session module is still setting a
cookie that causes the above conflict, then you may consider disabling
the session state module.
Reconfigure the CookieAuthenticationMiddleware to write directly to System.Web's cookie collection.
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
See SystemWebCookieManager implementation from the documentation (link above)
More information here
Edit
Below the steps we took to solve the issue. Both 1. and 2. solved the problem also separately but we decided to apply both just in case:
1.
Use SystemWebCookieManager
2.
Set the session variable:
protected override void Initialize(RequestContext requestContext)
{
base.Initialize(requestContext);
// See http://stackoverflow.com/questions/20737578/asp-net-sessionid-owin-cookies-do-not-send-to-browser/
requestContext.HttpContext.Session["FixEternalRedirectLoop"] = 1;
}
(side note: the Initialize method above is the logical place for the fix because base.Initialize makes Session available. However, the fix could also be applied later because in OpenId there's first an anonymous request, then redirect to the OpenId provider and then back to the app. The problems would occur after the redirect back to the app while the fix sets the session variable already during the first anonymous request thus fixing the problem before any redirect back even happens)
Edit 2
Copy-paste from the Katana project 2016-05-14:
Add this:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
// ...
CookieManager = new SystemWebCookieManager()
});
...and this:
public class SystemWebCookieManager : ICookieManager
{
public string GetRequestCookie(IOwinContext context, string key)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
var cookie = webContext.Request.Cookies[key];
return cookie == null ? null : cookie.Value;
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
var webContext = context.Get<HttpContextBase>(typeof(HttpContextBase).FullName);
bool domainHasValue = !string.IsNullOrEmpty(options.Domain);
bool pathHasValue = !string.IsNullOrEmpty(options.Path);
bool expiresHasValue = options.Expires.HasValue;
var cookie = new HttpCookie(key, value);
if (domainHasValue)
{
cookie.Domain = options.Domain;
}
if (pathHasValue)
{
cookie.Path = options.Path;
}
if (expiresHasValue)
{
cookie.Expires = options.Expires.Value;
}
if (options.Secure)
{
cookie.Secure = true;
}
if (options.HttpOnly)
{
cookie.HttpOnly = true;
}
webContext.Response.AppendCookie(cookie);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
if (context == null)
{
throw new ArgumentNullException("context");
}
if (options == null)
{
throw new ArgumentNullException("options");
}
AppendResponseCookie(
context,
key,
string.Empty,
new CookieOptions
{
Path = options.Path,
Domain = options.Domain,
Expires = new DateTime(1970, 1, 1, 0, 0, 0, DateTimeKind.Utc),
});
}
}

Answers have been provided already, but in owin 3.1.0, there is a SystemWebChunkingCookieManager class that can be used.
https://github.com/aspnet/AspNetKatana/blob/dev/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs
https://raw.githubusercontent.com/aspnet/AspNetKatana/c33569969e79afd9fb4ec2d6bdff877e376821b2/src/Microsoft.Owin.Host.SystemWeb/SystemWebChunkingCookieManager.cs
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new SystemWebChunkingCookieManager()
...
});

If you are setting cookies in OWIN middleware yourself, then using OnSendingHeaders seems to get round the problem.
For example, using the code below owinResponseCookie2 will be set, even though owinResponseCookie1 is not:
private void SetCookies()
{
var owinContext = HttpContext.GetOwinContext();
var owinResponse = owinContext.Response;
owinResponse.Cookies.Append("owinResponseCookie1", "value1");
owinResponse.OnSendingHeaders(state =>
{
owinResponse.Cookies.Append("owinResponseCookie2", "value2");
},
null);
var httpResponse = HttpContext.Response;
httpResponse.Cookies.Remove("httpResponseCookie1");
}

I faced the Similar Issue with Visual Studio 2017 and .net MVC 5.2.4, Updating Nuget Microsoft.Owin.Security.Google to lastest version which currently is 4.0.1 worked for me!
Hope this Helps someone!

The fastest one-line code solution:
HttpContext.Current.Session["RunSession"] = "1";
Just add this line before CreateIdentity method:
HttpContext.Current.Session["RunSession"] = "1";
var userIdentity = userManager.CreateIdentity(user, DefaultAuthenticationTypes.ApplicationCookie);
_authenticationManager.SignIn(new AuthenticationProperties { IsPersistent = rememberLogin }, userIdentity);

I had the same symptom of the Set-Cookie header not being sent but none of these answers helped me. Everything worked on my local machine but when deployed to production the set-cookie headers would never get set.
It turns out it was a combination of using a custom CookieAuthenticationMiddleware with WebApi along with WebApi compression support
Luckily I was using ELMAH in my project which let me to this exception being logged:
System.Web.HttpException Server cannot append header after HTTP
headers have been sent.
Which led me to this GitHub Issue
Basically, if you have an odd setup like mine you will want to disable compression for your WebApi controllers/methods that set cookies, or try the OwinServerCompressionHandler.

Related

Changes to cookie domain for outgoing responses ignored for ServiceStack requests

I have a multi-tenant website (e.g. several different sites, each with it's own domain, all in the same project, separated using MVC areas), where the authentication cookie has the domain manually set when the user logs in so that it is available to all subdomains (but not the various other sites in the project, this is not an SSO).
So a user logins a x.foo.com, the cookie domain is set for foo.com, so that it also works at y.foo.com and z.foo.com. However, because of the other domains being served from the same project the auth cookie domain cannot be set in the web.config in the usual manner, instead it is set manually when the user logins in like so:
public HttpCookie GetAuthenticationCookie(string username)
{
var cookieDomain = UrlHelper.GetTopAndSecondLevelDomain();
var authenticationCookie = FormsAuthentication.GetAuthCookie(username, false);
authenticationCookie.Domain = cookieDomain;
return authenticationCookie;
}
This works fine, but of course can cause a problem when the cookie is automatically refreshed for sliding expiration. So we have an HTTP module which is hooked into the PostRequestHandlerExecute event of our MVC app to look for auth cookies that were set into the response during the request, and overriding the domain:
public class AuthenticationCookieDomainInterceptorModule : IHttpModule
{
public void Init(HttpApplication context)
{
context.PostRequestHandlerExecute += UpdateAuthenticationCookieExpiry;
}
private void UpdateAuthenticationCookieExpiry(object sender, EventArgs e)
{
var app = (HttpApplication) sender;
var cookieDomain = UrlHelper.GetTopAndSecondLevelDomain();
var authenticationCookie = GetCookieFromResponse(app.Context.Response, FormsAuthentication.FormsCookieName);
if (authenticationCookie != null)
{
if (authenticationCookie.Domain == null || !string.Equals(authenticationCookie.Domain, cookieDomain, StringComparison.InvariantCultureIgnoreCase))
{
authenticationCookie.Domain = cookieDomain;
}
}
}
private HttpCookie GetCookieFromResponse(HttpResponse response, string cookieName)
{
var cookies = response.Cookies;
for (var i = 0; i < cookies.Count; i++) {
if (cookies[i].Name == cookieName)
{
return cookies[i];
}
}
return null;
}
}
This is also works fine unless the request is to our ServiceStack front end which we use to handle our AJAX requests. In that case the module fires as normal, picks up the cookie if its been set, changes the domain as it should, but when the response is sent back to the client the changes to the cookie are ignored.
Is there any reason why the cookie changes wouldn't be saved to the response in this scenario? My guess would be something to do with the fact ServiceStack uses an HttpHandler to hook in the request cycle in the first place, so we are not going through the normal MVC request life-cycle.

How is Owin able to set the Asp.Net Identity authentication cookies after the Application_EndRequest stage?

As a test, I created a fresh Asp.Net MVC5 app using the latest template in Visual Studio 2013. I added the following method to Global.asax.cs:
protected void Application_PreSendRequestHeaders()
{
Response.AppendCookie(new HttpCookie("TotalNumberOfCookiesInApplication_EndRequestIs", Response.Cookies.Count + string.Empty));
}
When I start the app and do a POST to /Account/Login using the credentials of a registered user, the cookies that get returned to the client are:
Note that the custom cookie I've added shows that there are no cookies set in the response by the time Application_PreSendRequestHeaders() is called. Despite this, all the Auth cookies arrive at the client. I was of the understanding that Application_PreSendRequestHeaders() is the last stage we can "hook" into for modifying cookies. Is the Owin middleware able to somehow add cookies after that, or am I missing something?
(In case you're interested, my motivation for all this is: I'm trying to modify the domain of the auth cookies to be ".abc.com", where "abc.com" is the last two parts of the host in the request URI. I want to do this to support authentication across multiple subdomains. Setting the CookieDomain in the context of the global Owin configuration (IAppBuilder) isn't enough because the request host changes between our debug/staging/production environments, and we often deploy the production code to Azure staging first for testing before doing a VIP swap).
(Note also that I'm aware of posts like this one, however it doesn't explain where the cookies are actually set)
EDIT:
Based on a bit more searching, it seems I'm looking into the wrong pipeline. Owin has its own pipeline, so I found this post which describes how we can hook into it. Viola...there were the cookies. Would be great if anybody could confirm that this is indeed the most sensible way to do it.
EDIT 2:
Finally decided to look into the Katana source code and found out that all I needed to do to get my cookie domains set was the following code in my CookieAuthenticationProvider
OnResponseSignIn = context =>
{
// Example only!
context.CookieOptions.Domain = context.Request.Uri.Host;
},
OnResponseSignOut = context =>
{
// Example only!
context.CookieOptions.Domain = context.Request.Uri.Host;
}
Edit 3:
An even cleaner solution for my case was just to use a custom cookie manager, which set the cookie domain based on the current request URI:
/// <summary>
/// This class simply appends the cookie domain to the usual auth cookies
/// </summary>
public class ChunkingCookieManagerWithSubdomains : ICookieManager
{
private readonly ChunkingCookieManager _chunkingCookieManager;
public ChunkingCookieManagerWithSubdomains()
{
_chunkingCookieManager = new ChunkingCookieManager();
}
public string GetRequestCookie(IOwinContext context, string key)
{
return _chunkingCookieManager.GetRequestCookie(context, key);
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.DeleteCookie(context, key, options);
}
}
...which is then set in the Cookie Auth Options in the Owin setup:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new ChunkingCookieManagerWithSubdomains(),
...
}
});
Hope that helps somebody coming across the same kind of question.
As requested by Tieson, here's a summary of my edits in the original post above, as an answer.
Suggested solution: Use a custom cookie manager.
/// <summary>
/// This class simply appends the cookie domain to the usual auth cookies
/// </summary>
public class ChunkingCookieManagerWithSubdomains : ICookieManager
{
private readonly ChunkingCookieManager _chunkingCookieManager;
public ChunkingCookieManagerWithSubdomains()
{
_chunkingCookieManager = new ChunkingCookieManager();
}
public string GetRequestCookie(IOwinContext context, string key)
{
return _chunkingCookieManager.GetRequestCookie(context, key);
}
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
// Simplification (use the context parameter to get the required request info)
options.Domain = ".domainBasedOnRequestInContext.com";
_chunkingCookieManager.DeleteCookie(context, key, options);
}
}
...which can then be set in the Cookie Auth Options in the Owin setup:
app.UseCookieAuthentication(new CookieAuthenticationOptions
{
...
CookieManager = new ChunkingCookieManagerWithSubdomains(),
...
}
});

Serving an iCalendar file in ASPNET MVC with authentication

I'm trying to serve an iCalendar file (.ics) in my MVC application.
So far it's working fine. I have an iPhone subscribing to the URL for the calendar but now I need to serve a personalised calendar to each user.
When subscribing to the calendar on the iPhone I can enter a username and password, but I don't know how to access these in my MVC app.
Where can I find details of how the authentication works, and how to implement it?
It turns out that Basic Authentication is what is required. I half had it working but my IIS configuration got in the way. So, simply returning a 401 response when there is no Authorization header causes the client (e.g. iPhone) to require a username/password to subscribe to the calendar.
On the authorization of the request where there is an Authorization request header, the basic authentication can be processed, retrieving the username and password from the base 64 encoded string.
Here's some useful code for MVC:
public class BasicAuthorizeAttribute : AuthorizeAttribute
{
public override void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
var auth = filterContext.HttpContext.Request.Headers["Authorization"];
if (!String.IsNullOrEmpty(auth))
{
var encodedDataAsBytes = Convert.FromBase64String(auth.Replace("Basic ", ""));
var value = Encoding.ASCII.GetString(encodedDataAsBytes);
var username = value.Substring(0, value.IndexOf(':'));
var password = value.Substring(value.IndexOf(':') + 1);
if (MembershipService.ValidateUser(username, password))
{
filterContext.HttpContext.User = new GenericPrincipal(new GenericIdentity(username), null);
}
else
{
filterContext.Result = new HttpStatusCodeResult(401);
}
}
else
{
if (AuthorizeCore(filterContext.HttpContext))
{
var cachePolicy = filterContext.HttpContext.Response.Cache;
cachePolicy.SetProxyMaxAge(new TimeSpan(0));
cachePolicy.AddValidationCallback(CacheValidateHandler, null);
}
else
{
filterContext.HttpContext.Response.Clear();
filterContext.HttpContext.Response.StatusDescription = "Unauthorized";
filterContext.HttpContext.Response.AddHeader("WWW-Authenticate", "Basic realm=\"Secure Calendar\"");
filterContext.HttpContext.Response.Write("401, please authenticate");
filterContext.HttpContext.Response.StatusCode = 401;
filterContext.Result = new EmptyResult();
filterContext.HttpContext.Response.End();
}
}
}
private void CacheValidateHandler(HttpContext context, object data, ref HttpValidationStatus validationStatus)
{
validationStatus = OnCacheAuthorization(new HttpContextWrapper(context));
}
}
Then, my controller action looks like this:
[BasicAuthorize]
public ActionResult Calendar()
{
var userName = HttpContext.User.Identity.Name;
var appointments = GetAppointments(userName);
return new CalendarResult(appointments, "Appointments.ics");
}
I found this really helpful, but i hit a few problems during the development and i thought i would share some of them to help save other people some time.
I was looking to get data from my web application into the calendar for an android device and i was using discountasp as a hosting service.
The first problem i hit was that the validation did not work when uploaded to the server, stangely enough it was accepting my control panel login for discountasp but not my forms login.
The answer to this was to turn off Basic Authentication in IIS manager. This resolved the issue.
Secondly, the app i used to sync the calendar to the android device was called iCalSync2 - its a nice app and works well. But i found that it only worked properly when the file was delivered as a .ics (duh for some reason i put it as a .ical.. it must have been late) and i also had to choose the webcal option
Lastly i found i had to add webcal:// to the start of my url instead of http://
Also be careful as the code posted above ignores the roles input variable and always passes nothing so you might need to do some role based checks inside your calendar routine or modify the code above to process the roles variable.

ASP.NET MVC 3 Anti-Forgery protection for multiportal app hosted on one local server

I have 2 websites, actually they are the same. The purpose is in that one of them is for internet users and the second one is for local use.
Now they are hosted on the same IIS server on my localhost machine.
When I open this two websites and trying to get action result which is marked with [ValidateAntiForgeryToken] I have an issue that in my cookies i have cookies for my localhost site and there is a cookie with name "RequestVerificationToken_Lw" which is anti-forgery protection key.
And the problem is in that both sites are using the same cookie to store this key. And so if did smth on one web site I get anti-forgery error when I'm trying to do smth on the other.
How can I change cookie domain or any other solutions to split cookies?
Thank You!
Well, lets see what the ValidateAntiforgeryTokenAttribute does (Reflector/ILSpy is your friend) :
public void OnAuthorization(AuthorizationContext filterContext)
{
if (filterContext == null)
{
throw new ArgumentNullException("filterContext");
}
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(null);
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
if (httpCookie == null || string.IsNullOrEmpty(httpCookie.Value))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData = this.Serializer.Deserialize(httpCookie.Value);
string text = filterContext.HttpContext.Request.Form[antiForgeryTokenName];
if (string.IsNullOrEmpty(text))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
AntiForgeryData antiForgeryData2 = this.Serializer.Deserialize(text);
if (!string.Equals(antiForgeryData.Value, antiForgeryData2.Value, StringComparison.Ordinal))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
string username = AntiForgeryData.GetUsername(filterContext.HttpContext.User);
if (!string.Equals(antiForgeryData2.Username, username, StringComparison.OrdinalIgnoreCase))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
if (!this.ValidateFormToken(antiForgeryData2))
{
throw ValidateAntiForgeryTokenAttribute.CreateValidationException();
}
}
Okay, it is obvious, that the cookie name for the token is made from application path:
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
So, you create your own filter, just copy-paste this code, and change that to respect also port (or whatever by what you distinguish your applications):
string antiForgeryTokenName2 = AntiForgeryData.GetAntiForgeryTokenName(filterContext.HttpContext.Request.ApplicationPath + filterContext.HttpContext.Request.Url.Port);
HttpCookie httpCookie = filterContext.HttpContext.Request.Cookies[antiForgeryTokenName2];
This way the cookie name ("RequestVerificationToken_Lw") will vary by port too.
And of course we cant forget to change this cookie name while creating the token too. Unfortunately, you will need to copy-paste "re-implement" 2 things here - first the AntiForgeryToken extension method to call your own AntiForgeryWorker, and then the AntiForgeryWorker itself - just override the method GetAntiForgeryTokenAndSetCookie, it is the same stuff as before :
string antiForgeryTokenName = AntiForgeryData.GetAntiForgeryTokenName(httpContext.Request.ApplicationPath);
Well, it seems a mess, and its definitely not a DRY solution, but if you really really want this, you can have it done in few minutes. Just use reflector and copy-paste :)

Cookie is not deleted

I am using the following code to set a cookie in my asp.net mvc(C#) application:
public static void SetValue(string key, string value, DateTime expires)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key, value) { Expires = expires };
_response.Cookies.Set(cookie);
}
I need to delete the cookies when the user clicks logout. The set cookie is not removing/deleting with Clear/Remove. The code is as below:
public static void Clear()
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
_request.Cookies.Clear();
_response.Cookies.Clear();
}
public static void Remove(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
if (_request.Cookies[key] != null)
{
_request.Cookies.Remove(key);
}
if (_response.Cookies[key] != null)
{
_response.Cookies.Remove(key);
}
}
I have tried both the above functions, but still the cookie exists when i try to check exist.
public static bool Exists(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_request = httpContext.Request;
_response = httpContext.Response;
return _request.Cookies[key] != null;
}
What may be problem here? or whats the thing i need to do to remove/delete the cookie?
Clearing the cookies of the response doesn't instruct the browser to clear the cookie, it merely does not send the cookie back to the browser. To instruct the browser to clear the cookie you need to tell it the cookie has expired, e.g.
public static void Clear(string key)
{
var httpContext = new HttpContextWrapper(HttpContext.Current);
_response = httpContext.Response;
HttpCookie cookie = new HttpCookie(key)
{
Expires = DateTime.Now.AddDays(-1) // or any other time in the past
};
_response.Cookies.Set(cookie);
}
The Cookies collection in the Request and Response objects aren't proxies for the cookies in the browser, they're a set of what cookies the browser sends you and you send back. If you remove a cookie from the request it's entirely server side, and if you have no cookies in the response you're just not going to send any thing back to the client, which won't change the set of cookies in the browser at all.
To delete a cookie, make sure that it is in the response cookie collection, but has an expiration time in the past.
Just to add something else I also pass the value back as null e.g.
public static void RemoveCookie(string cookieName)
{
if (HttpContext.Current.Response.Cookies[cookieName] != null)
{
HttpContext.Current.Response.Cookies[cookieName].Value = null;
HttpContext.Current.Response.Cookies[cookieName].Expires = DateTime.Now.AddMonths(-1);
}
}
The best way to implement this is to use a tool like Reflector and see how the System.Web.Security.FormsAuthentication.SignOut method implements removing the authentication cookie.
In Reflector, open up System.Web and navigate to the FormsAuthentication object and find the SignOut method. Right click on it and select "Disassemble" (Choose your language from the menu).
VB.NET
Public Shared Sub SignOut()
FormsAuthentication.Initialize
Dim current As HttpContext = HttpContext.Current
Dim flag As Boolean = current.CookielessHelper.DoesCookieValueExistInOriginal("F"c)
current.CookielessHelper.SetCookieValue("F"c, Nothing)
If (Not CookielessHelperClass.UseCookieless(current, False, FormsAuthentication.CookieMode) OrElse current.Request.Browser.Cookies) Then
Dim str As String = String.Empty
If (current.Request.Browser.Item("supportsEmptyStringInCookieValue") = "false") Then
str = "NoCookie"
End If
Dim cookie As New HttpCookie(FormsAuthentication.FormsCookieName, str)
cookie.HttpOnly = True
cookie.Path = FormsAuthentication._FormsCookiePath
cookie.Expires = New DateTime(&H7CF, 10, 12)
cookie.Secure = FormsAuthentication._RequireSSL
If (Not FormsAuthentication._CookieDomain Is Nothing) Then
cookie.Domain = FormsAuthentication._CookieDomain
End If
current.Response.Cookies.RemoveCookie(FormsAuthentication.FormsCookieName)
current.Response.Cookies.Add(cookie)
End If
If flag Then
current.Response.Redirect(FormsAuthentication.GetLoginPage(Nothing), False)
End If
End Sub
With the above as an example, I was able to create a common method called RemoveCookie() in a shared assembly, code is below:
VB.NET
''' <summary>
''' Method to remove a cookie
''' </summary>
''' <param name="key">Key</param>
''' <remarks></remarks>
Public Shared Sub RemoveCookie(ByVal key As String)
' Encode key for retrieval and remove cookie
With HttpContext.Current
Dim cookie As New HttpCookie(.Server.UrlEncode(key))
If Not IsNothing(cookie) Then
With cookie
.HttpOnly = True
.Expires = New DateTime(&H7CF, 10, 12)
End With
' Remove from server (has no effect on client)
.Response.Cookies.Remove(.Server.UrlEncode(key))
' Add expired cookie to client, effectively removing it
.Response.Cookies.Add(cookie)
End If
End With
End Sub
Having tested this using FireBug and the Cookie add-in for FireBug (in FireFox), I can attest that the cookie immediately gets removed.
Any questions, feel free to message me.
After playing around with this for some time and trying all of the other answers here I discovered that none of the answers here are totally correct.
The part that is correct is that you have to send an expired cookie to effect the deletion. The part that nobody else picked up on (but is demonstrated in the Microsoft code posted by Ed DeGagne) is that the cookie options for the deletion must match exactly the cookie options that were used to set the cookie in the first place.
For example if you originally created the cookie with the HttpOnly option then you must also set this option when deleting the cookie. I expect the exact behavior will vary across browsers and probably over time, so the only safe option that will work long-term is to make sure that all of the cookie options in the deletion response match exactly the cookie options used to create the cookie originally.
Response.Cookies["key"].Expires= DateTime.Now;

Resources