Share Session State Across Multiple Domains in .net ( NOT SUB Domains ) - c#

I am using ASP.net 3.5 w/ C# using SQL Server with Session State stored in the SQL Server DB.
I have an issue with losing session state across multiple domains, but using the same browser instance and same code base.
For example, We are directing customers to www.MyStore.com to browse our store and we want to send customers to www.MyStore.ShopPlatform.com to checkout w/ secure SSL validation. The session is being re-created when re-directed to www.MyStore.ShopPlatform.com.
The wildcard SSL is installed at www.ShopPlatform.com.
So, The question is.... How do you associate 1 session state cookie to multiple domains (www.MyStore.com and www.MyStore.ShopPlatform.com) on the same server using .net?

If you use cookies for session, you don't. The browser won't send cookies to domains that don't match, and you can't set a cookie for all domains. Even if you could, the session cookie is just a key to server-side session and that key doesn't mean anything without access to the session store. A third party won't have that.
If www.mystore.com and www.mystore.shopplatform.com happen to both be backed by machines you own, and both machines have access to the same session store, then you might be able to use a Cookieless ASP.NET Session config to make it work.
Generally speaking, you can't use session this way.

Since both domains are different, new sessions will always get created.
I suggest using a shared database approach.
Have a column created in users table that can store a random hash. A datatype of uniqueidentifier should do.
When redirecting user, generate a new GUID and store it for the current user and then append the same to the url being redirected. At the receiving domain, setup an endpoint page that will accept an URL encoded path and query string as ReturnUrl parameter and a tokenId parameter for the guid.
Here is a dummy code that will redirect the user to a public endpoint along with a dummy query string parameter. The code was hosted on domain1.local
Using ctx As New DataContexts.SBWebs
Dim u As DataEntities.User = (From usr In ctx.Users Where usr.User.Equals(Page.User.Identity.Name) Select usr).FirstOrDefault
If u Is Nothing Then Exit Sub
Dim id As Guid = Guid.NewGuid
u.Token = id
ctx.SubmitChanges()
Dim newPath As String = "/protected/?tick=" & Now.Ticks
Response.Redirect(String.Format("http://www.domain2.local/EndPoint.aspx?tokenid={0}&ReturnUrl={1}", id.ToString, HttpUtility.UrlEncode(newPath)))
End Using
And the EndPoint.aspx contains the following code..
Protected Sub Page_Load(ByVal sender As Object, ByVal e As System.EventArgs) Handles Me.Load
If Request.QueryString.HasKeys AndAlso Not String.IsNullOrEmpty(Request.QueryString("tokenid")) Then
Using ctx As New DataContexts.SBWebs
Dim usr As DataEntities.User = (From u In ctx.Users Where u.Token.Equals(Request.QueryString("tokenid"))).FirstOrDefault
If usr Is Nothing Then Exit Sub
usr.Token = Nothing
ctx.SubmitChanges()
FormsAuthentication.RedirectFromLoginPage(usr.User, False)
End Using
End If
End Sub
On Security Note: Once the cookie is created, remove the GUID from the table to protect against REPLAY attacks.
EDIT:
Just a word of caution. Don't send user to a ASP.Net protected page (where Deny="?" or something like that is given). Redirect the user to a public end-point that can handle the tokenid and after the setting the cookie, redirect to the intended page.

Related

Is It possible to serialize the session object in ASP Classic? [duplicate]

