ASP Identity : Binding Sessions To Request Url (Sub Domains) - asp.net

I don't think the title of the question is particularly accurate but that's how best i could title it.
Without summarizing, I have an MVC app hosted on Microsoft Azure. The app was built for multiple institutions (each connecting to a separate database) but the Login Module (Asp Identity) is in a central database (users are identified by their institution code). So during deployment, a sub domain is created (still pointing to the app on azure).
My problem is, the app has no regard for the Request Url, the sessions are maintained across domains. This is a serious problem because i cache User data (by session). So if a user Logs in on "domain1.myapp.com" , then opens another tab , logs into "domain2.myapp.com" , all data cached for the user logged in for "domain1" will be used for the user logged in at "domain2". The app doesn't bother to get data for the user in "domain2" since the key for that data value is already present in the session cache.
Okay, I hope the problem is understood. How do i get past this.
Ideas ? implementation ?
EDIT 1
I insert data into the cache by,
HttpRuntime.Cache.Insert("KEY", "VALUE", null, DateTime.Now.AddMinutes(30),Cache.NoSlidingExpiration);

Your caching strategy needs to change when you cache per user and per domain. The simplest approach is just to add the domain and user name to the cache key, which will keep each cache in a separate per user and per domain bucket. Make sure you put a delimiter between the values to ensure uniqueness of the key.
var domain = HttpContext.Request.Url.DnsSafeHost;
var user = HttpContext.User.Identity.Name;
var key = "__" + domain + "_" + user + "_" + "KEY";
HttpRuntime.Cache.Insert(key, "VALUE", null, DateTime.Now.AddMinutes(30),Cache.NoSlidingExpiration);
Note that if you use HttpContext.Session, it will automatically put different domain information into separate buckets because it is based on a cookie (which by default is domain specific). However, HttpContext.Session comes with its own set of problems.

Related

Add Roles fetched from SQL Server as Claims to AD FS Relying Party Trust

I'm authenticating users of an asp.net mvc web site by using ADFS Server 2016 passive redirection, and I cannot get claims from a SQL attribute store. I'm interested in discovering what I am doing wrong or missing.
Side note: I'm using the System.Identity libraries from Framework 4.5 (I'm not referencing the Microsoft.Identity libraries created for older framework versions; most ADFS code samples that I stumble across use these old libraries).
The basics are working well. All of this is in one domain. I have my asp.net web.config set up to redirect users to my ADFS server for authentication. The ADFS server successfully authenticates and redirects users back to my asp.net web site. On the ADFS I have one Claim Issuance Policy rule where I simply pass back all claims from the Active Directory.
On the web site I am able to iterate through the user's Claims collection and display them. Here is the code from the *.cshtml page where I iterate though the claims, it works fine:
#using System.Security.Claims;
#{
var currentPrincipalIdentity = (ClaimsIdentity)System.Threading.Thread.CurrentPrincipal.Identity;
}
#foreach (Claim claim in currentPrincipalIdentity.Claims)
{
<br/>#claim.Type : #claim.Value
}
In addition to these claims from Active Directory, I want to fetch a bunch of roles from a SQL Server database and add them to the Claims collection as roles. I'm fetching the roles from a legacy asp.net Membership database. As step 1 I just want to hard-code the username in the SQL statement (eventually I will need to figure out how to pass the username as a parameter to the SQL statement, but that will be step 2).
First, I gave the identity that the ADFS server runs under read/write/execute permissions on my SQL Server (when I take these permissions away I get a permissions error, which gives me confidence that my SQL statement is executing).
In my AD FS I added a SQL Server Attribute Store by right-clicking the "Attribute Stores" node, selecting an Attribute store type of "SQL", named is "SQLServer", and added a connection string like so:
Server=SqlDev01; Database=MyLegacyMembershipDatabase; Integrated Security=SSPI;
I then select the "Relying Party Trusts" folder, select the trust I am interested in, and select "Edit Claim Issuance Policies." I have one rule there that works; it simply passes back all Active Directory claims. I can see all of these claims on my web page (upn, name, windowsaccountname, all of my group sids, and etc):
c:[]
=> issue(claim = c);
I'm trying to add a 2nd custom rule to read a legacy membership database. In my ADFS I click "Add Rule", "Send Claims Using a Custom Rule", and add this as the rule:
c:[Type == "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn"]
=> add(store = "SQLServer", types =
("http://schemas.microsoft.com/ws/2008/06/identity/claims/role"), query =
"select r.RoleName AS Role from dbo.aspnet_Roles r INNER JOIN
dbo.aspnet_UsersInRoles uir ON r.RoleId = uir.RoleId INNER JOIN
dbo.aspnet_Users u ON uir.UserId = u.UserId WHERE u.UserName = '[hard-coded
value here]' OR u.UserName={0}", param = c.Value);
It saves fine, but when I re-run the page nothing changes; I still get the original collection of Active Directory claims, but not the data from SQL Server.
I am confident the SQL Server statement is executing, because if I remove permissions for the identity that ADFS runs under from the SQL Server I get an error, and if I deliberately garble the SQL syntax I get an error. If I reverse these deliberate mistakes then the page functions properly again. But I never see the Roles that I want to see in the Claims collection.
From my understanding of custom rules, "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/upn" is passed as a parameter into the query, that is why I have the OR statement above; my ultimate goal is to pass the user's UPN as a parameter into the SQL query.
Am I missing something or doing something wrong? Bonus question--assuming I get this working, can you tell me how to pass the user's UPN as a parameter into the SQL query?
Try an "issue" rule rather than an "add".

