Mocking "ASP.NET_SessionId" cookie - c#

I am trying to come up with a solution for the following scenario:
I have migrated a web app from forms auth, to OWIN+OAuth.
Now, there is also a mobile client app ( android ). The mobile client is authenticated against the web app, if everything goes well the server will respond with the user's data and it used to respond with 2 cookies as well, one been the session cookie ( never used, but its presence was checked on the client ) and the other one was the user's identity cookie. The mobile client validates that the user's data "look good" and it also checks for the presence of those 2 cookies in order to pass the login screen.
What i am trying to do right now is to keep the mobile app unaffected by the changes on the server, by supplying the user's data as needed after all the required checks, and then mocking the presence of the 2 cookies, so that i can avoid making any changes on the mobile apps ( avoid the need to compile and update the clients that is )
The code below is implemented in an API controller, and while the .ASPXAUTH cookie seems to work fine , the old ASP.NET_SessionId cookie seems uncontrollable.
The code below will never respond with the ASP.NET_SessionId cookie to the client, although the code is the same as it is for the other cookie, which will completely obey either .Add/.Set/.Remove on the collection.
if (HttpContext.Current.Response.Cookies.AllKeys.Contains("ASP.NET_SessionId"))
HttpContext.Current.Response.Cookies.Set(new HttpCookie("ASP.NET_SessionId", "1st") { HttpOnly = false });
else
HttpContext.Current.Response.Cookies.Add(new HttpCookie("ASP.NET_SessionId", "1st") { HttpOnly = false });
if (HttpContext.Current.Response.Cookies.AllKeys.Contains(".ASPXAUTH"))
HttpContext.Current.Response.Cookies.Set(new HttpCookie(".ASPXAUTH", "2nd") { HttpOnly = false });
else
HttpContext.Current.Response.Cookies.Add(new HttpCookie(".ASPXAUTH", "2nd") { HttpOnly = false });
Is there any reason that i am unable to return a "custom cookie" named ASP.NET_SessionId ?
I have removed all logic related to forms auth, so that cookie is not generated by default anymore, but it looks like i can not fake return it either.
Thanks in advance

Related

Accessing HttpOnly cookies across multiple domain with URL redirection

