4
votes

We have a WebForms application built against ASP.Net Framework (4.7.2) that uses OWIN cookie authentication. As part of a migration towards .Net Core we would like to use the cookies within a .Net Core (2.1) API application.

The WebForms application runs in Azure and leverages DataProtectionStartup to hook into IServiceCollection the Machine Key file to use the PersistKeysToAzureBlobStorage method within Microsoft.AspNetCore.DataProtection as per the Microsoft documentation.

WebForms DataProtectionStartup

    services.AddDataProtection()
        .PersistKeysToAzureBlobStorage(blockblob)
        .SetApplicationName("OurAppName");

.Net Core API Startup

    services.AddDataProtection()
        .PersistKeysToAzureBlobStorage(blockblob)
        .SetApplicationName("OurAppName");

Both applications are happily running with the machine key generated and stored on blob storage.

Microsoft have documention that details how to share an OWIN cookie with a shared machine key file, using the DataProtectorShim from Microsoft.Owin.Security.Interop. The DataProtectionShim requires a DataProtectionProvider generated from the shared machine key, in the documentation this is referenced in both applications to create the cookie and uses the DataProtectionProvider.Create() method that takes the file location as an argument.

As we are using DataProtection with blob storage, we do not have this location. We have tried using DataProtectionProvider.Create() with just the application name on both applications in that it would use the blob storage key file. Unfortunately this does not create a cookie that works across both applications.

OWIN cookie authentication settings within OwinStartup:

    app.UseCookieAuthentication(new CookieAuthenticationOptions
        {
            AuthenticationType = 'Identity.Application',
            AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
            LoginPath = new PathString("/login.aspx"),
            CookieName = "AppCookieName",
            ExpireTimeSpan = 300,
            SlidingExpiration = true,

            Provider = new CookieAuthenticationProvider
            {
                OnValidateIdentity =
                    SecurityStampValidator
                        .OnValidateIdentity<UserManager, User, int>(
                            validateInterval: TimeSpan.FromMinutes(30),
                            regenerateIdentityCallback: (manager, user) =>
                                manager.CreateIdentityAsync(user, 'Identity.Application'),
                            getUserIdCallback: (user) => user.GetUserId<int>())
            },
            TicketDataFormat = new AspNetTicketDataFormat(
                new DataProtectorShim(
                    DataProtectionProvider.Create("OurAppName")
                        .CreateProtector(
                            "Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware",
                            'Identity.Application',
                            "v2")
                        )),

            CookieManager = new ChunkingCookieManager()
        });

And our .Net Core Startup setup for cookies:

    services.AddAuthentication("Identity.Application")
        .AddCookie("Identity.Application", options =>
        {
            options.Cookie.Name = "AppCookieName";
        });

Has anyone come across this scenario before, all the examples we have found only examples of the use of DataProtectionProvider.Create() used with machine key file location and have found no guidance for how to accomplish this with the PersistKeysToAzureBlobStorage method.

2
I'm trying to do the exact same thing. Did you find any solution to point OWIN to a shared AzureBlob for the data protection keys?Scott Brightman
Just coming across this as well and finding nothing. Even the documented single-machine folder location when testing in dev env doesn't seem to work as the keys differ between the two apps (DataProtection on the OWIN side throws "key not found in key ring" errors). Frustrating.Ran Sagy
FWIW if someone reads into this later on, That issue was an old/stale cookie with a different/expired key; the CryptographicException was burried far, far beneath.. I went as far as replacing most of the Microsoft.Owin.* and Microsoft.AspNetCore.* NuGets with actual git clones to debug, But turning on the Trace logging for these namespaces would have done the same, in hindsight.Ran Sagy
@ScottBrightman I have not found a solution and not had time to look at the issue recently.markfknight
@RanSagy Thank you for detailing your solution, I will have a look at it over the next week and see if it fixes the issue for us.markfknight

2 Answers

1
votes

