Search code examples
asp.netasp.net-coresecuritycookiesoauth-2.0

Prevent ASP.NET Core cookie-authentication from accepting cookies signed for different hostnames


Environment
I have a special case with a single ASP.Core 5 web application hosted on a wildcard domain.

I have an infinite number of dynamic sub-domains, and there is a Single-Sign-On OpenID authority responsible for authentication and authorizating what user has access to what domain.

For example, all these domains go to the same ASP.Core web application, and many more:

  • device1.mydomain.io
  • device2.mydomain.io
  • device3.mydomain.io
  • deviceN.mydomain.io
  • anything.mydomain.io

The Single-Sign-On server will refuse to sign your login if the return URL during the OIDC- redirect points to a sub-domain that your user should not have access to. Either you have access to that particular sub-domain or you do not.

Considerations so far
So far, we have added event handlers to the OpenID cycle of the webserver to dynamically pick an OIDC Client ID based on the URL we were contacted on before the redirect to Single-Sign-On server.

After the redirect, this application will also refuse to accept the token signed by the Single-Sign-On server if it was signed for a different redirect URL than this application was contacted on. This to prevent someone from copying the token, and changing the URL and trying to use the same token for a different sub-domain the user should not have access to.

There are no longer any security problems that I can see in the OpenID redirect-cycle itself. And all here is working fine.

Problem
However now there is a security problem after the cookie has been signed when using the service.

  • The user has access to domain1.mydomain.io, but no access to domain2.mydomain.io.
  • The user logs into domain1.mydomain.io and ASP.Core service signs a cookie.
  • The user copies the cookie into Postman and uses it to contact domain2.mydomain.io.
  • Now the user has access to domain2.mydomain.io too, since the ASP.Core service never checks which domain the cookie was signed for.

How can I make the ASP.Core cookie-authentication middleware check which domain the cookie was signed for, and refuse it if the domain differs from the one we were contacted at?

The Startup.cs code

void AddOpenIdConnectServices(IServiceCollection services, IDataProtectionProvider dataProtectionProvider)
{
    services
        .AddAuthentication(options =>
        {
            options.DefaultScheme = "Cookies";
            options.DefaultChallengeScheme = "oidc";
        })
        .AddCookie(o =>
        {
            o.DataProtectionProvider = dataProtectionProvider;
            o.Cookie.SameSite = SameSiteMode.None;
        })
        .AddOpenIdConnect("oidc", options =>
        {
            options.Authority = this.config.OpenId_Authority;
            options.ClientId = this.config.OpenId_ClientId;
            options.RequireHttpsMetadata = true;
            options.SaveTokens = true;

            // Ensure that the "state" sent to the SSO server is encrypted with the same secret as the other webservers use when scaled to >1.
            // Without this login will fail because we're unable to decrypt the "state" at "signin-oidc" endpoint when coming back from the SSO-server.
            options.DataProtectionProvider = dataProtectionProvider;

            // Customize OpenID so that we can provide the SSO- server a dynamic client-id based on which hostname we were contacted on.
            // We need to intercept the redirection to the SSO- server, as well as the audience/client-id validation when the JWT- token is returned from the SSO- server.
            DynamicOpenIdClientHandler dynamicClientId = new DynamicOpenIdClientHandler(clientIdPrefix: options.ClientId);
            options.Events.OnRedirectToIdentityProvider = dynamicClientId.OnRedirectToidentityProvider;
            options.TokenValidationParameters.AudienceValidator = dynamicClientId.AudienceValidator;
            options.Events.OnTokenValidated = dynamicClientId.OnTokenValidated;
        });
}

Similar questions
This is a similar question, however I'm not sure customizing the cookie manager is the best way to go, or if it solves the problem.

Multiple & SubDomain's cookie in asp.net Core Identity


Solution

  • I found a solution!

    It seems like ASP.Core cookie authentication by default does not care about the hostname the cookie was signed for when the token is validated on each request. And probably for a good reason. In most use cases the webserver can always accept cookies just based on that the same webserver signed it, and not care about how we were contacted.

    This behaviour can be changed by adding additional principal validation to Events.OnValidatePrincipal when configuring AddCookie during startup.

    I added an extra check validating the hostname the cookie was signed for, with the current actual hostname. This works, the server no longer accepts cookies signed for the wrong hostname. It will now redirect these requests to the Single-Sign-On server instead.

    o.Events.OnValidatePrincipal = context =>
    {
        if (context.Properties.Items.TryGetValue("OpenIdConnect.Code.RedirectUri", out string redirectUri))
        {
            Uri cookieWasSignedForUri = new Uri(redirectUri);
            if (context.Request.Host.Host != cookieWasSignedForUri.Host)
            {
                context.RejectPrincipal();
            }
        }
    
        return Task.CompletedTask;
    };
    

    I feel this solution is pretty safe and straight forward once found. If anyone see later and know a better solution, please tell me. :)