Can't set FormsAuthenicationTicket.UserData in cookieless mode - c#

I'm trying to implement the "Writing Information to UserData" section of this article, but it doesn't work properly when the cookie is part of the URI.
My code:
// Create the cookie that contains the forms authentication ticket
HttpCookie authCookie = FormsAuthentication.GetAuthCookie( userName, createPersistantCookie );
// Get the FormsAuthenticationTicket out of the encrypted cookie
FormsAuthenticationTicket ticket = FormsAuthentication.Decrypt( authCookie.Value );
// Create a new FormsAuthenticationTicket that includes our custom User Data
FormsAuthenticationTicket newTicket = new FormsAuthenticationTicket( ticket.Version, ticket.Name, ticket.IssueDate, ticket.Expiration, ticket.IsPersistent, "foo");
// Update the authCookie's Value to use the encrypted version of newTicket
authCookie.Value = FormsAuthentication.Encrypt( newTicket );
// Manually add the authCookie to the Cookies collection
HttpContext.Current.Response.Cookies.Add( authCookie );
// Determine redirect URL and send user there
string redirUrl = FormsAuthentication.GetRedirectUrl( userName, createPersistantCookie );
HttpContext.Current.Response.Redirect( redirUrl, false );
When cookieless is used, the page redirects but doesn't get the correct URI with the cookie information in it, so it loops back to my Login page where Request.IsAuthenticated returns false. An endless loop ensues.
How do I redirect to the proper URI?

I found this to be an interesting problem, so I set about doing some digging, testing, and a little bit of debugging into the .net framework source.
Basically, what you are trying to do will not work. Anything you put into the Response.Cookies collection will just be ignored if the browser doesn't support cookies. You can check Request.Browser.Cookies to see if cookies are supported.
In asp.net, both session state and authentication support a cookieless mode, but this does not extend to other cookies. In fact, it seems that session and authentication can be set to different modes of operation themselves even.
The authentication system can store it's own data in the URI, but it does so by directly manipulating the URI itself. Sadly, Microsoft doesn't appear to have exposed these capabilities to code outside the authentication module.
Basically, if you use the methods like FormsAuthentication.GetAuthCookie() and FormsAuthentication.SetAuthCookie() then the authentication system will take care of putting that information into the URI for you automagically... but it doesn't allow you to supply a customized authentication ticket to these methods... so you are stuck with the default auth ticket.In these cases, you are on your own for storing any custom data.
Anyway...
There really isn't much advantage to storing custom data directly in an authentication ticket if the authentication system has gone cookieless... in cookieless mode, things like "persistant cookie" have no meaning so you'll be regenerating the data at least once per session anyway.
The most common suggestion for cases where you are cookieless but still need custom data like this is to enable cookieless sessions, and just store your custom data as a session variable. The session ID will get put into the URI, but the custom data will stay in memory on the server. The usage pattern is identical no matter if your sessions are cookieless or not.
If you really wanted to, you could come up with a system of storing the custom data in the URI manually. The easiest thing to do would be to put the custom data into query strings or use pathdata. I can't see any real advantage to this over sessions variables unless you are just deperate not to use server memory (adding a little memory to a server is cheap, ugly URLs and manually writing code to deal with them is not cheap).

Thank you for the great explanation, Stephen. In cases where the user does not allow cookies, I'm just going to have to avoid the UserData and load the data from the database.
Before the code listed above I'll do:
if( !HttpContext.Current.Request.Browser.Cookies || !FormsAuthentication.CookiesSupported )
{
FormsAuthentication.RedirectFromLoginPage( userName, false);
return;
}

Related

ASP .NET Core using InMemory Cache per user