We have a website in classic asp that we are slowly migrating to ASP.NET as necessary.
The problem of course is how classic asp and ASP.NET handle Sessions. After spending the last few hours researching the web, I found many articles, but not one that stood out over the others.
Is there a best practice for transferring session variables from and to classic asp and asp.net? Security is a must and any explanation with examples is much appreciated.
A simple bridge to pass a single session variable from classic asp to .net serverside (hiding your sessionvalue from the client), would be this:
On the ASP end: An asp page to output your session, call it e.g. asp2netbridge.asp
<%
'Make sure it can be only called from local server '
if (request.servervariables("LOCAL_ADDR") = request.servervariables("REMOTE_ADDR")) then
if (Request.QueryString("sessVar") <> "") then
response.write Session(Request.QueryString("sessVar"))
end if
end if
%>
On the .net end, a remote call to that asp page. :
private static string GetAspSession(string sessionValue)
{
HttpWebRequest _myRequest = (HttpWebRequest)WebRequest.Create(new Uri("http://yourdomain.com/asp2netbridge.asp?sessVar=" + sessionValue));
_myRequest.ContentType = "text/html";
_myRequest.Credentials = CredentialCache.DefaultCredentials;
if (_myRequest.CookieContainer == null)
_myRequest.CookieContainer = new CookieContainer();
foreach (string cookieKey in HttpContext.Current.Request.Cookies.Keys)
{
' it is absolutely necessary to pass the ASPSESSIONID cookie or you will start a new session ! '
if (cookieKey.StartsWith("ASPSESSIONID")) {
HttpCookie cookie = HttpContext.Current.Request.Cookies[cookieKey.ToString()];
_myRequest.CookieContainer.Add(new Cookie(cookie.Name, cookie.Value, cookie.Path, string.IsNullOrEmpty(cookie.Domain)
? HttpContext.Current.Request.Url.Host
: cookie.Domain));
}
}
try
{
HttpWebResponse _myWebResponse = (HttpWebResponse)_myRequest.GetResponse();
StreamReader sr = new StreamReader(_myWebResponse.GetResponseStream());
return sr.ReadToEnd();
}
catch (WebException we)
{
return we.Message;
}
}
I've used an ajax bridge (for want of a better term), specifically, a classic asp page that reads all session vars into a database with a guid, it then redirects to a .net page passing the guid in the querystring, the asp.net page reads from sql for the given guid and created those vars as sessions.
Eg, in classic asp (pseudocode code - just to give you an idea, use parameterised queries in yours etc):
'#### Create GUID
Dim GUID 'as string
GUID = CreateWindowsGUID() '#### Lots of methods on http://support.microsoft.com/kb/320375
'#### Save session to sql
For Each SessionVar In Session.Contents
db.execute("INSERT INTO SessionBridge (GUID, Key, Value) VALUES ('" & GUID & "', '" & SessionVar & "', '" & session(SessionVar) & "')")
Next
Then, in a .net page:
'#### Fetch GUID
Dim GUID as string = Request.QueryString("GUID")
session.clear
'#### Fetch from SQL
db.execute(RS, "SELECT * FROM SessionBridge WHERE GUID = '" & GUID & "'")
For Each db_row as datarow in RS.rows
Session(db_row("Key")) = db_row("Value")
Next
As i say, this is very rough pseudocode, but you can call the asp with a simple background ajax function, then call the .net page for the given GUID.
This has the advantage of not exposing all your vars and values to the client (as post methods do etc).
They use different sessions, so you'll need to devise some way of transferring the vars yourself. You could include them in cookies, or send them via HTTP POST (i.e. a form with hidden fields) to the asp.net side.
Alternatively, you could scrap using session storage and stick everything in a database for each user/session, then just pass a session key from classic ASP to ASP.NET via one of the above suggestions. I know this sounds like you're reinventing the wheel, but this might be one of those cases where you just can't get around it.
I have a website that does that exact action. The secret is to use an intermediate page with the asp function response.redirect
<%
client_id = session("client_id")
response.redirect "aspx_page.aspx?client_id=" & client_id
%>
This is an example to pull the classic asp session variable client_id and pass it to an aspx page. Your aspx page will need to process it from there.
This needs to be at the top of a classic asp page, with no HTML of any type above. IIS will process this on the server and redirect to the aspx page with the attached query string, without sending the data to the client machine. It's very fast.
In case anyone else stumbles here looking for some help, another possible option is to use cookies. The benefit of a cookie is that you can store reasonable amounts of data and then persist that data to your .Net application (or another web application). There are security risks with exposing a cookie since that data can be easily manipulated or faked. I would not send sensitive data in a cookie. Here the cookie is only storing a unique identifier that can be used to retrieve data from a table.
The approach:
Grab the session data you need from your classic asp page. Store
this data in a table along with a unique hash and a timestamp.
Store the value of the hash in a short-lived cookie.
Redirect to whatever page you need to go and read the hash in the cookie.
Check that the hash in the cookie hasn't expired in the database. If it is valid, send back the data you need to your page and then expire the hash so it can't be reused.
I used a SHA 256 hash that was a combination of the user's unique identifier, session ID, and current timestamp. The hash is valid for only a few minutes and is expired upon reading. The idea is to limit the window for an attacker who would need to guess a valid hash before it expired.

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 populate user(Identity) roles for a Web application when roles are stored in a SQL Server database

