ASP.NET MVC 4.8 application with local users and Okta

Hello,

We are in the process of migrating a .Net 4.8 MVC application that uses local users accounts to Okta authentication. My goal is to authenticate the users via Okta and then link the external user with the internal ones (local db).

Using the code below, I encountered 2 issues:
1 - I always get a 400 error when logging out from Okta
2 - First login works fine, then I logout using the code below. On a 2nd attempt to login, in the ExternalLoginCallback method, the loginInfo object is null.

ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

I am using ASPNet.Okta middleware.

//----------------------------------------------------------------
// Startup.cs
//----------------------------------------------------------------
public void ConfigureAuth(IAppBuilder app)
{
    //Configure the db context, user manager and signin manager to use a single instance per request
    app.CreatePerOwinContext(ApplicationDbContext.Create);
    app.CreatePerOwinContext<ApplicationUserManager>(ApplicationUserManager.Create);
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);
   
    // Enable the application to use a cookie to store information for the signed in user
    // and to use a cookie to temporarily store information about a user logging in with a third party login provider
    // Configure the sign in cookie
    app.UseCookieAuthentication(new CookieAuthenticationOptions
    {
        //CookieManager = new SystemWebCookieManager(),

        AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,

        LoginPath = new PathString("/Account/Login"),

        Provider = new CookieAuthenticationProvider
        {
            // Enables the application to validate the security stamp when the user logs in.
            // This is a security feature which is used when you change a password or add an external login to your account.
            OnValidateIdentity = SecurityStampValidator.OnValidateIdentity<ApplicationUserManager, ApplicationUser>(
                    validateInterval: TimeSpan.FromMinutes(30),
                    regenerateIdentity: (manager, user) => user.GenerateUserIdentityAsync(manager)),
        }

    });

    //app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ExternalCookie);
    app.UseExternalSignInCookie(DefaultAuthenticationTypes.ExternalCookie);

    app.UseOktaMvc(new OktaMvcOptions()
    {
        OktaDomain = ConfigurationManager.AppSettings["okta:OktaDomain"],
        ClientId = ConfigurationManager.AppSettings["okta:ClientId"],
        ClientSecret = ConfigurationManager.AppSettings["okta:ClientSecret"],
        AuthorizationServerId = ConfigurationManager.AppSettings["okta:AuthorizationServerId"],
        RedirectUri = ConfigurationManager.AppSettings["okta:RedirectUri"],
        PostLogoutRedirectUri = ConfigurationManager.AppSettings["okta:PostLogoutRedirectUri"],
        GetClaimsFromUserInfoEndpoint = true,
        Scope = new List<string> { "openid", "profile", "email" },

        OpenIdConnectEvents = new OpenIdConnectAuthenticationNotifications
        {
            // add email 
            SecurityTokenValidated = (notification) =>
            {
                var claims = new List<Claim>();

                var email = notification.AuthenticationTicket.Identity.Claims.FirstOrDefault(x => x.Type == "email").Value;
                claims.Add(new Claim(ClaimTypes.Email, email));

                notification.AuthenticationTicket.Identity.AddClaims(claims);

                return Task.CompletedTask;
            },

        },
    });
  
}


//----------------------------------------------------------------
// Account controller
//----------------------------------------------------------------


   // POST: /Account/ExternalLogin
   [HttpPost]
   [AllowAnonymous]
   [ValidateAntiForgeryToken]
   public ActionResult ExternalLogin(string provider, string returnUrl)
   {
       // Request a redirect to the external login provider
       return new ChallengeResult(provider, Url.Action("ExternalLoginCallback", "Account", new { ReturnUrl = returnUrl }));
   }


 // GET: /Account/ExternalLoginCallback
 [AllowAnonymous]
 public async Task<ActionResult> ExternalLoginCallback(string returnUrl)
 {
     ExternalLoginInfo loginInfo = await AuthenticationManager.GetExternalLoginInfoAsync();

     if (loginInfo == null)
     {
         return RedirectToAction("Login");
     }

     // Sign in the user with this external login provider if the user already has a login
     var result = await SignInManager.ExternalSignInAsync(loginInfo, isPersistent: false);

     switch (result)
     {
         case SignInStatus.Success:
             return RedirectToAction("Index", "Claims");

	 // code removed for simplicity
     }
 }

 //
 // POST: /Account/LogOff
 [HttpPost]
 [ValidateAntiForgeryToken]
 public ActionResult LogOff()
 {
     if (HttpContext.User.Identity.IsAuthenticated)
     {
         AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie, 
                                         DefaultAuthenticationTypes.ExternalCookie
                                         /*, OktaDefaults.MvcAuthenticationType*/); // error 400 received from Okta

         return RedirectToAction("Index", "Home");
     }

     return RedirectToAction("Index", "Home");
 }


It turns out, I cannot re-login multiple times due to the fact that I am using Session, after user’s authentication. I managed to overcome this issue by implementing Session_Start event in the global.asax.cs.

// global.asax.cs file
 void Session_Start(object sender, EventArgs e)
 {
     // your code here, it will be executed upon session start
 }

I had the same issue with a different scenario (ASP.NET MVC app with Okta authentication only, no local user’s db). In this case the following code worked:

app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                // ...
                CookieManager = new SystemWebCookieManager()
            });
app.UseCookieAuthentication(new CookieAuthenticationOptions
            {
                // ...
                CookieManager = new SystemWebCookieManager()
            });

I still have the 400 error received from Okta if I am trying to logout from OktaDefaults.MvcAuthenticationType :

 //
 // POST: /Account/LogOff
 [HttpPost]
 [ValidateAntiForgeryToken]
 public ActionResult LogOff()
 {
     if (HttpContext.User.Identity.IsAuthenticated)
     {
         AuthenticationManager.SignOut(DefaultAuthenticationTypes.ApplicationCookie,
                                         DefaultAuthenticationTypes.ExternalCookie, 
                                         OktaDefaults.MvcAuthenticationType // error 400 received from Okta
                                         ); 

         return RedirectToAction("Index", "Home");
     }

     return RedirectToAction("Index", "Home");
 }