How to add OpenID combined with Forms Authentication to MVC

Viewed 1324

I have an existing MVC project that uses FormsAuthentication for its authentication.

I need to incorporate the option of logging in with an OpenID IDP in addition to the regular login page already available.

The problem I'm having is challenging the IDP on demand and setting the authentication cookie once the claims are received, I can't find the reason why the cookie is not sticking. The flow seems to be working fine, and I can see the claims in the AuthorizationCodeReceived callback.

Here's the Startup.Auth.cs code:

var notificationHandlers = new OpenIdConnectAuthenticationNotifications
        {
            AuthorizationCodeReceived = (context) =>
            {
                string username = context.AuthenticationTicket.Identity.FindFirst("preferred_username").Value;
                FormsAuthenticationTicket authTicket = new FormsAuthenticationTicket(1, username, DateTime.Now, DateTime.Now.AddMinutes(60), true, "");
                string encryptedTicket = FormsAuthentication.Encrypt(authTicket);
                context.Response.Cookies.Append(FormsAuthentication.FormsCookieName, encryptedTicket);

                return Task.FromResult(0);
            },
            RedirectToIdentityProvider = (context) =>
            {
                if (context.OwinContext.Request.Path.Value != "/Account/SignInWithOpenId")
                {
                    context.OwinContext.Response.Redirect("/Account/Login");
                    context.HandleResponse();
                }
                return Task.FromResult(0);
            }
        };

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            AuthenticationType = "oidc",
            SignInAsAuthenticationType = "Cookies",
            Authority = "xxxxxxxxx",
            ClientId = "MyClient",
            ClientSecret = "xxxxxxxx",
            RedirectUri = "http://localhost:52389/",
            PostLogoutRedirectUri = "http://localhost:52389/",
            ResponseType = "code id_token",
            Scope = "openid profile email roles",
            UseTokenLifetime = false,
            TokenValidationParameters = new TokenValidationParameters()
            {
                NameClaimType = "preferred_username",
                RoleClaimType = "role"
            },
            Notifications = notificationHandlers                
        });

        app.SetDefaultSignInAsAuthenticationType("Cookies");

        app.UseCookieAuthentication(new CookieAuthenticationOptions()
        {
            AuthenticationType = "Cookies",
            AuthenticationMode = AuthenticationMode.Passive,
            LoginPath = new PathString("/Account/Login"),
            Provider = new CookieAuthenticationProvider()
        });

        app.UseStageMarker(PipelineStage.Authenticate);

And here's the AccountController SignInWithOpenId method:

public ActionResult SignInWithOpenId()
    {
        if (!Request.IsAuthenticated)
        {
            HttpContext.GetOwinContext().Authentication.Challenge(OpenIdConnectAuthenticationDefaults.AuthenticationType);

            // If I don't have this line, reponse redirects to the forms authentication login... so maybe something is wrong here?
            return new HttpUnauthorizedResult("IDP");
        }
        else
        {
            return RedirectToAction("Index", "Default");
        }
    }

Any pointers would be greatly appreciated. Thank you.

2 Answers

This is the exact thing I'm trying to do at the moment. I will let you know if I find anything useful.

Update: I ended up disabling Forms Authentication in the MVC web app. I was doing a proof of concept so it wasn't a hard requirement. I know this was not really what you were getting at. I successfully used my IdP to login and redirect back to the web app. Where the proof of concept ended was the HttpContext.User object was needed to be populated.

I was able to get this, or at least an equivalent, working in .NET 4.7. My use case is that most subscribers are being upgraded to log in via Azure AD B2C, but we have public PCs that we want to authenticate with a manual claim via an obscured URL.

I'm using Microsoft.Owin.Security.OpenIDConnect and related packages, and the Owin startup is standard, although I will point out this line:

app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

I had to disable Forms authentication entirely; I could not get this working when anything other than Anonymous Authentication was enabled in IIS.

The core of the solution was actually an example I found here: How to use OWIN forms authentication without aspnet identity

    /* URL validated, add authenticated claim */
    var claims = new List<Claim>
    {
        new Claim(ClaimTypes.Name, "PublicPC"),
        new Claim(ClaimTypes.Email, "PublicPC@example.org")
    };
    var id = new ClaimsIdentity(claims, CookieAuthenticationDefaults.AuthenticationType);

    var ctx = HttpContext.Current.GetOwinContext();
    var authenticationManager = ctx.Authentication;
    authenticationManager.SignIn(id);

But critically, I needed to specify CookieAuthenticationDefaults.AuthenticationType, which is what I'm using in the Owin startup.

Related