Disappearing Cookie in MVC4 after creation in HttpModule - asp.net-mvc

I have created a module to handle authentication to Microsoft HealthVault. In the BeginRequest event handler I am checking for the authToken and when it's received, I am creating a new cookie to hold the user's information for use later on in the controller.
I am losing the cookie but maybe I am looking in the wrong place.
The event handler take an HttpApplication as the only parameter, so I add the cookie to application.Response.Cookies collection. The question comes down to: is this instance of HttpApplication a singleton? Is it the same as System.Web.HttpContext.Current.ApplicationInstance?
Perhaps though, cookies set at this point in the lifecycle are later wiped out. Is that what I am doing wrong?
Any help is greatly appreciated.
UPDATE
I have changed a few things since I posed the question. I am now handling PreRequestEventHandler in my HttpModule. I am letting HealthVault's WebApplicationUtilities object do the cookie creation and storage, though conceptually nothing has really changed. I am not having any trouble creating the cookie or reading it the first time after it's created.
Here is my cookie creation code in the HttpModule event handler:
static void PreRequestHandlerExecute(Object sender, EventArgs e)
{
HttpContext ctx = System.Web.HttpContext.Current;
string authToken = ctx.Request.Params["wctoken"];
if (!String.IsNullOrEmpty(authToken))
{
personInfo = WebApplicationUtilities.GetPersonInfo(authToken);
WebApplicationUtilities.SavePersonInfoToCookie(ctx, personInfo);
NameValueCollection query = HttpUtility.ParseQueryString(ctx.Request.Url.Query);
query.Remove("wctoken");
query.Remove("suggestedtokenttl");
UriBuilder newUrl = new UriBuilder(ctx.Request.Url);
newUrl.Query = query.ToString();
//app.Response.Redirect(newUrl.Uri.OriginalString);
}
}
Notice that the redirect is commented. On the first request (after auth) the cookie is created and my default action is able to then read it using LoadPersonInfoFromCookie(). I found that doing the redirect caused the cookie not to be sent to the client.
I have also noticed that on the subsequent requests, the cookie is not present in the Request.Cookies collection, so when LoadPersonInfoFromCookie() runs, I end up with a null. Strangely I can see the cookie in the Response object, but the contents are empty.
Here is the action code, just because...
public HttpContext Context
{
get { return System.Web.HttpContext.Current; }
}
public ActionResult Dashboard()
{
try
{
HealthVaultAccountModel model = new HealthVaultAccountModel();
PersonInfo personInfo = WebApplicationUtilities.LoadPersonInfoFromCookie(Context);
if (personInfo != null)
model.PersonName = personInfo.Name;
return View(model);
}
catch (Exception ex)
{
return RedirectToAction("Index", "Error");
}
}
UPDATE
Here is the cookie from the Immediate Window. I even bumped the Expires time out 30 days.
In the default action right after it is created
Context.Request.Cookies["_wcpage"]
{System.Web.HttpCookie}
Domain: null
Expires: {8/13/2012 5:24:02 PM}
HasKeys: true
HttpOnly: true
Name: "_wcpage"
Path: "/"
Secure: true
Shareable: false
Value: "p=1:1234-pVTbctowEP0V..."
Values: {p=1%3a1234-pVTbctowEP0V...}
In the action on the next request
Context.Request.Cookies["_wcpage"]
null
Interestingly the cookie is defined in the response object, but the value is gone and the expires time is reset.
Context.Response.Cookies["_wcpage"]
{System.Web.HttpCookie}
Domain: null
Expires: {1/1/0001 12:00:00 AM}
HasKeys: false
HttpOnly: false
Name: "_wcpage"
Path: "/"
Secure: false
Shareable: false
Value: ""
Values: {}

How are you creating/fetching the cookie? The SavePersonInfoToCookie and LoadPersonInfoFromCookie methods on the WebApplicationUtilities class will do it for you.
BeginRequest:
// given an authToken from the querystring/post values
var personInfo = WebApplicationUtilities.GetPersonInfo(authToken);
WebApplicationUtilities.SavePersonInfoToCookie(application.Context, personInfo);
Controller:
var personInfo = WebApplicationUtilities.LoadPersonInfoFromCookie(HttpContext);

Related

.ASPNET.Cookies cookie is not creating during Azure AD authentication [duplicate]

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.

ServiceStack authentication request fails

