-1

We have three ASP.NET MVC 5 projects working alongside. For authentication, we came up using Single Sign On, following the very simple tutorial:

Implementation of Single Sign On (SSO) in ASP.NET MVC

The main idea is to create a MachineKey shared between 3, and add the same authentication settings in 3 web.config files.

So now we have 3 sites called:

  • SSO
  • WebApp1
  • WebApp2

One of our projects (SSO) does the job and two other depend on it. It works and we were happy but...

We are using Identity 2 claims-based authentication in the SSO project and when a user logs in, we add some custom claims to his "identity". This way, we have 2 separate cookies: one for Single Sign On process, and a second one for saving claims.

Here is the C# code:

    [HttpPost]
    [AllowAnonymous]
    [ValidateAntiForgeryToken]
    public async Task<ActionResult> Login(LoginViewModel model, string fromSite, string returnUrl)
    {
        if (!ModelState.IsValid)
        {
            return View(model);
        }

        var result = await SignInManager.PasswordSignInAsync(model.Email, model.Password, model.RememberMe, shouldLockout: false);
        switch (result)
        {
            case SignInStatus.Success:
                // here the cookie which contains claims *is created* by Identity 2

                // here we create the cookie used for Single Sing On
                FormsAuthentication.SetAuthCookie(username, false);

                // redirecting
                if (string.IsNullOrWhiteSpace(fromSite))
                {
                    if (string.IsNullOrWhiteSpace(returnUrl)) return RedirectToAction("Index", "Home");
                    return RedirectToLocal(returnUrl);
                }

                return Redirect(string.Format("{0}{1}", fromSite, returnUrl));

                // other cases inside switch
                .
                .
                .
        }
    }

When a user goes from SSO site to another, say WebApp1, he remains logged in but we lost claims.

Is there any way to "merge" these 2 cookies and retrieve the claims in another site?

marc_s
  • 732,580
  • 175
  • 1,330
  • 1,459
Ali
  • 99
  • 1
  • 11

2 Answers2

2

I finally managed this! Here is the sulotion:

Create 3 new MVC 5 projects with Individual Authentication. Add a same MachineKey in their web.config files inside system.web tag. Now you are done and every thing works like a charm! Easy peasy :) The OWIN takes care of every thing.

For better clarity, delete anything related to authentication and authorization from 2 of 3 projects. This way one of them will be the core and two others depend on it. My projects are:

  • SSO
  • WebApp1
  • WebApp2

For example delete AccountController.cs and ManageController.cs from Controllers folder and remove Account and Manage folders under Views with their related ViewModels. You can also delete the Startup.Auth.cs file under App_Start folder in both projects, say WebApp1 and WebApp2.

After all of deleting, replace the content of Startup.cs in that 2 projects root directory with this:

using Microsoft.AspNet.Identity;
using Microsoft.Owin;
using Microsoft.Owin.Security.Cookies;
using Owin;

[assembly: OwinStartupAttribute(typeof(WebApp1.Startup))]
// for the other one, rename the WebApp1 to WebApp2
namespace WebApp1
{
    public class Startup
    {
        public void Configuration(IAppBuilder app)
        {
            app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            });
        }
    }
}

The above code was originally part of Startup.Auth.cs file which you have deleted! Now OWIN knows the type of cookie and can read it.

There is one thing left: when an unauthorized user, who has not logged in yet, goes to WebApp1, he will see the HTTP 401 error page and will not redirect to SSO's login page. You should handle this case in any manner you want. I think a Filter would do the job.

Hope i helped :)

Ali
  • 99
  • 1
  • 11
0

Consider we have 4 projects.

  • WebAppMain(This project contains the login page)

  • App2

  • App3

  • App4

If you are using ASP.Net Identity you need to add code for that in Startup class(Not in main project, this for App2, 3 and 4).

One important thing is, keep all Owin related dlls in same version. Otherwise SSO won't work.

[assembly: OwinStartupAttribute(typeof(ABC.Startup))]
namespace ABC
{
    public partial class Startup
    {
        public void Configuration(IAppBuilder app)
        {

                app.CreatePerOwinContext(ApplicationDbContext.Create);
                app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
                app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
                app.UseCookieAuthentication(new CookieAuthenticationOptions
                {
                    AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
                    AuthenticationMode = Microsoft.Owin.Security.AuthenticationMode.Active,
                    CookieDomain = ConfigurationManager.AppSettings["DomainNameForSSO"],
                    CookieName = "SSOCookie",

                });

        }
    }
}

Use a appsettings variable to store domain

<add key="DomainNameForSSO" value=".test.com"/>

Use the same CookieName and CookieDomain in main project Startup.Auth.cs file.

Add a common machine key to all project's web.config file.

<machineKey
       validationKey="{Update this value}"
       decryptionKey="{Update this value}"
       validation="SHA1"
       decryption="AES"
    />

you can generate machine using online tools or custom code is available on internet.

Once it is done, create a Custom Authorize attribute like below.

[AttributeUsage(AttributeTargets.Class | AttributeTargets.Method, Inherited = true, AllowMultiple = false)]
    public class CustomAuthorize : ActionFilterAttribute
    {

        public override void OnActionExecuting(ActionExecutingContext filterContext)
        {
            bool skipAuthorization = filterContext.ActionDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true) ||
                                     filterContext.ActionDescriptor.ControllerDescriptor.IsDefined(typeof(AllowAnonymousAttribute), true);
            if (skipAuthorization) return;

            var authManager = HttpContext.Current.GetOwinContext().Authentication;
            if (authManager == null || authManager.User == null || authManager.User.Identity == null || !authManager.User.Identity.IsAuthenticated)
            {
                UnAuthorizedProcess(filterContext);// handle unathorized request here, we can redirect the request to main project from here
            }
            else
            {
                AuthorizedProcess(filterContext);// Do the default action or if we have any custom validation, write here.
            }
        }
}

Add attribute in all actions/controllers need to authorized.

 [CustomAuthorize]
 public ActionResult Dashboard()
 {
    return View();
 }

you have to give full URL as returnURL parameter to the main website.

Abdul Rahim
  • 159
  • 1
  • 8