I have a system where at some point, the user will be locked to a single page. In this situation his account his locked and he cannot be redirected to any other page and this is after authentication.
The verification is done using Page Filters accessing database. To improve performance I have used memory cache.
However, the result wasn't as expected because once the cache is used for a single user it will affect all the others.
As far as i know, you can separate caching using tag helpers per user but I have no idea if this is possible using code
public async Task<IActionResult> Iniciar(int paragemId, string paragem)
{
var registoId = Convert.ToInt32(User.GetRegistoId());
if (await _paragemService.IsParagemOnGoingAsync(registoId))
{
return new JsonResult(new { started = false, message = "Já existe uma paragem a decorrer..." });
}
else
{
await _paragemService.RegistarInicioParagemAsync(paragemId, paragem, registoId);
_registoService.UpdateParagem(new ProducaoRegisto(registoId)
{
IsParado = true
});
await _registoService.SaveChangesAsync();
_cache.Set(CustomCacheEntries.RecordIsParado, true, DateTimeOffset.Now.AddHours(8));
return new JsonResult(new { started = true, message = "Paragem Iniciada." });
}
}
here i only check first if the user account is blocked in the database first without checking cache first and then create the cache entry.
Every user will be locked because of this.
So my point is... Is there a way to achieve this like tag helpers?
The CacheTagHelper is different than cache in general. It works via the request and therefore can vary on things like headers or cookie values. Just using MemoryCache or IDistributedCache directly is low-level; you're just adding values for keys directly, so there's nothing here to "vary" on.
That said, you can compose your key using something like the authenticated user's id, which would then give each user a unique entry in the cache, i.e. something like:
var cacheKey = $"myawesomecachekey-{User.FindFirstValue(ClaimTypes.NameIdentifier)}";
Short of that, you should use session storage, which is automatically unique to the user, because it's per session.
There are several alternatives to the cache. For details please see this link that describes them in greater detail.
Session State
An alternative would be to store the value in session state. This way, the session of one user does not interfere with the ones of others.
However, there are some downsides of this approach. If the session state is kept in memory, you cannot run your application in a server farm because one server does not know of the others session memory. So you would need to save the session state in a cache (REDIS?) or a database.
In addition, as session memory is stored in the server users cannot change it and avoid the redirection that you try to implement. The downside is that this reduces the amount of users that your server can handle because the server needs to have a specific amount of memory per user.
Cookies
You can send a cookie to the client and check for this cookie when the next request arrives at your server. The downside of this approach is that the user can delete the cookie. If the only consequence of a missing cookie is a request to the database, this is neglectable.
You can use session cookies that are discarded by the server when the session expires.
General
Another hint is that you need to clear the state memory when a user signs out so that with the next sign in, the state is correctly set up for the new user.

How to make sure the user's original request URL is used after WS Federated authentication in OWIN app

I have an OWIN app that authenticates users with cookie authentication, and if that fails then it tries WS-Federated authentication. In other words, if the correct cookie isn't there in the initial request, then go to the STS and fetch the security token.
The problem I'm having is making sure the user's original URL request is fulfilled if federated authentication is used. Thanks to this post, I realized that the redirect URL from the STS back to the OWIN app contains the "original" URL in the wctx query parameter. The issue is that this value is set, as far as I can tell, using WsFederationAuthenticationOptions.Wtrealm (perhaps Wreply?). This is a problem because these values are set in the initial configuration. I don't want a hard coded value -- I just want the URL the user originally used (i.e. IOwinContext.Request.Uri). The documentation for Wtrealm and Wreply does not help explain what the values should be.
I thought I could sneakily set Wtrealm to the user's request URL before redirecting to the STS, but apparently it's already set by the time RedirectToIdentityProvider notification is raised:
RedirectToIdentityProvider = context =>
{
context.Options.Wtrealm = context.Request.Uri.ToString();
}
Does anyone know what the correct approach is? Is there a way to make Wtrealm in the initial configuration the user's request URL? Or is Wtrealm not what I think it is, and I should be approaching this in a different way?
I believe I figured it out. Wtrealm, as I suspected, isn't what's causing the issue; that is used to determine where the STS should reenter to finish the federated authentication, but there is another redirect URL that determines where to go after authenticating.
I dug through the Microsoft.Owin.Security.WsFederation source code using ILSpy, and I figured out that there was a property I was not setting: AuthenticationProperties.RedirectUri. This RedirectUri property needs to be set before redirecting to the STS, as it needs to be used later in InvokeAsync after authenticating. I have an AuthenticateAllRequests method and I initialize this AuthenticationProperties object there:
private static void AuthenticateAllRequests(IAppBuilder app, params string[] authenticationTypes)
{
app.Use((context, continuation) =>
{
if (context.Authentication.User?.Identity?.IsAuthenticated ?? false)
{
return continuation();
}
else
{
AuthenticationProperties authProps = new AuthenticationProperties
{
RedirectUri = context.Request.Uri.ToString()
};
context.Authentication.Challenge(authProps, WsFederationAuthenticationDefaults.AuthenticationType);
return Task.CompletedTask;
}
});
}
To summarize more concretely: let's say my OWIN startup URL is http://myapp.com/owin. So then I set Wtrealm = "http://myapp.com/owin". Let's say a user goes to http://myapp.com/owin/coolstuff, and let's say they are signed into the authentication server with SSO, but they don't have the cookie for my app. In this case WS-Federated authentication needs to be used. http://myapp.com/owin/coolstuff is set as AuthenticationProperties.RedirectUri before redirecting to the STS so that it is put in the wctx query parameter, and can therefore be used after returning back to the OWIN app.
I was getting confused before because after returning from the STS, I would see IOwinRequest.Uri = "http://myapp.com/owin", and I would assume that was the final URL, and that the user's original request was lost. Now it makes a lot more sense that that is the URL the STS redirects to in order to finish authentication, but the final redirect URL, http://myapp.com/owin/coolstuff, is stored for redirecting after authentication finishes.
I am pretty sure this is how it works, but I may be wrong and if anyone has anything to add I'd love to hear it.