I am trying to set up authentication with my ServiceStack service by following this tutorial.
My service is decorated with the [Authenticate] attribute.
My AppHost looks like this:
public class TestAppHost : AppHostHttpListenerBase
{
public TestAppHost() : base("TestService", typeof(TestService).Assembly) { }
public static void ConfigureAppHost(IAppHost host, Container container)
{
try
{
// Set JSON web services to return idiomatic JSON camelCase properties.
ServiceStack.Text.JsConfig.EmitCamelCaseNames = true;
// Configure the IOC container
IoC.Configure(container);
// Configure ServiceStack authentication to use our custom authentication providers.
var appSettings = new AppSettings();
host.Plugins.Add(new AuthFeature(() =>
new AuthUserSession(), // use ServiceStack's session class but fill it with our own data using our own auth service provider
new IAuthProvider[] {
new UserCredentialsAuthProvider(appSettings)
}));
}
}
where UserCredentialsAuthProvider is my custom credentials provider:
public class UserCredentialsAuthProvider : CredentialsAuthProvider
{
public override bool TryAuthenticate(IServiceBase authService, string userName, string password)
{
try
{
// Authenticate the user.
var userRepo = authService.TryResolve<IUserRepository>();
var user = userRepo.Authenticate(userName, password);
// Populate session properties.
var session = authService.GetSession();
session.IsAuthenticated = true;
session.CreatedAt = DateTime.UtcNow;
session.DisplayName = user.FullName;
session.UserAuthName = session.UserName = user.Username;
session.UserAuthId = user.ID.ToString();
}
catch (Exception ex)
{
// ... Log exception ...
return false;
}
return true;
}
}
In my user tests I initialize and start my TestAppHost on http://127.0.0.1:8888, then use JsonServiceClient to authenticate itself to the service like so:
var client = new JsonServiceClient("http://127.0.0.1:8888/")
var response = client.Send<AuthResponse>(new Auth
{
provider = UserCredentialsAuthProvider.Name,
UserName = username,
Password = password,
RememberMe = true
});
But getting the following exception:
The remote server returned an error: (400) Bad Request.
at System.Net.HttpWebRequest.GetResponse()
at ServiceStack.ServiceClient.Web.ServiceClientBase.Send[TResponse](Object request)...
The ServiceStack.ServiceInterface.Auth.Auth request contains the correct username and passsword, and the request is being posted to:
http://127.0.0.1:8888/json/syncreply/Auth
I am not sure why the URL is not /json/auth/credentials or what I might be doing wrong. Any suggestions?
UPDATE
Tracing the chain of events up the stack I found the following:
JsonDataContractSerializer.SerializeToStream correctly serializes the Auth request into Json. However, the System.Net.HttpRequestStream passed to JsonDataContractDeserializer by EndpointHandlerBase has a stream of the correct length that is filled with nulls (zero bytes). As a result, the request object passed to CredentialsAuthProvider.Authenticate has nulls in all its properties.
How can the HTTP stream get stripped of its data?
Got it!!!
The problem was the following pre-request filter that I added for logging purposes in TestAppHost.Configure:
PreRequestFilters.Add((httpReq, httpRes) =>
{
LastRequestBody = httpReq.GetRawBody();
});
as seen here.
When the GetRawBody() method reads the request InputStream it leaves it in the EOS state, and all subsequent read attempts return nothing.
So obviously GetRawBody() can only be safely used with buffered streams, but unfortunately it quietly causes a very nasty bug instead of throwing an exception when used with a non-buffered stream.

Extend AuthorizeAttribute to detect logged in non-user (How to handle user authorization)

Environment: ASP.NET MVC 4, Visual Studio 2012
The [Authorize] attribute verifies that the user has a valid login cookie, but it does NOT verify that the user actually exists. This would happen if a user is deleted while that user's computer still holds the persisted credentials cookie. In this scenario, a logged-in non-user is allowed to run a controller action marked with the [Authorize] attribute.
The solution would seem to be pretty simple: Extend AuthorizeAttribute and, in the AuthorizeCore routine, verify that the user exists.
Before I write this code for my own use, I'd like to know if someone knows of a ready-to-go solution to this gaping hole in the [Authorize] attribute.
You need a special authentication global action filter.
Solution to your problem is the following. You have to introduce the global action filter that will be executed before controller action is invoked. This event is named OnActionExecuting. And within this global action filter you can also handle the scenario that user have a valid auth cookie, but does not exists in persistence (DB) anymore (and you have to remove its cookie).
Here is the code example to get an idea:
public class LoadCustomPrincipalAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
CustomIdentity customIdentity;
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
UserData userData = UserRepository.GetUserByName(HttpContext.Current.User.Identity.Name);
if (userData == null)
{
//TODO: Add here user missing logic,
//throw an exception, override with the custom identity with "false" -
//this boolean means that it have IsAuthenticated on false, but you
//have to override this in CustomIdentity!
//Of course - at this point you also remove the user cookie from response!
}
customIdentity = new CustomIdentity(userData, true);
}
else
{
customIdentity = new CustomIdentity(new UserData {Username = "Anonymous"}, false);
}
HttpContext.Current.User = new CustomPrincipal(customIdentity);
base.OnActionExecuting(filterContext);
}
}
Hope it helps to you!
Do not forget to register this action filter as a global one. You can do this like:
private static void RegisterGlobalFilters(GlobalFilterCollection filters)
{
filters.Add(new LoadCustomPrincipalAttribute());
}
Just to add this. Leave alone AuthorizeAttribute. It should work as it was meant. It simply check the HttpContext.Current.User.Identity.IsAuthenticated == true condition. There are situations that you would need to overide it, but this is not the one. You really need a proper user/auth handling before even AuthorizeAttribute kicks in.
Agreed with Peter. Here is what I did for an AngularJs app. Create an attribute that checks the lockout date. Change YourAppUserManager out with the correct one.
public class LockoutPolicyAttribute : ActionFilterAttribute
{
public override async Task OnActionExecutingAsync(HttpActionContext actionContext, CancellationToken cancellationToken)
{
if (HttpContext.Current.User.Identity.IsAuthenticated)
{
var now = DateTime.UtcNow;
var currentUserId = Convert.ToInt32(HttpContext.Current.User?.Identity?.GetUserId());
var user = await HttpContext.Current.GetOwinContext().GetUserManager<YourAppUserManager>().FindByIdAsync(currentUserId);
if (user?.LockedOutUntil >= now)
{
actionContext.Response = actionContext.Request.CreateErrorResponse((HttpStatusCode)423, "Account Lockout");
return;
}
}
base.OnActionExecuting(actionContext);
}
}
Then have an AngularJs intercept service for status code 423 to redirect to login page.
switch (response.status) {
case 423: //Account lockout sent by the server.
AuthService.logOut();
window.location.href = '/login';

Concurrent requests: session lost after removing session-id from one single request

In my ASP .NET MVC 2 - application, there are several controllers, that need the session state. However, one of my controllers in some cases runs very long and the client should be able to stop it.
Here is the long running controller:
[SessionExpireFilter]
[NoAsyncTimeout]
public void ComputeAsync(...) //needs the session
{
}
public ActionResult ComputeCompleted(...)
{
}
This is the controller to stop the request:
public ActionResult Stop()
{
...
}
Unfortunately, in ASP .NET MVC 2 concurrent requests are not possible for one and the same user, so my Stop-Request has to wait until the long running operation has completed. Therefore I have tried the trick described in this article and added the following handler to Global.asax.cs:
protected void Application_BeginRequest()
{
if (Request.Url.AbsoluteUri.Contains("Stop") && Request.Cookies["ASP.NET_SessionId"] != null)
{
var session_id = Request.Cookies["ASP.NET_SessionId"].Value;
Request.Cookies.Remove("ASP.NET_SessionId");
...
}
}
This simply removes the session-id from the Stop-Request. At the first glance this works well - the Stop-Request comes through and the operation is stopped. However, after that, it seems that the session of the user with the long running request has been killed.
I use my own SessionExpireFilter in order to recognize session timeouts:
public class SessionExpireFilterAttribute : ActionFilterAttribute
{
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
HttpContext ctx = HttpContext.Current;
// check if session is supported
if (ctx.Session != null)
{
// check if a new session id was generated
if (ctx.Session.IsNewSession)
{
// If it says it is a new session, but an existing cookie exists, then it must
// have timed out
string sessionCookie = ctx.Request.Headers["Cookie"];
if ((null != sessionCookie) && (sessionCookie.IndexOf("ASP.NET_SessionId") >= 0))
{
filterContext.Result = new JsonResult() { Data = new { success = false, timeout = true }, JsonRequestBehavior = JsonRequestBehavior.AllowGet };
}
}
}
base.OnActionExecuting(filterContext);
}
}
ctx.Session.IsNewSession is always true after the Stop-Request has been called, but I don't know why. Does anyone know why the session is lost? Is there any mistake in the implementation of the Stop-Controller?
The session is lost because you removed the session cookie. I'm not sure why that seems illogical. Each new page request supplies the cookie to asp.net, and if there is no cookie it generates a new one.
One option you could use to use cookieless sessions, which will add a token to the querystring. All you need to do is generate a new session for each login, or similar.
But this is one of the reasons why session variables are discouraged. Can you change the code to use an in-page variable, or store the variable in a database?

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