Cross domain cookie accessing

How can we access cookie created by one domain in another domain.
I have created cookie as following in one domain
Response.Cookies["newOne"].Value = "something";
from another domain I am accessing it as
var data = Request.Cookies["newOne"].Value; //This is throwing exception
I am able to access cookie from local but not from another domain.
Give your cookie domain name also as shown :-
Response.Cookies["newOne"].Value = "something";
Response.Cookies["newOne"].Domain = ".mydomain.com"
and then access its value in other domain.
HttpCookie hcookie = new HttpCookie("cookiename","Cookie Value");
hcookie.Domain = ".example.com";
Please try this link
http://msdn.microsoft.com/en-us/library/dd920298(v=vs.95).aspx
This question's pretty cold, but in case anyone else stumbling on it, or the OP still has need, I've created an NPM module, which allows you to share locally-stored data across domains:
https://www.npmjs.com/package/cookie-toss
By using an iframe hosted on Domain A, you can store all of your user data on Domain A, and reference that data by posting requests to the Domain A iframe.
Thus, Domains B, C, etc. can inject the iframe and post requests to it to store and access the desired data. Domain A becomes the hub for all shared data.
With a domain whitelist inside of Domain A, you can ensure only your dependent sites can access the data on Domain A.
The trick is to have the code inside of the iframe on Domain A which is able to recognize which data is being requested. The README in the above NPM module goes more in depth into the procedure.
Hope this helps!

Check other user's role membership (IsInRole, WindowsIdentity/Principal)

I'm writing ASP.NET code to run on an internal network where Windows Authentication will be used. Certain operations will require me to run a group membership check on other users (not the current user)
NOTE: I am NOT trying to impersonate this account, or access any information in the context of this other user. Just trying to find out what kind of user they are for internal business logic.
My first thought was to use
new WindowsPrincipal(new WindowsIdentity("MACHINENAME\\username"))
.IsInRole("MACHINENAME\\Group1")
However, the WindowsIdentity constructor fails with a SecurityException "The name provided is not a properly formed account name".
If I strip MACHINENAME\ from the parameter, I get a different error: There are currently no logon servers available to service the logon request
The WindowsTokenRoleProvider role provider explicitly only works with the current user, and will not check other user accounts.
Are there security restrictions to checking roles of other users? Would it make a difference if the web server was on a domain and I were checking domain accounts?
In the end, I'll need to have this work on an AD domain, but would prefer a solution that will work on either local or AD accounts.
Thank you
UPDATE: I've been able to test this on a domain now -- the code does work in an AD context so long as I don't use the domain name (test "username" against "Group1", not "DOMAIN\username" against "DOMAIN\Group1")
So how would I get this to work in the context of local users and groups?
Based on Rob A's comment, PrincipalContext and UserPrincipal are the classes I apparently need to use:
using (PrincipalContext ctx = new PrincipalContext(ContextType.Machine))
{
var u = UserPrincipal.FindByIdentity(ctx, IdentityType.Name, "username");
var b = u.IsMemberOf(ctx, IdentityType.Name, "Group1");
var groups = u.GetAuthorizationGroups();
}
And by altering the ContextType, can switch between local accounts and AD accounts. I wish this was built into a RoleProvider, but I guess that's something I'd have to do for myself.

