WIF and Subdomains - c#

We have an existing ASP.NET application (WebForms) that uses home-grown authentication. We've been tasked with implementing a single sign-on solution and have chosen to use WIF.
We have a single instance of the application running and we identify the client by using a subdomain (e.g. client1.ourapp.com, client2.ourapp.com, etc). In the application code we strip off the first subdomain and that identifies the client.
We've been working with a WIF proof-of-concept to figure out how to get the user redirected back to the correct subdomain once they've authenticated. The out-of-the-box behavior seems to be that the STS redirects the user to whatever realm is identified in the config file. The following is the PoC config file. I'm using my hosts file to fake different clients (i.e. 127.0.0.1 client1.ourapp.com, 127.0.0.1 client2.ourapp.com).
<federatedAuthentication>
<wsFederation
passiveRedirectEnabled="true"
issuer="http://ourapp.com/SSOPOCSite_STS/"
realm="http://client1.ourapp.com"
requireHttps="false" />
</federatedAuthentication>
Obviously this isn't going to work because we can't redirect everyone to the same subdomain.
We think we've figured out how to handle this but would like some outside opinions on whether we're doing it the right way or whether we just got lucky.
We created an event handler for the FAM's RedirectingToIdentityProvider event. In it we get the company name from the request URL, build a realm string using the company name, set the Realm and HomeRealm of the SignInRequestMessage, then let the FAM do its thing (i.e. redirect us to the STS for authentication).
protected void WSFederationAuthenticationModule_RedirectingToIdentityProvider( object sender, RedirectingToIdentityProviderEventArgs e )
{
// this method parses the HTTP_HOST and gets the first subdomain
var companyName = GetCompanyName();
var realm = GetRealm( companyName );
e.SignInRequestMessage.Realm = realm;
e.SignInRequestMessage.HomeRealm = companyName;
}
string GetRealm( string companyName )
{
return String.Format( "http://{0}.ourapp.com/SSOPOCSite/", companyName );
}
Does this seem like a reasonable solution to the problem?
Are there any problems we might experience as a result?
Is there a better approach?

Your solution sounds good (explicitly passing along the information you need), the only other solution that comes to mind is using Request.UrlReferrer to determine which subdomain the user came from.

Related

Accurate Session Tracking in ASP.NET MVC