Create cookie with cross domain

I am working on cookies. I am able to create cookies very easily. To create a cookie I am using this code:
HttpCookie aCookie = new HttpCookie("Cookie name");
aCookie.Value = "Value";
Response.Cookies.Add(aCookie);
This code is fine for me and it gives me localhost as Host. But the problem comes here when I try to add a domain name here like:
HttpCookie aCookie = new HttpCookie("Cookie name");
aCookie.Value = "Value";
aCookie.Domain = "192.168.0.11";
Response.Cookies.Add(aCookie);
Now the cookie is not generated. Any suggestions?
You can only set the domain to yourself (the current site) and sub-domains of yourself, for security reasons. You can't set cookies for arbitrary sites.
As Marc has said - you can't do this; unless the domain is a subdomain of the one returning the response.
The same limitation applies to javascript code on the client adding cookies as well - the same origin policy will apply.
A simple way to achieve this is generally to include on the page returned from abc.com somewhere a reference to a resource on the domain xyz.com - typically a javascript file or something like that.
You have to watch out there, though, because that's a third-party cookie and some users will have those disabled (because it's how ad-tracking works).
Assuming you have a known set of cookies you want to track across domains and that you own all the domains you are sharing cookies with, you can build this functionality yourself. Here is poor man's cross-domain cookie tracking:
You can add "?favoriteColor=red" to all links on abc.com that point to xyz.com.
XYZ Contact
Then do the same thing for all links on xyz.com that point to abc.com.
ABC Contact
Now on every page of abc.com and xyz.com need to check the http request path for ?favoriteColor=red and if it exists, set the favoriteColor cookie on that domain to red.
// Pseudocode
if(queryString["favoriteColor"] != null) {
setCookie("favoriteColor", queryString["favoriteColor"]);
}
Tip 1: Do some validation to ensure that the value you get is valid because users can enter anything.
Tip 2: You should be using https if you're going to do this.
Tip 3: Be sure to url escape your cookie name and value in the url.

How do I use the cookie container with RestSharp and ASP.NET sessions?