ASP.NET Authentication and Provider

I am trying to navigate from one website on my localhost to second website on my localhost.
Both sites have their own membership provider. I'm trying to use a FormsAuthorizationTicket from site #1 to SSO a user into site #2.
Currently I'm getting this error:
System.Security.Cryptography.CryptographicException: Padding is invalid and cannot be removed
Steps I have taken up to this point include:
Setting the element to specific key values
Set the machineKey attributes of validation and encryption to "3DES"
Verified through logging that the encrypted ticket has the same value in the #2 website as it was given in the #1 website.
My code is here:
*FormsAuthentication.Initialize();
FormsAuthenticationTicket newTicket = new
FormsAuthenticationTicket(1 // Ticket Version
, Login1.UserName // User Name
, DateTime.Now // Creation Date
, DateTime.Now.AddDays(1) // Expiration Date
, true // Is Persistant
, Login1.UserName); // This should be a list of Roles
string strEncyptedTicket = FormsAuthentication.Encrypt(newTicket);
HttpCookie myCookie = new HttpCookie("cryptCookie", strEncyptedTicket);
myCookie.Values.Add("username", Login1.UserName);
myCookie.Values.Add("cryptTick", strEncyptedTicket);
Response.Cookies.Add(myCookie);*
In website #2 I created a landing page to verify the ticket and redirect to a members only page. During decryption is when I get the error specified above.
Here is my landing page code on website #2:
*FormsAuthenticationTicket fat2 = FormsAuthentication.Decrypt(Request.Cookies["cryptCookie"].Values["cryptTick"]);
MembershipUser mu = Membership.GetUser(Request.Cookies["cryptCookie"].Values["username"]);
if (mu == null)
{
lblInfo.Text += "member not found";
return;
}
Response.Redirect(#"~\MemberPages\MemberPage.aspx");*
If anyone has an idea to help I'll be happy to try.
Both sites need to share the same machine key.
Ian is correct in that your sites need to have a matching machine key. Also, you need to make sure that your membership providers have the same setting, particularly concerning password encryption.
Also, why are you handling this completely in code? You should be able to configure this functionality withing the Web.Config of both sites very easily. In essence, you are doing a lot of rework and introducing potential problem areas where it is not necessary (unless you have reason that you have not stated here).

How to allow secure login across multiple domains

I have a web based application that allows a user to point their DNS at my IP and my app will serve up their content based on the domain name I see in the HTTP host header. I should point out this is done by the application, not by the actual HTTP server(apache), it is a rebranded app sort of thing. The problem I have is that I would like the users to be able to login through a form on the served page and somehow stay within the domain of the user. This is easy, unless you want security. I would have to have a SSL cert installed for every domain to pull this off. Right now I can do it by submitting the form to a domain with an SSL cert installed, but due to browser security I can't exactly set the required cookies on the original domain.
Does anyone know a way I can securely log in users through the app that does not involve installing a lot of ssl certs. I can think of some convoluted ways using redirects or other mechanisms, but it is not that clean. I don't mind a submit to the secure url and a redirect, it's just setting the cookie can't be done.
I've done this before using the following method...
Create auth key on server 1.
create_auth_key
expires = time + expire_time
data = username + '|' + password + expires
secret = 'my secret key'
hash = md5( data + secret )
key = base64( data ) + hash
On server two you pass the newly created authkey
valid_auth_key(key)
hash = key[-hash_size:]
b64data = key[:-hash_size]
data = base64decode( b64data )
data_hash = md5( data + secret )
if data_hash != hash:
return false # invalid hash
data_parts = data.split('|')
user = data_parts[0]
password = data_parts[1]
expires = data_parts[2]
if now > expires:
return false # url expired
return true
It's kind of quick and dirty but only relies on simple data passed via URL. The down side is that a specific url is all that's required to login and someone could share that url for a period of time. You also have to make sure your expiration time is not greater than the time difference between servers.
A common trick is to pass data in the URL. Facebook Connect does this. You can redirect from one domain to the other with a session token in the URL and then verify the token (perhaps convert to a cookie) when the request comes in on the other domain. Edit: the MSDN article that Facebook links to has much more detail.

Resources