Whenever a user hits a page on my website, I run the following code to track user hits, page views, where they are going, etc...
public static void AddPath(string pathType, string renderType, int pageid = 0, int testid = 0)
{
UserTracking ut = (UserTracking)HttpContext.Current.Session["Paths"];
if (ut == null)
{
ut = new UserTracking();
ut.IPAddress = HttpContext.Current.Request.UserHostAddress;
ut.VisitDate = DateTime.Now;
ut.Device = (string)HttpContext.Current.Session["Browser"];
if (HttpContext.Current.Request.UrlReferrer != null)
{
ut.Referrer = HttpContext.Current.Request.UrlReferrer.PathAndQuery.ToString();
ut.ReferrerHost = HttpContext.Current.Request.UrlReferrer.Host.ToString();
ut.AbsoluteUri = HttpContext.Current.Request.UrlReferrer.AbsoluteUri.ToString();
}
}
//Do some stuff including adding paths
HttpContext.Current.Session["Paths"] = ut;
}
In my Global.asax.cs file when the session ends, I store that session information. The current session timeout is set to 20 minutes.
protected void Session_End(object sender, EventArgs e)
{
UserTracking ut = (UserTracking)Session["Paths"];
if (ut != null)
TrackingHelper.StorePathData(ut);
}
The problem is that I'm not getting accurate storage of the information. For instance, I'm getting thousands of session stores that look like this within a couple minutes.
Session #1
Time: 2014-10-21 01:30:31.990
Paths: /blog
IP Address: 54.201.99.134
Session #2
Time: 2014-10-21 01:30:31.357
Paths: /blog-page-2
IP Address: 54.201.99.134
What it should be doing, is storing only one session for these instances:
What the session should look like
Time: 2014-10-21 01:30:31.357
Paths: /blog,/blog-page-2
IP Address: 54.201.99.134
Clearly, this seems like a search engine crawl, but the problem is, I'm not sure if this is the case.
1) Why is this happening?
2) How can I get an accurate # of sessions to match Google analytics as closely as possible?
3) How can I exclude bots? Or how to detect that it was a bot that fired it?
Edit: Many people are asking "Why"
For those of you that are asking "Why" we are doing this as opposed to just using analytics, to make a very long story short, we are building user profiles to mine data out of their profile. We're looking at what they are viewing, how long they are viewing it, their click paths, we also have A/B tests running for certain pages and we're detecting which pages are firing throughout the user viewing cycle and we're tracking some other information that is custom and we're not able to put this into a google analytics API and pull this information out. Once they've navigated the site, we're thing using this information to build user profiles for every session on the site. We essentially need to then detect which of these sessions is actually real and give the site owners the ability to view the data along with our data mining application to analyze the data and provide feedback to the site owners on certain criteria to help them better their website from these profiles. If you have a better way of doing this, we're all ears.
1) the asp.net session is tracked with the help of the asp.net session Cookie.
But it is disabled for anonymous users (not logged on users)
You can activate sessionId creation for anonymous user's in the web.config
<configuration>
<system.web>
<anonymousIdentification enabled="true"/>
</system.web>
</configuration>
A much better place to hook up your tackin is to add an global mvc ActionFilterAttribute.
The generated SessionId is stored in the httprequest, accessed by
filterContext.RequestContext.HttpContext.Request.AnonymousID
2) You should create a feed of tracking paths to analys it asyncronly or not even in the same process. Maybe you want to store the tracking on disk "like a Server log" to reanalyse it later.
Geo Location and db lookup's needs some processing time and most likly you cant get the accurate geo location from the ip address.
A much better source is to get it from the user profiles / user address later on. (after the order submit)
Sometimes the asp.net session cookie don't work, because the user has some notracking plugin activated. Google Analytics would fail here too. You can increase the tracking accuracy with a custom
ajax Client callback.
To make the Ajax callback happen globally for all pages, you can use the help of the ActionFilterAttribute to inject some Script-Content to the end of the html content stream Response.
To map an IPv4 address to a session can help, but it should only be a hint.
Noadays a lot of ISP supporting IPv6. They are mapping there clients
most of the time to a small IPv4 pool. So one user can switch its ipv4 very fast
and there is a high possibility that visitors of the same page are using the same ISP and so share a IPv4.
3) Most robots identify themselves by a custom user agent in the request headers.
There are good and bad ones. See http://www.affiliatebeginnersguide.com/articles/block_bots.html
But with the Ajax callback u can verify the browser presents, at least the present of a costly
html-dom with JavaScript Environment.
X) To simplfy the start and concentrate on the Analysis. Implement a simple ActionFilterAttribute
and Register it globaly in RegisterGlobalFilters
filters.Add(new OurTrackingActionFilterAttribute(ourTrackingService));
In the filter override OnActionExecuting
public override void OnActionExecuting(ActionExecutingContext filterContext)
{
base.OnActionExecuting(filterContext);
OnTrackingAction(filterContext);
}
public virtual void OnTrackingAction(ActionExecutingContext filterContext)
{
var context = filterContext.RequestContext.HttpContext;
var track = new OurWebTrack(context);
trackingService.Track(track);
}
To don't delay the Server Response with some tracking processing,
take a look into the Reactive package http://msdn.microsoft.com/en-us/data/gg577609.aspx
It's a good way to split the capture from the processing.
Create a "Subject" in the TrackingService and simple push our tracking objects into it.
You can write observers to transmit, save or process the tracking objects.
As default the observers will only get one object at a time and so you dont need to syncronise/lock your status variables/Directory/memeory-cache and maybe u want to load the data and reprocess it with a new version of your application later on (maybe in debuging).

How to get current domain name in ASP.NET