I have a C# based asp.net application which does a form based authentication and also needs authorization.
Here is the simplified version of the User table (SQL Server)
UID UName PasswordHash Userroles
----------------------------------------------
1 a GERGERGEGER Proivder;Data Entry
2 b WERGTWETWTW HelpDSK; UserNamager
...
...
I'm quite familiar with the Authentication part. But for Authorization I am not sure what is the best way:
I know once user is Authorized, you can use the Identity object to get his/her info.
The question is what my choice to read the logged in user's roles on every page other than call that DB table every time to get them?
I am not sure this is a SQL Server question. This is an ASP.NET question.
ASP.NET forms authentication allows the application to define a "Principal" which (among other things) contains an array of strings known as "roles." You can populate the roles from the DB one time (when the user signs on) then serialize the principal into the forms authentication ticket, which becomes an encrypted cookie on the browser. ASP.NET decodes the cookie with each http request and provides it to your ASP.NET c# code via HttpContext.User. It can then retrieve the roles from context and never needs to talk to the DB again.
Storing the roles would look something like this:
string roles = "Admin,Member";
FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(
1,
userId, //user id
DateTime.Now,
DateTime.Now.AddMinutes(20), // expiry
false, //do not remember
roles,
"/");
HttpCookie cookie = new HttpCookie(FormsAuthentication.FormsCookieName,
FormsAuthentication.Encrypt(authTicket));
Response.Cookies.Add(cookie);

Get Username with ASP.NET Generic Hanlder

I'm trying to get username of currely logged in user by ASP.NET Generic Handler (.ashx). But the username is always nothing even though the user is currently logged in.
Here's my Generic Handler class:
Imports System.Web.SessionState
Public Class FileUploadHandler
Implements System.Web.IHttpHandler
Implements IRequiresSessionState
Sub ProcessRequest(ByVal context As HttpContext) Implements IHttpHandler.ProcessRequest
'username is always null, i'm not sure why there's nothing eventhough the user is logged in
Dim username = context.Current.User.Identity.Name
If String.IsNullOrEmpty(username) Then
context.Response.StatusCode = 0
Else
context.Response.StatusCode = 1
End If
End Sub
ReadOnly Property IsReusable() As Boolean Implements IHttpHandler.IsReusable
Get
Return False
End Get
End Property
End Class
But I can get the username of the logged in user from any page like this:
Dim username = Context.Current.User.Identity.Name
Do you think what it is the problem here?
I'm OK with both C# and VB.NET. Thanks.
If you are using any client side component such as Uploadify, Plupload, it can be that the component is not sending authentication and session cookies with the request. There is a good explanation here for workaround.
Check out Uploadify (Session and authentication) with ASP.NET
The default ASP.NET application built up uses cookie to authenticate users via session-id which is written in the cookie itself, while I think it should be the session object that is to deal with this. Therefore the default created MVC application is not safe to follow. you'd better change the source code into using session instead.
For your issue, getting username from the user class might not be a good option as it will return an empty string. If you use session this problem will be resolved once you use the identity from the session object.

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.

Categories