I have an ASP.Net MCV website (.net 4.6) (https://site.aa.main.com) and we have built a redirection to an Angular SAP (https://spa.aa.new.main.com). We also have a standalone API (.net core 3.1)(https://api.aa.new.main.com:5001) to serve requests from the SPA.
Here I need to set a cookie in site 1 before the redirection, then I can use that cookie in the API.
I have the below code in site 1 to set this cookie,
HttpCookie payidCookie = new HttpCookie("myKey", "myValue")
{
Secure = true,
HttpOnly = true,
Domain = ".new.main.com",
};
this.Response.Cookies.Add(payidCookie);
Then I have the below code to consume the cookie in the API,
if (Request.Cookies["myKey"] != null)
{
var value = Request.Cookies["myKey"];
}
But the cookie is not available in the API. Request.Cookies["myKey"] return null.
Does anyone know why I cannot see the cookie in the API and how to fix this issue?
Thanks.
This case is explicitly covered in specification, site.aa.main.com cannot set cookies for new.main.com:
The user agent will reject cookies unless the Domain attribute
specifies a scope for the cookie that would include the origin
server. For example, the user agent will accept a cookie with a
Domain attribute of "example.com" or of "foo.example.com" from
foo.example.com, but the user agent will not accept a cookie with a
Domain attribute of "bar.example.com" or of "baz.foo.example.com".
You would either have to set cookie for main.com or figure out other naming setup that would have common subdomain for all involved hosts.

OpenIdConnectProtocolValidationContext.Nonce was null

HI can someone please help imgetting below error when calling outlook rest api
IDX21323: RequireNonce is '[PII is hidden by default. Set the 'ShowPII' flag in IdentityModelEventSource.cs to true to reveal it.]'. OpenIdConnectProtocolValidationContext.Nonce was null, OpenIdConnectProtocol.ValidatedIdToken.Payload.Nonce was not null. The nonce cannot be validated. If you don't need to check the nonce, set OpenIdConnectProtocolValidator.RequireNonce to 'false'. Note if a 'nonce' is found it will be evaluated.
aka IDX21323 points towards losing the nonce cookie (set by the initial Challenge call). Inspect your initial SignIn call (or WebForms postback SignIn) and confirm that you have a OpenIdConnect.nonce cookie actually set (Chrome network tab).
If not, I suspect that you have the same issue we had, which is that the OWIN Middleware sets the cookie, but its content gets accidentally overwritten by some other cookie modifications of your legacy application.
This is very likely a bug of the OWIN middleware (see ASP.NET_SessionId + OWIN Cookies do not send to browser), as it handles cookies through its own OwinContext and Cookie representation implementation, which is not in sync with the standard HttpContext.
How to fix when you have the initial nonce cookie missing:
We avoided any cookie changes during the SignIn request -> therefore the OWIN middleware can read/write its cookies with no interference.
When setting the nonce cookie running on localhost (non-secure) in a Chromium based browser, it's blocked because of SameSite=none and it not being secure. The fix for this case is to change localhost to use SSL (use https on asp.net application running on localhost) and update the Azure AD redirect URL to match.
In a WebForms app I got the same error when I used my machine name in the project url, but used "localhost" as my login redirect url. When I set them both to localhost the problem went away.
If your tenant was created on or after October 22nd, 2019, it’s possible you are experiencing the new secure-by-default behavior and already have security defaults enabled in your tenant.
How to Fix :- goto your Azure AD account => properties => on tab Access management for Azure resources => enable this tab to Yes.

ASP.NET Identity: Not updating cookie after modifying claims

I'm having issues with updating claims on ASP.Net Identity 2.2.1 in .Net 4.6.2 / MVC5.
After updating a claim it will normally send an updated cookie to the browser and everything works fine but sometimes no set cookie header is sent to the browser.
I've not been able to identify any pattern as to when it happens other than when it is failing, the server is sending a
Persistent-Auth: true
http header value for every response in the session. I don't know what causes this header value to get set and it sometimes appears mid-session and once it starts sending it, it will be sent for the rest of the session and trying to update the claims will never work again for that session.
As far as I can see, I have hard-coded the isPersistent parameter to be false in every call into ASP.Net identity and I can't see anything else that could be related to this header.
The code I'm using for updating claims is
public static void UpdateClaims(List<Claim> claims)
{
var authenticationManager = HttpContext.Current.GetOwinContext().Authentication;
var newIdentity = new ClaimsIdentity(HttpContext.Current.User.Identity);
foreach (Claim claim in claims)
{
Claim oldClaim = newIdentity.FindFirst(claim.Type);
if (oldClaim != null && oldClaim.Type != "")
{
newIdentity.RemoveClaim(oldClaim);
}
newIdentity.AddClaim(claim);
}
authenticationManager.AuthenticationResponseGrant = new AuthenticationResponseGrant
(new ClaimsPrincipal(newIdentity), new AuthenticationProperties { IsPersistent = false });
}
This is being called from an MVC action method.
Does anyone have any suggestions what might be going wrong or even just a starting point of where to look? I don't know what causes that persistent-auth header but it looks to be related to the problem; whether it is the cause or a symptom of the problem, I don't know.
I'm using ASP.Net Identity 2.2.1 with .Net 4.6.2.
I'm running on Windows Server 2012R2 and the problem seems to occur with IE11, Chrome and Firefox.
I'm using Fiddler 4.6.3 to view the http headers / responses.
Update:
I have noticed that it seems to go wrong only when Windows authentication is enabled. My server has a setting that allows username/password, windows auth or both (user can choose to sign in as a different user using username/password). When windows auth is used, I initially authenticate the user using windows and then set a cookie, which I then use for all future requests in the session.
If windows auth is disabled, updating the claims like this always works. If windows auth is enabled, updating the claims usually works.
First, you're conflating two different things, although it's understandable since they're named similarly. The IsPeristent setting determines whether the cookie is a session-cookie or persistent cookie. In other words: it determines whether or not the cookie will expire when the browser is closed or at some predetermined time, whether or not the browser is closed.
The Persistent-Auth header is an optimization header that informs the client that it doesn't necessarily need to authorize each request. It has nothing to do with the IsPersistent flag.
Claims are set at login. Period. If you need to update the claims, you must sign the user out and sign them back in. This can be done programmatically (i.e. without user intervention), but it must be done. In other words, if you need to alter a claim, and you need that alteration to be available in the next request, then you follow it with:
Identity 2.0
AuthenticationManager.SignOut();
await SignInManager.SignInAsync(user);
Identity 3.0
await SignInManager.RefreshSignInAsync(user);
Instead of
authenticationManager.AuthenticationResponseGrant =
new AuthenticationResponseGrant(new ClaimsPrincipal(newIdentity),
new AuthenticationProperties { IsPersistent = false });
you should use
authenticationManager.SignIn(
new AuthenticationProperties { IsPersistent = false },
new ClaimsPrincipal(newIdentity));
I found the problem. It was using the wrong identity when it tried to update the claims. In my scenario there were two identity objects, one for windows authentication and one for cookie authentication. In most cases HttpContext.Current.User.Identity gets the cookie authentication object (which is the one with the claims) but occasionally it was giving me the windows authentication object, so when I tried to update the claims on that, it didn't do anything.
The problem was solved by replacing
var newIdentity = new ClaimsIdentity(HttpContext.Current.User.Identity);
with
ClaimsIdentity oldIdentity = claimsPrincipal.Identities.FirstOrDefault(i => i.AuthenticationType == "ApplicationCookie");
var newIdentity = new ClaimsIdentity(oldIdentity);
It now seems to work solidly without needing to sign out / back in again.
I guess the Persistent-Auth: true http header was being sent when OWin considered the Windows auth to be the primary identity so that is why its presence correlated with the inability to update claims.
I believe in our case it was the wrong/long-term IoC lifetime/'scope' of the ApplicationUserManager & possibly the same for the role manager too.

Cross-Domain OWIN Authentication for Multi-Tenanted ASP.NET MVC Application

I am using OWIN Authentication for a Multi-Tenant ASP.NET MVC application.
The application and authentication sits on one server in a single application but can be accessed via many domains and subdomains. For instance:
www.domain.com
site1.domain.com
site2.domain.com
site3.domain.com
www.differentdomain.com
site4.differentdomain.com
site5.differentdomain.com
site6.differentdomain.com
I would like to allow a user to login on any of these domains and have their authentication cookie work regardless of which domain is used to access the application.
This is how I have my authentication setup:
public void ConfigureAuthentication(IAppBuilder Application)
{
Application.CreatePerOwinContext<RepositoryManager>((x, y) => new RepositoryManager(new SiteDatabase(), x, y));
Application.UseCookieAuthentication(new CookieAuthenticationOptions
{
CookieName = "sso.domain.com",
CookieDomain = ".domain.com",
LoginPath = new PathString("/login"),
AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
Provider = new CookieAuthenticationProvider
{
OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<UserManager, User, int>(
validateInterval: TimeSpan.FromMinutes(30),
regenerateIdentityCallback: (manager, user) => user.GenerateClaimsAsync(manager),
getUserIdCallback: (claim) => int.Parse(claim.GetUserId()))
}
});
Application.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);
}
I have also explicitly set a Machine Key for my application in the root web.config of my application:
<configuration>
<system.web>
<machineKey decryption="AES" decryptionKey="<Redacted>" validation="<Redacted>" validationKey="<Redacted>" />
</system.web>
</configuration>
Update
This setup works as expected when I navigate between domain.com and site1.domain.com, but now it is not letting me login to differentdomain.com.
I understand that cookies are tied to a single domain. But what is the easiest way of persisting a login across multiple domains? Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
Since you need something simple, consider this. In your particular setup, where you really have just one app accessible by multiple domain names, you can make simple "single sign on". First you have to choose single domain name which is responsible for initial authentication. Let's say that is auth.domain.com (remember it's just domain name - all your domains still point to single application). Then:
Suppose user is on domain1.com and you found he is not logged-in (no cookie). You direct him to auth.domain.com login page.
Suppose you are logged-in there already. You see that request came from domain1.com (via Referrer header, or you can pass domain explicitly). You verify that is your trusted domain (important), and generate auth token like this:
var token = FormsAuthentication.Encrypt(
new FormsAuthenticationTicket(1, "username", DateTime.Now, DateTime.Now.AddHours(8), true, "some relevant data"));
If you do not use forms authentication - just protect some data with machine key:
var myTicket = new MyTicket()
{
Username = "username",
Issued = DateTime.Now,
Expires = DateTime.Now.AddHours(8),
TicketExpires = DateTime.Now.AddMinutes(1)
};
using (var ms = new MemoryStream()) {
new BinaryFormatter().Serialize(ms, myTicket);
var token = Convert.ToBase64String(MachineKey.Protect(ms.ToArray(), "auth"));
}
So basically you generate your token in the same way asp.net does. Since your sites are all in the same app - no need to bother about different machine keys.
You redirect user back to domain1.com, passing encrypted token in query string. See here for example about security implications of this. Of course I suppose you use https, otherwise no setup (be it "single sign on" or not) is secure anyway. This is in some ways similar to asp.net "cookieless" authentication.
On domain1.com you see that token and verify:
var ticket = FormsAuthentication.Decrypt(token);
var userName = ticket.Name;
var expires = ticket.Expiration;
Or with:
var unprotected = MachineKey.Unprotect(Convert.FromBase64String(token), "auth");
using (var ms = new MemoryStream(unprotected)) {
var ticket = (MyTicket) new BinaryFormatter().Deserialize(ms);
var user = ticket.Username;
}
You create cookie on domain1.com using information you received in token and redirect user back to the location he came from initially.
So there is a bunch of redirects but at least user have to type his password just once.
Update to answer your questions.
Yes if you find that user is authenticated on domain1.com you redirect to auth.domain.com. But after auth.domain.com redirects back with token - you create a cookie at domain1.com as usual and user becomes logged-in a domain1.com. So this redirect happens just once per user (just as with usual log in).
You can make request to auth.domain.com with javascript (XmlHttpRequest, or just jquery.get\post methods). But note you have to configure CORS to allow that (see here for example). What is CORS in short? When siteB is requested via javascript from siteA (another domain) - browser will first ask siteB if it trusts siteA to make such requests. It does so with adding special headers to request and it wants to see some special headers in response. Those headers you need to add to allow domain1.com to request auth.domain.com via javascript. When this is done - make such request from domain1.com javascript to auth.domain.com and if logged in - auth.domain.com will return you token as described above. Then make a query (again with javascript) to domain1.com with that token so that domain1.com can set a cookie in response. Now you are logged in at domain1.com with cookie and can continue.
Why we need all this at all, even if we have one application just reachable from different domains? Because browser does not know that and treats them completely different. In addition to that - http protocol is stateless and every request is not related to any other, so our server also needs confirmation that request A and B made by the same user, hence those tokens.
Yes, HttpServerUtility.UrlTokenEncode is perfectly fine to use here, even better than just Convert.ToBase64String, because you need to url encode it anyway (you pass it in query string). But if you will not pass token in query string (for example you would use javascript way above - you won't need to url encode it, so don't use HttpServerUtility.UrlTokenEncode in that case.
You are right on how cookie works, but that it not how OWIN works.
Don't override the cookie domain of the Auth Server(auth.domain.com).
You may override the cookie domain of the individual sites to "site1.domain.com" and "site2.domain.com".
In your SSO page, let's say someone lands on site1.domain.com and since is unauthenticated is taken to your auth server. The auth server takes the login credentials and sends a code to site1.domain.com on the registered URI(eg: /oauthcallback). This endpoint on site1.domain.com will get an access token from the code and SignIn(automatically write the cookie). So 2 cookies are written one on auth.domain.com and second on site1.domain.com
Now, same user visits site2.domain.com and finds a cookie of logged in user on "auth.domain.com". This means that the user is logged in and a new cookie is created with same claims on "site2.domain.com"
User is now logged into both site.
You don't manually write the cookie. Use OwinContext.Signin and the cookie will be saved / created.
To answer the question on your update, there is no way of sharing cookies across different domains.
You could possibly use some query strings parameters and some server side logic to handle this particular case, but this could raise some security concerns.
Se this suggestion: https://stackoverflow.com/a/315141/4567456
Update
Following your comment, here are the details:
https://blog.stackoverflow.com/2010/09/global-network-auto-login/
https://meta.stackexchange.com/questions/64260/how-does-sos-new-auto-login-feature-work
http://openid.net/specs/openid-connect-session-1_0.html
Bonus:
The mechanism in use today is a bit different, and simpler, than what is discribed in the first two links above.
If you look at the network requests when you login on StackOverflow, you will see that it logs you in individually to each site on the network.
https://stackexchange.com/users/login/universal.gif?authToken=....
https://serverfault.com/users/login/universal.gif?authToken=...
https://askubuntu.com/users/login/universal.gif?authToken=...
etc, etc...
William,
I understand that cookies are tied to a single domain.
Yes and there is no way you can manipulate it on the client side. The browsers never send a cookie of one domain to another.
But what is the easiest way of persisting a login across multiple domains?
External Identity Provider or Security Token Service (STS) is the easiest way to achieve this. In this setup all the domains site1.com. site2.com etc will trust the STS as the identity provider. In this federated solution, the user authenticates with the STS and the federated identity is used across all the domains. Here is a great resource on this topic from an expert.
Is there a way for me to read a cookie from a different domain, decrypt it, and recreate a new cookie for the differentdomain.com?
With some tweaks you may achieve this federated solution with your current setup. FYI, this is not recommended or an in-use approach, but an idea to help you achieve the goal.
Lets say you have multiple domains 1, 2, 3 pointing to a single application. I will create another domain STS pointing to the same application but deals only with cookie creation and validation. Create a custom middleware that utilizes the asp.net cookie authentication middleware under the wrap. This gets executed only if the requests are for STS domain. This can be achieved with a simple if condition on the domain/ host or by using the Map on IAppBuilder interface.
Lets look at the flow:
a. The user tries to access a protected resource using domain 1
b. Since he is not authenticated, he will be redirected to domain STS, with a query parameter for domain1 (for STS to identify which domain he is accessing the resource from) and the url for the protected resource on domain1
c. As the request is for STS domain, the custom middleware kicks in and authenticates the user. And sends two cookies one for STS and the second one for whatever the domain (in this case 1) he is trying.
d. Now the user will be redirected to the protected resource on domain1
e. If he tries to access protected resource on domain 2, he is not autheticated hence will be redirected to STS.
f. Since he had an authentication cookie for STS that will be attached with this request to STS by the browser. The STS middleware can validate the cookie and can authenticate the user. If authenticate, issues another cookie for domain 2 and redirects him to the protected resource on domain2.
If you closely look at the flow it is similar to what we do when we have an external STS, but in our case the STS is our application. I hope this makes sense.
If I had to do this task, I would use an external STS sitting on the same host (IIS). IdentityServer, an opensource implementation of OpenID Connect standard, is what I would use as STS. It is extremely flexible in terms of usage and can be co-hosted with our application (which I think is great deal in your case). Here are links Identity server, Video
I hope that this is helpful. Please let me know if you have any questions.
Thank you,
Soma.

Browser occasionally losing HttpCookie for authentication after postback and redirect

This has been a nagging issue for some time, but very sporadic and difficult to isolate.
From time to time, browsers that have authenticated on a web application, have been open for a while, have logged in and out of the same web application multiple times, have multiple tabs, are pretty much any browser (Chrome, IE, Firefox, Safari), and seemingly at random, lose their ability to retain an AuthCookie after being set and followed by a redirect. Closing the browser and starting a new session resolves the issue, as does opening up a different browser and attempting to authenticate.
Our team uses forms authentication for all of our websites and web application. This is a pretty typical setup where a login form is displayed, the user enters credentials and a cookie is set on the click event of the postback, then a redirect occurs to the same page where the cookie is then referenced and used to complete authentication.
In this situation
FormsAuthentication.FormsCookieName = ".WebAuth"
Within Event:
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, Username, DateTime.Now, DateTime.Now.AddMinutes(SessionTimeout), false, Username);
HttpCookie faCookie = new HttpCookie(FormsAuthentication.FormsCookieName, FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(faCookie);
Response.Redirect(Request.RawUrl, true);
After the redirect, on PreInit:
HttpCookie authCookie = Request.Cookies[cookieName];
At this point, the authCookie variable is typically not null, but in these isolated circumstances that I've outlined above, the cookie comes back null after the redirect.
This happens very randomly, sometimes weeks before affecting one of our developers. As I said, restarting the browser resolves the issue.
Today I had it happen on our dev server while using Chrome. I had logged into the application, allowed the application to session timeout, and then attempted to login again. The attempted login then failed to set the cookie. I remotely attached Visual Studio to the process on the server to begin debugging. The entire time I could step through my code, even deploy new code versions to the server with updates, restart the app, restart IIS on the server, attach and reattach to the project, and the issue persisted in Chrome. In Firefox, I was able to authenticate without issue.
From Chrome, the login would validate, attempt to set a Response Cookie as outlined above. Prior to redirect, I could see the properly set Response Cookie, as well as its counterpart in the Request Cookies. However, on each redirect after a seemingly successful login, the Response and Request Cookie are gone.
I enabled Trace on the application to view the cookie collection:
There is a .WebAuth in the Request Cookies Collection, as well as ASP.NET_SessionId and several ASPSESSIONIDxxxxxxxx, but when the page loads, only the ASP.NET_SessionId and ASPSESSIONIDxxxxxxxx cookies are available in the Request.Cookies scope, no sign of the .WebAuth. However, in the page's Trace information after render, there multiple .WebAuth cookies listed, it is just that the page seems to have no access to them.
Primarily, on a working version after authentication there is both a .WebAuth Response and Request Cookie in the page's Trace info. But on a non functioning browser window, the Response Cookie is absent.
Has anyone else had any experience with this? It is such a nagging issue, and so sporadic, but I would love to be able to resolve it. My concern is that it may be affecting users and we would have no knowledge since the description of the issue is so convoluted.
Based on your scenario you may be running into browser's restrictions on number of cookies per domain/total. The limits are relatively high, but exist (spec: http://www.ietf.org/rfc/rfc2109.txt, section 6.3 , some recent information - http://www.nczonline.net/blog/2008/05/17/browser-cookie-restrictions/)
If you get it happening again try to look at actual server responses (i.e. using Fiddler) to see if cookies are send to the browser. Check what cookies are set for the domain and current page (depending on browser there are different ways of doing it, in all browsers you can see some cookies by running following in address bar javascript:alert(document.cookie) )
This is a non-persistent cookie issue. Session simply times out.
Try changing false to true in this line:
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, Username, DateTime.Now, DateTime.Now.AddMinutes(SessionTimeout), true, Username);
Also add
faCookie.Expires = DateTime.Now.AddMinutes(SessionTimeout);
I have fall in similar situation, things was certain as you described. But, later the reason have been found.
ASP.NET and IIS understood MyApplication and myapplication as one equal, but browsers undertood them as different. So, when we set auth cookies for /MyApplication, they were not sent to the server, when we go to /myapplication.
The fix is next:
protected void Application_BeginRequest(object sender, EventArgs e)
{
string url = HttpContext.Current.Request.Url.PathAndQuery;
string application = HttpContext.Current.Request.ApplicationPath;
if (!url.StartsWith(application))
{
HttpContext.Current.Response.Redirect(application + url.Substring(application.Length));
Response.End();
return;
}
}

Categories