Usually, web applications work together to integrate different functionalities in a single login. So, if web applications (eg: - multiple modules) can host within a primary application, it is easier to use ASP.NET Core cookie sharing mechanisms over a single sign-on (SSO) experience.

For example, we can start with two MVC applications in .Net core and share the cookie for authentication. Let’s consider the primary application name is “PrimarySite” and secondary application “Subsite”.

Initially both applications have similar startup.cs file.

public void ConfigureServices(IServiceCollection services) {
    services.AddControllersWithViews();
}
// This method gets called by the runtime. Use this method to configure the HTTP request pipeline.
public void Configure(IApplicationBuilder app, IWebHostEnvironment env) {
    if (env.IsDevelopment()) {
        app.UseDeveloperExceptionPage();
    } else {
        app.UseExceptionHandler("/Home/Error");
        // The default HSTS value is 30 days. You may want to change this for production scenarios, see https://aka.ms/aspnetcore-hsts.
        app.UseHsts();
    }
    app.UseHttpsRedirection();
    app.UseStaticFiles();
    app.UseRouting();
    app.UseAuthorization();
    app.UseEndpoints(endpoints => {
        endpoints.MapControllerRoute(name: "default", pattern: "{controller=Home}/{action=Index}/{id?}");
    });
}


Step 1
First, we can make a login/register page in Primarysite. For that we need to add authentication middleware in configure method above UseAuthorization middleware.
app.UseAuthentication();


Steps 2
Set up a cookie sharing configuration with a key storage location in Configureservices method of startup.cs. The application name “SharedCookieApp” should be common in other applications/modules. (available namespace:- using Microsoft.AspNetCore.DataProtection;)

Then apply cookie policy configurations.

services.AddDataProtection().PersistKeysToFileSystem(GetKyRingDirectoryInfo()).SetApplicationName("SharedCookieApp");
services.Configure < CookiePolicyOptions > (options => {
    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});


We are using different applications/solutions set samesite policy to “None”

GetKyRingDirectoryInfo is a custom method to retrieve common path directory.

private DirectoryInfo GetKyRingDirectoryInfo() {
    string applicationBasePath = System.AppContext.BaseDirectory;
    DirectoryInfo directoryInof = new DirectoryInfo(applicationBasePath);
    string keyRingPath = Configuration.GetSection("AppKeys").GetValue < string > ("keyRingPath");
    do {
        directoryInof = directoryInof.Parent;
        DirectoryInfo keyRingDirectoryInfo = new DirectoryInfo($ "{directoryInof.FullName}{keyRingPath}");
        if (keyRingDirectoryInfo.Exists) {
            return keyRingDirectoryInfo;
        }
    }
    while (directoryInof.Parent != null);
    throw new Exception($ "key ring path not found");
}


Common path can be set in appsettings.json
"keyRingPath": "\\PrimarySite\\wwwroot\\Ring"

 

 

We are using different applications/solutions set samesite policy to “None”

GetKyRingDirectoryInfo is a custom method to retrieve common path directory.
private DirectoryInfo GetKyRingDirectoryInfo() {
    string applicationBasePath = System.AppContext.BaseDirectory;
    DirectoryInfo directoryInof = new DirectoryInfo(applicationBasePath);
    string keyRingPath = Configuration.GetSection("AppKeys").GetValue < string > ("keyRingPath");
    do {
        directoryInof = directoryInof.Parent;
        DirectoryInfo keyRingDirectoryInfo = new DirectoryInfo($ "{directoryInof.FullName}{keyRingPath}");
        if (keyRingDirectoryInfo.Exists) {
            return keyRingDirectoryInfo;
        }
    }
    while (directoryInof.Parent != null);
    throw new Exception($ "key ring path not found");
}


Common path can be set in appsettings.json
"keyRingPath": "\\PrimarySite\\wwwroot\\Ring"


Step 3
For testing the register/login functionality, we can use in-memory database (data storing in memory not in real database). For that we need to install the below NuGet packages (entity framework core Identity and In-memory). This step is optional and used for testing register and login. Real entity framework can be used if it exists in the original application.