I want to get the current domain name in asp.net c#.
I am using this code.
string DomainName = HttpContext.Current.Request.Url.Host;
My URL is localhost:5858but it's returning only localhost.
Now, I am using my project in localhost. I want to get localhost:5858.
For another example, when I am using this domain
www.somedomainname.com
I want to get somedomainname.com
Please give me an idea how to get the current domain name.
Try getting the “left part” of the url, like this:
string domainName = HttpContext.Current.Request.Url.GetLeftPart(UriPartial.Authority);
This will give you either http://localhost:5858 or https://www.somedomainname.com whether you're on local or production. If you want to drop the www part, you should configure IIS to do so, but that's another topic.
Do note that the resulting URL will not have a trailing slash.
Using Request.Url.Host is appropriate - it's how you retrieve the value of the HTTP Host: header, which specifies which hostname (domain name) the UA (browser) wants, as the Resource-path part of the HTTP request does not include the hostname.
Note that localhost:5858 is not a domain name, it is an endpoint specifier, also known as an "authority", which includes the hostname and TCP port number. This is retrieved by accessing Request.Uri.Authority.
Furthermore, it is not valid to get somedomain.com from www.somedomain.com because a webserver could be configured to serve a different site for www.somedomain.com compared to somedomain.com, however if you are sure this is valid in your case then you'll need to manually parse the hostname, though using String.Split('.') works in a pinch.
Note that webserver (IIS) configuration is distinct from ASP.NET's configuration, and that ASP.NET is actually completely ignorant of the HTTP binding configuration of the websites and web-applications that it runs under. The fact that both IIS and ASP.NET share the same configuration files (web.config) is a red-herring.
Here is a screenshot of Request.RequestUri and all its properties for everyone's reference.
You can try the following code :
Request.Url.Host +
(Request.Url.IsDefaultPort ? "" : ":" + Request.Url.Port)
I use it like this in asp.net core 3.1
var url =Request.Scheme+"://"+ Request.Host.Value;
www.somedomain.com is the domain/host. The subdomain is an important part. www. is often used interchangeably with not having one, but that has to be set up as a rule (even if it's set by default) because they are not equivalent. Think of another subdomain, like mx.. That probably has a different target than www..
Given that, I'd advise not doing this sort of thing. That said, since you're asking I imagine you have a good reason.
Personally, I'd suggest special-casing www. for this.
string host = HttpContext.Current.Request.Url.GetComponents(UriComponents.HostAndPort, UriFormat.Unescaped);;
if (host.StartsWith("www."))
return host.Substring(4);
else
return host;
Otherwise, if you're really 100% sure that you want to chop off any subdomain, you'll need something a tad more complicated.
string host = ...;
int lastDot = host.LastIndexOf('.');
int secondToLastDot = host.Substring(0, lastDot).LastIndexOf('.');
if (secondToLastDot > -1)
return host.Substring(secondToLastDot + 1);
else
return host;
Getting the port is just like other people have said.
HttpContext.Current.Request.Url.Host is returning the correct values. If you run it on www.somedomainname.com it will give you www.somedomainname.com. If you want to get the 5858 as well you need to use
HttpContext.Current.Request.Url.Port
the Request.ServerVariables object works for me. I don't know of any reason not to use it.
ServerVariables["SERVER_NAME"] and ServerVariables["HTTP_URL"] should get what you're looking for
You can try the following code to get fully qualified domain name:
Request.Url.Scheme + System.Uri.SchemeDelimiter + Request.Url.Host
Here is a quick easy way to just get the name of the url.
var urlHost = HttpContext.Current.Request.Url.Host;
var xUrlHost = urlHost.Split('.');
foreach(var thing in xUrlHost)
{
if(thing != "www" && thing != "com")
{
urlHost = thing;
}
}
To get base URL in MVC even with subdomain www.somedomain.com/subdomain:
var url = $"{Request.Url.GetLeftPart(UriPartial.Authority)}{Url.Content("~/")}";
string domainName = HttpContext.Request.Host.Value;
this line should solve it
Try this:
#Request.Url.GetLeftPart(UriPartial.Authority)

How can I make sure a url provided by the user is not a local path?

I'm writhing a web application (ASP.Net MVC, C#) that require the user to provide urls to RSS or Atom Feed that I then read with the following code :
var xmlRdr = XmlReader.Create(urlProvidedByUserAsString);
var syndicFeed = SyndicationFeed.Load(xmlRdr);
While debugging my application I accidentally passed /something/like/this as an url and I got an exception telling me that C:\something\like\this can't be opened.
It looks like a user could provide a local path and my application would try to read it.
How can I make this code safe? It probably is not sufficient to check for https:// or http:// at the begining of the url, since the user could still enter something like http://localhost/blah. Is there any other way, maybe with the uri class to check if an url is pointing to the web?
Edit: I think I also need to prevent the user from entering adresses that would point to other machines on my network like this example: http://192.168.0.6/ or http://AnotherMachineName/
Try:
new Uri(#"http://stackoverflow.com").IsLoopback
new Uri(#"http://localhost/").IsLoopback
new Uri(#"c:\windows\").IsLoopback

How can i get the Logged in user Name of Client machine

How can i get the LoggedIn in user Name of Client machine
without client providing the useid and password...
(wjen the users visits the page i need to get In which user Id he/she loggedIn)
I tried
string clientMachineName;
clientMachineName = (Dns.GetHostEntry(Request.ServerVariables["remote_addr"]).HostName);
Response.Write(clientMachineName);
If you're in a domain environment you could enable Windows Authentication which will allow the users to bypass explicitly logging on in favor of NTLM authentication. IE and Chrome work well with this out of the box, FF has a config setting for it.
EDIT
If you only care about browsers/OSs that support ActiveX then you can get it using Javascript with specific ActiveX privileges (from here):
<script type="text/javascript">
<!--
var WinNetwork = new ActiveXObject("WScript.Network");
alert(WinNetwork.UserName);
//-->
</script>
Try this
Might be its work as per your requirement
Request.ServerVariables["LOGON_USER"]
if Request.ServerVariables("LOGON_USER") Returns Empty String in ASP.NET
Microsoft Guidline for that
You can use Request.LogonUserIdentity for getting client details.
Response.Write(Request.LogonUserIdentity.Name);
It seems ServerVariables have been depreciated for C# in some instances.
If so, you'll need to do it this way:
string login = System.Security.Principal.WindowsIdentity.GetCurrent().Name;
If you really want to use ServerVariables, keep in mind they are CaSe Sensitive in C#. The correct casing is almost always UPPER, and here is the list of them:
List of ServerVariables

Differentiate between client app and browser in ASMX Web Service?

This is a follow-up to Choosing a Connection String based on kind of request for which I got no answer and what I thought worked doesn't.
I have a webservice that needs to choose a specific connection string based on the user calling it from a browser or from a client application.
I tried:
HttpContext.Current != null? ConnectionStrings["Website"].ConnectionString : ConnectionStrings["Client"].ConnectionString
but realized that at some point even if I'm using the client application, there is some HttpContext (if someone can explain why it'd be great) but the Browser field under Request is "Unknown". So, then I tried:
if ( HttpContext.Current != null )
{
if ( HttpContext.Current.Request.Browser != "Unknown" )
{
//browser connection string here
}
else
//client app connection string here
}
else
//client app connection string here
This worked wonders when debugging, but on testing environment it still points to Browser connection string even when calling from the client app, as if at some point the Browser isn't "Unknown" ...
Is there a MUCH easier/simpler way to do this? The way I'm doing it seems really ugly.
I'm quite desperate at the moment as I have no idea why this is happening..
Rather than detecting and switching on the browser type, consider these two suggestions:
Add Custom Request Headers
In your various callers, define a new custom header in your Http request.
HttpWebRequest webRequest = (HttpWebRequest)WebRequest.Create(url);
webRequest.Headers.Add("CallerType", "ClientApp"); // "Browser", etc.
Then you know exactly and reliably what type of client is calling. This would be hard to get wrong, and couldn't be spoofed/mistaken.
Include The Caller Type in the QueryString
myService.asmx?BrowserType=1
Add a simple new querystring parameter to your .asmx webmethod. This will work just the same in a controlled environment, but if other users/developers get it wrong, or malform the expected values, you'd have to take other measures to correct/handle.
Both allow you to easily determine the connString on the incoming value. Perhaps the absense of a modifier/header, you could assume a default. Your sample question has 2 basic outcomes, and either suggested solution will be easy to extend (browser, client app, iPhone, whathaveyou).

Categories

Resources