Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

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?

like image 501
William Avatar asked Apr 21 '16 17:04

William


People also ask

What is OWIN MVC?

OWIN is an interface between . NET web applications and web server. The main goal of the OWIN interface is to decouple the server and the applications. It acts as middleware. ASP.NET MVC, ASP.NET applications using middleware can interoperate with OWIN-based applications, servers, and middleware.

Is OWIN an OAuth?

OWIN is an Open Web Interface for . Net which acts as a middleware OAuth 2.0 authorization server between SharePoint site and a third party client application. OWIN defines a standard interface between . NET web servers and web applications.


1 Answers

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:

  1. 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.
  2. 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.

  3. 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.

  4. 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;
    }
    
  5. 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.

  1. 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).

  2. 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.

  3. 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.

like image 168
Evk Avatar answered Oct 23 '22 00:10

Evk