Then create a context class which inherits from IdentityDBContext

public class AppDbContext: IdentityDbContext {
    public AppDbContext(DbContextOptions < AppDbContext > options): base(options) {}
}


And Inject AddDbcontext with UseInmemmoryDatabase in Configureservice method.
#region DB
services.AddDbContext < AppDbContext > (config => {
    config.UseInMemoryDatabase("Memory");
});
#endregion


Configure Identity specification and EntityFramework core with AppDbContext.

services.Configure < CookiePolicyOptions > (options => {
    // This lambda determines whether user consent for non-essential cookies is needed for a given request.
    options.CheckConsentNeeded = context => true;
    options.MinimumSameSitePolicy = SameSiteMode.None;
});
services.AddIdentity < IdentityUser, IdentityRole > (config => {
    config.Password.RequiredLength = 4;
    config.Password.RequireDigit = false;
    config.Password.RequireNonAlphanumeric = false;
    config.Password.RequireUppercase = false;
}).AddEntityFrameworkStores < AppDbContext > ().AddDefaultTokenProviders();

Step 4
Set authentication type set to Identity.Application and a common cookie name (.AspNet.SharedCookie) which should be same among different applications those shares cookie authentication.

services.AddAuthentication("Identity.Application");
services.ConfigureApplicationCookie(config => {
    config.Cookie.Name = ".AspNet.SharedCookie";
    config.LoginPath = "/Home/Login";
    //config.Cookie.Domain = ".test.com";
});

Step 6
This step is also optional. This is to show a simple authentication (If it already has a  different authentication setup, it would be good to use that.)

Creating a user registration and authentication within memory database.
[HttpPost]
public async Task < IActionResult > Register(string username, string password) {
    var user = new IdentityUser {
        UserName = username,
            Email = ""
    };
    var result = await _userManager.CreateAsync(user, password);
    if (result.Succeeded) {
        var signInResult = await _signInManager.PasswordSignInAsync(user, password, false, false);
        if (signInResult.Succeeded) {
            return RedirectToAction("LoginSuccess");
        }
    }
    return RedirectToAction("Login");
}


Step 7
Now the primarysite will get logged in after entering values in register/login. Now we need to configure Startup.cs of SubSite/ Sub module as below.

ConfigureServices Method.
public void ConfigureServices(IServiceCollection services) {
    services.AddDataProtection().PersistKeysToFileSystem(GetKyRingDirectoryInfo()).SetApplicationName("SharedCookieApp");
    services.Configure < CookiePolicyOptions > (options => {
        // This lambda determines whether user consent for nonessential cookies is needed for a given request.
        options.CheckConsentNeeded = context => true;
        options.MinimumSameSitePolicy = SameSiteMode.None;
    });
    services.AddAuthentication("Identity.Application").AddCookie("Identity.Application", option => {
        option.Cookie.Name = ".AspNet.SharedCookie";
        option.Events.OnRedirectToLogin = (context) => {
            context.Response.StatusCode = 401;
            return Task.CompletedTask;
        };
    });
    services.AddControllersWithViews();
}

option.Events.OnRedirectToLogin can be configured to redirect in case of not authorized/authenticated scenario.

Configure Method.
Add UsecookiePolicy middleware above UseRouting
app.UseCookiePolicy();

Add UseAuthentication middleware below UseRouting.
app.UseAuthentication();


Then Add common key path in appsettings.json
"keyRingPath": "\\PrimarySite\\wwwroot\\Ring"

Step 8
Run both application and login to Primary site and redirect to the subsite authorize method(add[Authorize] attribute for methods or controller for which authentication is mandatory).


HostForLIFE.eu ASP.NET Core Hosting

European best, cheap and reliable ASP.NET hosting with instant activation. HostForLIFE.eu is #1 Recommended Windows and ASP.NET hosting in European Continent. With 99.99% Uptime Guaranteed of Relibility, Stability and Performace. HostForLIFE.eu security team is constantly monitoring the entire network for unusual behaviour. We deliver hosting solution including Shared hosting, Cloud hosting, Reseller hosting, Dedicated Servers, and IT as Service for companies of all size.