aspnet / Identity

[Archived] ASP.NET Core Identity is the membership system for building ASP.NET Core web applications, including membership, login, and user data. Project moved to https://github.com/aspnet/AspNetCore
Apache License 2.0
1.96k stars 868 forks source link

Identity 2.0 and sub domains #1556

Closed Wayne-Mather closed 6 years ago

Wayne-Mather commented 6 years ago

Hi,

I have several sites on the same server just using a different subdomain. An example may look like this:

I cannot use an identity server because while these sites are also replicated (SQL Server) to remote sites in remote areas (behind a satellite).

What I want to be able to do is to allow the authenticated cookie to be shared on contoso.com. How can this be achieved using ASP.NET Core 2.0 when Identify is used? I want to use something like Cookie.Domain = ".contoso.com" and the identity cookie to be stored as such.

blowdart commented 6 years ago

This isn't just a matter of setting the cookie. The cookie is encrypted with data protection so in addition to replicating your user database you must also replicate your encryption keys, and in order to do that you're going to need to write a SQL store for data protection, something we have yet to offer.

You'll also need to configure data protection with a fixed application name for all your sites.

Finally, to move the cookie domain upwards you can set the option like the other cookie settings, but using Cookie.Domain as the property.

services.ConfigureApplicationCookie(options =>
{
    options.Cookie.Name = "YourAppCookieName";
    options.Cookie.Domain = "contoso.com";
});
Wayne-Mather commented 6 years ago

This is an intranet app, so protection of the cookie is not necessary for my circumstances.

Pity there is no way to setup the data protection with your own IV/seed bytes (or disable encrypting the cookie). That way the cookie can be successfully decrypted by all running servers.

If I understand correctly any sub-domain servers are reading the cookie, but because it cannot be decrypted (due to the way the data protection works), they are overwriting it in the HTTP response encrypted with their own protection keys?

This would explain why if you login to subdomainA then navigate to subdomainB and access an authorised required action, you are prompted to login. If you don't login and navigate back to subdomainA you are again prompted to login (B has overridden the cookie with it's own encryption - A cannot decrypt - so redirects to login).

HaoK commented 6 years ago

If you really want, you can plug in your own data protection for cookies via

https://github.com/aspnet/Security/blob/dev/src/Microsoft.AspNetCore.Authentication.Cookies/CookieAuthenticationOptions.cs#L123

Wayne-Mather commented 6 years ago

@HaoK Is there an example any where on how to do this? app.UseCookieAuthentication() is deprecated so this can only be setup when configuring services.

I have tried several things such as even creating my own AddCustomDefaultProviders() method. But nothing allows me to share the cookie with different instantiated Kestral sites running under the same top-level domain. I am thinking of a refactor the revolves around not using Identity as it's only used for login and stamping who created/last updated records - I don't and can't use domain logins.

HaoK commented 6 years ago
services.ConfigureApplicationCookie(options =>
{
    options.TicketDataFormat = new WhateverSecureDateFormatYouWant()
});
Wayne-Mather commented 6 years ago

@HaoK Thanks I did try that before but got stuck on how serialise the AuthenticationTicket. It took me an hour of searching to finally find that Microsoft.AspNetCore.Authentication nuget package is in the ASPNET/Security github solution.

Once I found that I all I needed to implement the class and drop it it. Worked like a treat in my initial POC testing. I will consider this closed for my scenario.

Will document what I did below in-case handy for anyone else:

Inside Startup.ConfigureServices():

 services.AddIdentity<ApplicationUser, IdentityRole>(o =>
                 {
                     o.Password.RequireDigit = false;
                     o.Password.RequireLowercase = false;
                     o.Password.RequireUppercase = false;
                     o.Password.RequireNonAlphanumeric = false;
                     o.Password.RequiredLength = 5;
                 })
                 .AddEntityFrameworkStores<ApplicationDbContext>()
                 .AddDefaultTokenProviders();

             services.ConfigureApplicationCookie(opt => { opt.TicketDataFormat = new UnencryptedTicketDataFormat(); });

UencryptedTicketDataFormat Class:

public class UnencryptedTicketDataFormat : ISecureDataFormat<AuthenticationTicket>
    {
        private readonly IDataSerializer<AuthenticationTicket> _serializer;

        public UnencryptedTicketDataFormat()
        {
            _serializer = new TicketSerializer();
        }

        public string Protect(AuthenticationTicket data)
        {
            return Protect(data, null);
        }

        public string Protect(AuthenticationTicket data, string purpose)
        {
            var userData = _serializer.Serialize(data);
            return Base64UrlTextEncoder.Encode(userData);
        }

        public AuthenticationTicket Unprotect(string protectedText)
        {
            return Unprotect(protectedText, null);
        }

        public AuthenticationTicket Unprotect(string protectedText, string purpose)
        {
            try
            {
                if (protectedText == null)
                {
                    return default(AuthenticationTicket);
                }

                var protectedData = Base64UrlTextEncoder.Decode(protectedText);
                if (protectedData == null)
                {
                    return default(AuthenticationTicket);
                }

                return _serializer.Deserialize(protectedData);
            }
            catch
            {
                // TODO trace exception, but do not leak other information
                return default(AuthenticationTicket);
            }
        }

    }