IsAuthenticated works on browser - but not with Air client! - asp.net

My login code, after authentication:
var authTicket = new FormsAuthenticationTicket(
1,
userName,
DateTime.Now,
DateTime.Now.AddMinutes(20), // expiry
false,
roles,
"/");
var cookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(cookie);
and, thanks to Darin Dimitrov, I have a custom Authorize attribute:
[AttributeUsage(AttributeTargets.Method | AttributeTargets.Class, Inherited = true, AllowMultiple = true)]
public class TJAuthorize : AuthorizeAttribute {
public override void OnAuthorization(AuthorizationContext filterContext) {
string cookieName = FormsAuthentication.FormsCookieName;
if (!filterContext.HttpContext.User.Identity.IsAuthenticated ||
filterContext.HttpContext.Request.Cookies == null || filterContext.HttpContext.Request.Cookies[cookieName] == null) {
HandleUnauthorizedRequest(filterContext);
return;
}
var authCookie = filterContext.HttpContext.Request.Cookies[cookieName];
var authTicket = FormsAuthentication.Decrypt(authCookie.Value);
string[] roles = authTicket.UserData.Split(',');
var userIdentity = new GenericIdentity(authTicket.Name);
var userPrincipal = new GenericPrincipal(userIdentity, roles);
filterContext.HttpContext.User = userPrincipal;
base.OnAuthorization(filterContext);
}
This all works beautifully when I'm working in a browser session. But now I am working with a Flash/Adobe Air client, and the authentication attribute is causing a failure. By putting debug statements into the code, I can tell that:
filterContext.HttpContext.User.Identity.IsAuthenticated
is false - even after a successful login!
Why should there be any difference between using a browser client and an Air client? And how do I fix this?
EDIT: Another clue: after putting in some more debug statements, I have found that the filterContext.HttpContext.User.Identity is not correctly set when making the call from Air - the Name property comes out blank! Session ID is correct, cookie ID is correct - but the User.Identity is not set. Any ideas why this might be happening?

Perhaps HttpCookieMode (http://msdn.microsoft.com/en-us/library/system.web.httpcookiemode.aspx) is set to the wrong value?
Default is UseDeviceProfile ... what happens when you force it to UseCookies ?

It's a longshot, but IsAuthenticated depends on client's ASPXAUTH cookie (or whatever you've named id) being sent with request. Make sure that flash/air is sending that cookie (by wireshark or any other network tool)

Does the HttpContext.User.Identity show up in the Application_AuthorizeRequest in global.asax?

Related

Asp.Net Identity with 2FA - remember browser cookie not retained after session

I'm using the latest sample code for MVC5.2 with Asp.Identity and Two Factor authentication.
With 2FA enabled, when a user logins, the get prompted for a code (sent by phone or email) and they have the option to "Remember Browser" - so that they don't get ask for codes again on that browser.
This is handled in the VerifyCode action
var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
Note that model.RememberMe is not used in the default templates so it is false.
I find when I do this the .AspNet.TwoFactorRememberBrowser that gets set, expires on session end (so it does not remember the browser)
Now if I set isPersistent = true, .AspNet.TwoFactorRememberBrowser gets an expiration of 30 days which is great, but the .AspNet.ApplicationCookie also gets a 30 day expiration - which means that when I close the browser and re-open, I am automatically logged in.
I want it so that it doesn't persist my login, but that it will persist my choice of remembering the 2FA code. Ie the user should always have to login, but they should not be asked for a 2fa code if they have already save it.
Has anybody else seen this, or am I missing something?
It doesn't seem like this code was designed to set more than one identity cookie in the same request/response because the OWIN cookie handlers end up sharing the same AuthenticationProperties. This is because the AuthenticationResponseGrant has a single principal, but the principal can have multiple identities.
You can workaround this bug by altering and then restoring the AuthenticationProperties in the ResponseSignIn and ResponseSignedIn events specific to the 2FA cookie provider:
//Don't use this.
//app.UseTwoFactorRememberBrowserCookie(DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie);
//Set the 2FA cookie expiration and persistence directly
//ExpireTimeSpan and SlidingExpiration should match the Asp.Net Identity cookie setting
app.UseCookieAuthentication(new CookieAuthenticationOptions()
{
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = AuthenticationMode.Passive,
CookieName = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromHours(2),
SlidingExpiration = true,
Provider = new CookieAuthenticationProvider
{
OnResponseSignIn = ctx =>
{
ctx.OwinContext.Set("auth-prop-expires", ctx.Properties.ExpiresUtc);
ctx.OwinContext.Set("auth-prop-persist", ctx.Properties.IsPersistent);
var issued = ctx.Properties.IssuedUtc ?? DateTimeOffset.UtcNow;
ctx.Properties.ExpiresUtc = issued.AddDays(14);
ctx.Properties.IsPersistent = true;
},
OnResponseSignedIn = ctx =>
{
ctx.Properties.ExpiresUtc = ctx.OwinContext.Get<DateTimeOffset?>("auth-prop-expires");
ctx.Properties.IsPersistent = ctx.OwinContext.Get<bool>("auth-prop-persist");
}
}
});
Make sure to set the same ExpireTimeSpan and SldingExpiration as your main Asp.Net Identity cookie to preserve those settings (since they get merged in the AuthenticationResponseGrant).
This still appears to be an issue in Identity 2.2.1 (It may be fixed in Asp.Net Identity 3.0 - but that is currently pre-released and requires a later version of .Net framework that 4.5)
The following work around seems ok for now:
The cookie is getting set on the SignInManager.TwoFactorSignInAsync with the wrong values, so on Success of the VerifyCode action, I reset the cookie to be persistent and give it the expiry date that I wish (in this case I set it to a year)
public async Task<ActionResult> VerifyCode(VerifyCodeViewModel model)
{
if (!ModelState.IsValid)
{
return View(model);
} var result = await SignInManager.TwoFactorSignInAsync(model.Provider, model.Code, isPersistent: model.RememberMe, rememberBrowser: model.RememberBrowser);
switch (result)
{
case SignInStatus.Success:
// if we remember the browser, we need to adjsut the expiry date as persisted above
// Also set the expiry date for the .AspNet.ApplicationCookie
if (model.RememberBrowser)
{
var user = await UserManager.FindByIdAsync(await SignInManager.GetVerifiedUserIdAsync());
var rememberBrowserIdentity = AuthenticationManager.CreateTwoFactorRememberBrowserIdentity(user.Id);
AuthenticationManager.SignIn(new AuthenticationProperties { IsPersistent = true, ExpiresUtc = DateTime.UtcNow.AddDays(365) }, rememberBrowserIdentity);
}
return RedirectToLocal(model.ReturnUrl);
What you can do is assign your own CookieManager class that modifies the expiration time of the TwoFactorRememberBrowserCookie. This seems better than modifing the cookie in Application_PostAuthenticateRequest.
This works around the problem that you can either persist all or none of the authentication cookies.
Put this in your ConfigureAuth, the last line sets your custom cookie manager.
public void ConfigureAuth(IAppBuilder app)
{
// left out all but the modified initialization of the TwoFactorRememberBrowserCookie
var CookiePrefix = ".AspNet.";
app.UseCookieAuthentication(new CookieAuthenticationOptions {
AuthenticationType = DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Passive,
CookieName = CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie,
ExpireTimeSpan = TimeSpan.FromDays(14),
CookieManager = new TwoFactorRememberBrowserCookieManager()
});
}
Use this CookieManager class only for the TwoFactorRememberBrowserCookie.
When you do not persist cookies in TwoFactorSignInAsync, unfortunately the ExpirationTimeout is ignored.
So just set it again in the CookieManager (This is a modified version of the cookie manager coming from Microsoft.Owin.Infrastructure.CookieManager):
public class TwoFactorRememberBrowserCookieManager : Microsoft.Owin.Infrastructure.ICookieManager
{
string CookiePrefix = ".AspNet.";
Microsoft.Owin.Infrastructure.ICookieManager cm = new Microsoft.Owin.Infrastructure.ChunkingCookieManager();
public void AppendResponseCookie(IOwinContext context, string key, string value, CookieOptions options)
{
if (key == CookiePrefix + DefaultAuthenticationTypes.TwoFactorRememberBrowserCookie) {
options.Expires = DateTime.UtcNow.AddDays(14);
}
cm.AppendResponseCookie(context, key, value, options);
}
public void DeleteCookie(IOwinContext context, string key, CookieOptions options)
{
cm.DeleteCookie(context, key, options);
}
public string GetRequestCookie(IOwinContext context, string key)
{
return cm.GetRequestCookie(context, key);
}
}
This is what you will get:
Works for me that way.

How to add FormsAuthentication cookie to HttpClient HttpRequestMessage

I am trying to authenticate inside integration test by calling FormsAuthentication.SetAuthCookie("someUser", false);
After that I do need to call WebAPI and not receive unauthorized exception because I have authorized attribute applied.
I am using this code to create auth cookie :
var cookie = FormsAuthentication.GetAuthCookie(name, rememberMe);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration,
ticket.IsPersistent, userData.ToJson(), ticket.CookiePath);
var encTicket = FormsAuthentication.Encrypt(newTicket);
/// Use existing cookie. Could create new one but would have to copy settings over...
cookie.Value = encTicket;
Now I want to add this cookie to HttpRequestMessage inside new HttpClient and send this with my regular request in integration test.
I don't know how to add that auth cookie to HttpRequestMessage ?
For manipulating cookies, you need to use WebRequestHandler along with HttpClient. For example,
var handler = new WebRequestHandler();
var client = new HttpClient(handler);
// use handler to configure request such as add cookies to send to server
CookieContainer property will allow to access cookies collection.
On different note, I doubt if creating FormsAuthentication cookie on client will work. A same encryption key would be needed on both client/server. The best approach would be to replay the login request for actual web API - most probably, it would be a POST to login page with user credentials. Observe the same over browser using tool such as Fiddler and construct the same request within your http client.
Almost 6 years late, but still may be helpful. The solution based on this one:
https://blogs.taiga.nl/martijn/2016/03/10/asp-net-web-api-owin-authenticated-integration-tests-without-authorization-server/
First, while creating Owin TestServer you have to create DataProtector:
private readonly TestServer _testServer;
public IDataProtector DataProtector { get; private set; }
public Server(OwinStartup startupConfig)
{
_testServer = TestServer.Create(builder =>
{
DataProtector = builder.CreateDataProtector(
typeof(CookieAuthenticationMiddleware).FullName, DefaultAuthenticationTypes.ApplicationCookie, "v1");
startupConfig.Configuration(builder);
});
}
Then generate cookie like this (use DataProtector created in previous step):
public string GeterateCookie()
{
var claims = new List<Claim>
{
new Claim(ClaimTypes.Role, "your-role"),
new Claim(ClaimTypes.UserData, "user-data"),
new Claim(ClaimTypes.Name, "your-name")
};
var identity = new ClaimsIdentity(claims, DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
var tdf = new TicketDataFormat(DataProtector);
var ticket = new AuthenticationTicket(identity, new AuthenticationProperties {ExpiresUtc = DateTime.UtcNow.AddHours(1)});
var protectedCookieValue = tdf.Protect(ticket);
var cookie = new CookieHeaderValue("yourCookie", protectedCookieValue)
{
Path = "/",
HttpOnly = true
};
return cookie.ToString();
}
Make sure to set required claims, initialize ClaimsIdentity according to settings provided to UseCookieAuthentication method, and setting correct CookieName.
The last step is to add CookieHeader to your request:
public Task<HttpResponseMessage> RequestAsync(HttpRequestMessage request)
{
request.Headers.Add("cookie", GenerateCookie());
return _client.SendAsync(request);
}

FormsAuthentication - handling a change of username

My ASP.NET MVC web application allows administrators to change their own, or other users' usernames.
Users are logged in by calling FormsAuthentication.SetAuthCookie(userName [string], createPersistentCookie [bool]). They are logged out by calling FormsAuthentication.SignOut(). I understand that after updating the username I'd need to sign them out and back in again. But how do I retrieve the existing value of createPersistentCookie? e.g. how do I retain their original 'remember me' setting when signing them back in?
var cookieName = FormsAuthentication.FormsCookieName;
var request = HttpContext.Current.Request;
var cookie = request.Cookies.Get(cookieName);
if (cookie == null)
return;
try
{
var ticket = FormsAuthentication.Decrypt(cookie.Value);
//This should give you what you want...
bool isPersistent = ticket.IsPersistent;
}
catch (Exception ex)
{
//Logging
}

How can I create persistent cookies in ASP.NET?

I am creating cookies with following lines:
HttpCookie userid = new HttpCookie("userid", objUser.id.ToString());
userid.Expires.AddYears(1);
Response.Cookies.Add(userid);
Now how can I make it persistent?
If I visit the same page again after closing the browser, I'm unable to get it back.
Here's how you can do that.
Writing the persistent cookie.
//create a cookie
HttpCookie myCookie = new HttpCookie("myCookie");
//Add key-values in the cookie
myCookie.Values.Add("userid", objUser.id.ToString());
//set cookie expiry date-time. Made it to last for next 12 hours.
myCookie.Expires = DateTime.Now.AddHours(12);
//Most important, write the cookie to client.
Response.Cookies.Add(myCookie);
Reading the persistent cookie.
//Assuming user comes back after several hours. several < 12.
//Read the cookie from Request.
HttpCookie myCookie = Request.Cookies["myCookie"];
if (myCookie == null)
{
//No cookie found or cookie expired.
//Handle the situation here, Redirect the user or simply return;
}
//ok - cookie is found.
//Gracefully check if the cookie has the key-value as expected.
if (!string.IsNullOrEmpty(myCookie.Values["userid"]))
{
string userId = myCookie.Values["userid"].ToString();
//Yes userId is found. Mission accomplished.
}
Although the accepted answer is correct, it does not state why the original code failed to work.
Bad code from your question:
HttpCookie userid = new HttpCookie("userid", objUser.id.ToString());
userid.Expires.AddYears(1);
Response.Cookies.Add(userid);
Take a look at the second line. The basis for expiration is on the Expires property which contains the default of 1/1/0001. The above code is evaluating to 1/1/0002. Furthermore the evaluation is not being saved back to the property. Instead the Expires property should be set with the basis on the current date.
Corrected code:
HttpCookie userid = new HttpCookie("userid", objUser.id.ToString());
userid.Expires = DateTime.Now.AddYears(1);
Response.Cookies.Add(userid);
FWIW be very careful with storing something like a userid in a cookie unencrypted. Doing this makes your site very prone to cookie poisoning where users can easily impersonate another user. If you are considering something like this I would highly recommend using the forms authentication cookie directly.
bool persist = true;
var cookie = FormsAuthentication.GetAuthCookie(loginUser.ContactId, persist);
cookie.Expires = DateTime.Now.AddMonths(3);
var ticket = FormsAuthentication.Decrypt(cookie.Value);
var userData = "store any string values you want inside the ticket
extra than user id that will be encrypted"
var newTicket = new FormsAuthenticationTicket(ticket.Version, ticket.Name,
ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, userData);
cookie.Value = FormsAuthentication.Encrypt(newTicket);
Response.Cookies.Add(cookie);
Then you can read this at any time from an ASP.NET page by doing
string userId = null;
if (this.Context.User.Identity.IsAuthenticated)
{
userId = this.Context.User.Identity.Name;
}
As I understand you use ASP.NET authentication and to set cookies persistent you need to set FormsAuthenticationTicket.IsPersistent = true
It is the main idea.
bool isPersisted = true;
var authTicket = new FormsAuthenticationTicket(
1,
user_name,
DateTime.Now,
DateTime.Now.AddYears(1),//Expiration (you can set it to 1 year)
isPersisted,//THIS IS THE MAIN FLAG
addition_data);
HttpCookie authCookie = new HttpCookie(FormsAuthentication.FormsCookieName, authTicket );
if (isPersisted)
authCookie.Expires = authTicket.Expiration;
HttpContext.Current.Response.Cookies.Add(authCookie);
You need to add this as the last line...
HttpContext.Current.Response.Cookies.Add(userid);
When you need to read the value of the cookie, you'd use a method similar to this:
string cookieUserID= String.Empty;
try
{
if (HttpContext.Current.Request.Cookies["userid"] != null)
{
cookieUserID = HttpContext.Current.Request.Cookies["userid"];
}
}
catch (Exception ex)
{
//handle error
}
return cookieUserID;
//add cookie
var panelIdCookie = new HttpCookie("panelIdCookie");
panelIdCookie.Values.Add("panelId", panelId.ToString(CultureInfo.InvariantCulture));
panelIdCookie.Expires = DateTime.Now.AddMonths(2);
Response.Cookies.Add(panelIdCookie);
//read cookie
var httpCookie = Request.Cookies["panelIdCookie"];
if (httpCookie != null)
{
panelId = Convert.ToInt32(httpCookie["panelId"]);
}

How to set Request.IsAuthenticated to true when not using FormsAuthentication.RedirectFromLoginPage?

I am using Form Authentication and sending an Aajx request to the server for authentication. Based on the json result, the client decides where to go and what to do. That is the reason I am not using FormsAuthentication.RedirectFromLoginPage to not interfere the ajax/json response.
In this case Request.IsAuthenticated returns false, even after validating the user with Membership.ValidateUser. Then I set the cookie using
FormsAuthentication.SetAuthCookie(username, false);
Although the second parameter, persistent cookie, is false, the cookie is still valid across browser sessions.
Any idea how to make Request.IsAuthenticated work without using FormsAuthentication.RedirectFromLoginPage?
You need to update the current security principal for the request. When you call Response. Redirect(...) a new request is done and the security principal is reinitialized and Request.IsAuthenticated returns true in your case. FormsAuthentication.RedirectFromLoginPage internally calls Response. Redirect(...). You can manually renew the security principal for the current request like this:
public void RenewCurrentUser()
{
System.Web.HttpCookie authCookie =
System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = null;
authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
FormsAuthenticationTicket newAuthTicket = authTicket;
if (FormsAuthentication.SlidingExpiration)
{
newAuthTicket = FormsAuthentication.RenewTicketIfOld(authTicket);
}
string userData = newAuthTicket.UserData;
string[] roles = userData.Split(',');
System.Web.HttpContext.Current.User =
new System.Security.Principal.GenericPrincipal(new FormsIdentity(newAuthTicket), roles);
}
}
}
FormsAuthentication.SetAuthCookie
Method Creates an authentication
ticket for the supplied user name and
adds it to the cookies collection of
the response, or to the URL if you are
using cookieless authentication.
Ref: msdn
Have a look at the Forms Authentication Control Flow. The authentication cookie is set to the response cookie collection, and should be observable at the http protocol level (e.g. use FireCookie or Fiddler2 to verify this).
Membership only verifies a username/password. Neither Membership nor SetAuthCookie() will modify the current request. They expect to send the cookie back to the caller, and the next request is when the properties like IsAuthenticated will return true.
Note that you can override and extend these automatic processes using custom IIdentity and IPrincipal, and hook into the authentication events if you need to.
Also have a look at Using Forms Authentication with ASP.NET AJAX
Redirecting after a POST is best practice, and should be considered the correct solution.
In some cases, you may still want to find out whether a user is authenticated within the scope of the authentication request (for instance if you are running additional logic after the authentication was performed which is shared with other requests).
In this case, you can reset the value of Request.IsAuthenticated with the following code:
// set the forms auth cookie
FormsAuthentication.SetAuthCookie(username, createPersistentCookie);
// reset request.isauthenticated
var authCookie = System.Web.HttpContext.Current.Request.Cookies[FormsAuthentication.FormsCookieName];
if (authCookie != null)
{
FormsAuthenticationTicket authTicket = FormsAuthentication.Decrypt(authCookie.Value);
if (authTicket != null && !authTicket.Expired)
{
var roles = authTicket.UserData.Split(',');
System.Web.HttpContext.Current.User = new System.Security.Principal.GenericPrincipal(new FormsIdentity(authTicket), roles);
}
}
See post here: http://abadjimarinov.net/blog/2010/01/24/RenewUserInTheSameRequestInAspdotNET.xhtml
we can use this simply
FormsAuthentication.SetAuthCookie(username, true);

Resources