What i ended up doing is (ab)using the DI system (using the Microsoft.Extensions.DependencyInjection NuGet) to get the behaviour i wanted:

private static IDataProtector ConfigureDataProtection()
{
    // TODO this has to be wrong
    ServiceCollection services = new ServiceCollection();
    ConfigurationOptions configurationOptions = ConfigurationOptions.Parse("REDIS_CONNECTION_STRING_HERE");
    ConnectionMultiplexer rmp = ConnectionMultiplexer.Connect(configurationOptions);
    services.AddDataProtection()
        .SetApplicationName("APPNAME")
        .PersistKeysToStackExchangeRedis(rmp, "REDIS_DATA_PROTECTION_LIST_KEY");
    ServiceProvider provider = services.BuildServiceProvider();

    IDataProtectionProvider dataProtectionProvider = provider.GetRequiredService<IDataProtectionProvider>();
    return dataProtectionProvider.CreateProtector("Microsoft.AspNetCore.Authentication.Cookies.CookieAuthenticationMiddleware", "Cookies" /* <- Auth Type to match the ASP.NET Core side, the rest is CRUCIAL and has to be the same purpose strings as internally used by ASP.NET Core */, "v2");
}

While my example uses Redis, I imagine the same would apply to the other implementations.

0
votes

I was trying to do the same, Wanted to share cookie between New Asp.Net Core web application and Legacy WebForms project with Asp.Net Identity Authentication.

I hope this answer helps somebody who is trying to setup cookie authentication in Asp.Net WebForms with ability to store protection key some where other than shared directory path.

"DataProtectionProvider.Create" provides an action with IDataProtectionBuilder which can be used to overwrite key directory and store key in cloud (azure blob storage)

If webforms project has Azure.Extensions.AspNetCore.DataProtection.Blobs nuget package installed key could be stored in azure blob storage

e.g

WebForms project identity cookie authentication setup.

app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                CookieName = $"SharedCookieName",
                CookieDomain = "SharedDomain",
                CookieSameSite = SameSiteMode.Lax,
                CookiePath = "/",
                CookieHttpOnly = true,
                ExpireTimeSpan = TimeSpan.FromMinutes(30),
                AuthenticationType = "Identity.Application",
                LoginPath = new PathString("/Account/Login"),
                Provider = new CookieAuthenticationProvider
                {
                    OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser, int>(
                        validateInterval: TimeSpan.FromMinutes(30),
                        regenerateIdentityCallback: (manager, user) => user.GenerateUserIdentityAsync(manager),
                        getUserIdCallback: (id) => (id.GetUserId<int>()))
                },

                // Note: (new DirectoryInfo(@"\")  dummy directory to satisfy DataProtectionProvider.Create and than overwrite key storage with PersistKeysToAzureBlobStorage
                TicketDataFormat = new AspNetTicketDataFormat(new DataProtectorShim(DataProtectionProvider.Create(new DirectoryInfo(@"\"),
                                                        (builder) =>
                                                        {
                                                            builder.SetApplicationName("ShareApplicationName")
                                                            .PersistKeysToAzureBlobStorage("AzureBlobConnectionString","ContainerName", "key.xml");
                                                        })
                                                        .CreateProtector(
                                                           $"SomeSharedPurposeName",
                                                           "Identity.Application",
                                                           "v2"))),

                CookieManager = new Microsoft.Owin.Security.Interop.ChunkingCookieManager()
            });
IDataProtectionBuilder has multiple extension methods to use blob storage differently. 

public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, Uri blobSasUri)

public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, Uri blobUri, TokenCredential tokenCredential)

public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, Uri blobUri, StorageSharedKeyCredential sharedKeyCredential)

public static IDataProtectionBuilder PersistKeysToAzureBlobStorage(this IDataProtectionBuilder builder, BlobClient blobClient)

Same approach could be used with Asp.Net Core while setting up application cookie.

services.ConfigureApplicationCookie(options => { });