I'd like to be able to call an authentication action on a controller and if it succeeds, store the authenticated user details in the session.
However, I'm not sure how to keep the requests inside the session as I'm using RestSharp as a detached client. I need to somehow get a key back from the server on successful authorisation and then for each future call, check the key with that stored in the session.
How do I ensure the RestClient in RestSharp sends all future requests with the cookie set correctly so inside service calls, the session variable can be retrieved correctly?
I've been looking at the cookie container with HttpFactory but there doesn't seem to be any documentation on this anywhere.
If someone is having a similar problem, please note that the above is not quite required for a simple "store my cookies after each request" problem.
Jaffas approach above works, but you can simply attach a CookieStore to your RestClient and have the cookies be stored automatically.
I know this is not a solution for everyone, since you might want to store dedicated cookies only. On the other hand it makes your life easier for testing a REST client!
(I used Jaffas variables for ease):
CookieContainer _cookieJar = new CookieContainer();
var client = new RestClient("http://<test-server>/letteron"); //test URL
client.CookieContainer = _cookieJar;
I worked this out in the end.
Basically create a cookie container, then add the session cookie from the response into the cookie container. All future requests will then contain this cookie.
var sessionCookie = response.Cookies.SingleOrDefault(x => x.Name == "ASP.NET_SessionId");
if (sessionCookie != null)
{
_cookieJar.Add(new Cookie(sessionCookie.Name, sessionCookie.Value, sessionCookie.Path, sessionCookie.Domain));
}

FormsAuthentication after login

Ok, i have simple scenario:
have two pages:
login and welcome pages.
im using FormsAuthentication with my own table that has four columns: ID, UserName, Password, FullName
When pressed login im setting my username like:
FormsAuthentication.SetAuthCookie(userName, rememberMe ?? false);
on the welcome page i cant use:
Page.User.Identity.Name
to provide to user which user currently logged, BUT i dont user username like at all examples in http://asp.net web site i want to user FullName field
i think that always go to db and request fullname when page loads its crazy and dont like to user Sessions or Simple Cookie mayby FormsAuth provider has custom fields for this
I would store the user's full name in the session cookie after your call to FormsAuth
FormsAuth.SetAuthCookie(userName, rememberme);
// get the full name (ex "John Doe") from the datbase here during login
string fullName = "John Doe";
Response.Cookies["FullName"].Value = fullName;
Response.Cookies["FullName"].expires = DateTime.Now.AddDays(30);
and then retrieve it in your view pages via:
string fullName = HttpContext.Current.Request.Cookies["FullName"].Value
Forms authentication works using cookies. You could construct your own auth cookie and put the full name in it, but I think I would go with putting it into the session. If you use a cookie of any sort, you'll need to extract the name from it each time. Tying it to the session seems more natural and makes it easy for you to access. I agree that it seems a waste to go back to the DB every time and I would certainly cache the value somewhere.
Info on constructing your own forms authentication cookie can be found here.
Sorry I'm a little late to the party, but here's how you can do this without storing the value anywhere else :)
var authCookieKey = FormsAuthentication.FormsCookieName;
var responseCookies = HttpContext.Current.Response.Cookies;
var requestCookies = HttpContext.Current.Request.Cookies;
var aspxAuthCookieInResponse = responseCookies.AllKeys.Contains(authCookieKey) ? responseCookies[authCookieKey] : null;
var aspxAuthCookieInRequest = requestCookies.AllKeys.Contains(authCookieKey) ? requestCookies[authCookieKey] : null;
// Take ASPXAUTH cookie from either response or request.
var cookie = aspxAuthCookieInResponse ?? aspxAuthCookieInRequest;
var authTicket = FormsAuthentication.Decrypt(cookie.Value); // Todo: Check for nulls.
// Using the name!
var userName = authTicket.Name;
There are no custom fields for forms authentication. You'll just have to use session. That's what it's there for you know. ;) Just don't forget - forms authentication cookie and session are two independant things. They even each have their own timeouts. So the session won't be reset when a user logs out unless you do so yourself.
What about using Profiles to store the extra info with the User?
The simplest option is to use the session. By default session state is stored in memory and will be lost when the ASP.NET worker process recycles, however you can configure it to use state service instead, which retains session info in a separate process:
http://msdn.microsoft.com/en-us/library/ms178586.aspx
Another option would be to use profiles. As it sounds like you already have 'profile' information stored in your own tables, you'd probably have to write a custom provider for it so it's a more complex solution.

Categories