0

We have a .net MVC web application that uses ASP.NET Membership for user authentication. We also issue bearer tokens for our mobile application. I am attempting to add support for SSO using okta as an example. I have created a application in Okta and am attempting to sign into our application. My current flow is as follows:

  1. Open login page
  2. Click on "login with okta"
  3. Request received by our server. User is not authenticated, so a challenge is issued
  4. User is redirected to Okta's login page
  5. After successful login, a request is made to the login redirect url (which I suppose is captured by the OpenIdConnect middleware?)
  6. The AuthorizationCodeReceived event is triggered.
  7. The redirect completes, but no cookie is issued
  8. The user is redirected to the login page, but because they are not authenticated, it redirects to Okta to sign in. Loop back to step 4 and repeat.

It seems that after the login redirect and I set the correct claims, the result should be that a cookie should be issued and returned on the response to denote that the user is signed in. I'm not sure what I'm missing here, but if anyone can point me in the right direction, or explain what is happening I would very much appreciate it!

Below is a snippet from our Startup.cs:

public void ConfigureAuth(IAppBuilder app, bool allowInsecureHttp = false)
{
    // Configure the db context, user manager and signin manager to use a single instance per request
    app.CreatePerOwinContext(OwinIdentityDbContext.Create);
    app.CreatePerOwinContext<IdentityUserManager>(IdentityUserManager.Create);
    app.CreatePerOwinContext<ApplicationSignInManager>(ApplicationSignInManager.Create);

    app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
    {
        ClientId = GlobalConstants.Settings.OktaClientId,
        ClientSecret = GlobalConstants.Settings.OktaClientSecret,
        Authority = GlobalConstants.Settings.OktaDomain,
        AuthenticationType = "okta", // This is basically a "name" for this open id configuration
        RedirectUri = GlobalConstants.Settings.OktaRedirectUri,
        ResponseType = OpenIdConnectResponseType.CodeIdToken,
        Scope = $"{OpenIdConnectScope.OpenIdProfile} {OpenIdConnectScope.Email}", // Get the profile and email
        PostLogoutRedirectUri = GlobalConstants.Settings.OktaPostLogoutRedirectUri,
        TokenValidationParameters = new TokenValidationParameters
        {
            NameClaimType = "name"
        },

        Notifications = new OpenIdConnectAuthenticationNotifications
        {
            AuthorizationCodeReceived = async n =>
            {
                // Exchange code for access and ID tokens
                var tokenClient = new TokenClient(GlobalConstants.Settings.OktaDomain + "/v1/token", GlobalConstants.Settings.OktaClientId, GlobalConstants.Settings.OktaClientSecret);
                var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, GlobalConstants.Settings.OktaRedirectUri);

                if (tokenResponse.IsError)
                {
                    throw new Exception(tokenResponse.Error);
                }

                var userInfoClient = new UserInfoClient(GlobalConstants.Settings.OktaDomain + "/v1/userinfo");
                var userInfoResponse = await userInfoClient.GetAsync(tokenResponse.AccessToken);
                var claims = new List<Claim>();
                claims.AddRange(userInfoResponse.Claims);
                claims.Add(new Claim("id_token", tokenResponse.IdentityToken));
                claims.Add(new Claim("access_token", tokenResponse.AccessToken));

                if (!string.IsNullOrEmpty(tokenResponse.RefreshToken))
                {
                    claims.Add(new Claim("refresh_token", tokenResponse.RefreshToken));
                }

                // Add custom user claims here
                #region Add custom claims

                var email = claims.Find(_ => _.Type == "email")?.Value;

                claims.Add(new Claim(ClaimTypes.Name, email));
                claims.Add(new Claim(ClaimTypes.NameIdentifier, email));

                // Hardcoded for now. Discover these for real later
                claims.Add(new Claim(CustomClaimTypes.OurUserId, "{userId}"));
                claims.Add(new Claim(CustomClaimTypes.OurOrganizationId, "{orgId}"));

                #endregion

                // https://stackoverflow.com/questions/55797063/asp-net-owin-openid-connect-not-creating-user-authentication
                var identity = new ClaimsIdentity(DefaultAuthenticationTypes.ApplicationCookie, ClaimTypes.Name, ClaimTypes.Role);
                n.AuthenticationTicket = new AuthenticationTicket(identity, n.AuthenticationTicket.Properties);
                n.AuthenticationTicket.Identity.AddClaims(claims);

                return;
            },

            RedirectToIdentityProvider = n =>
            {
                // If signing out, add the id_token_hint
                if (n.ProtocolMessage.RequestType == OpenIdConnectRequestType.Logout)
                {
                    var idTokenClaim = n.OwinContext.Authentication.User.FindFirst("id_token");

                    if (idTokenClaim != null)
                    {
                        n.ProtocolMessage.IdTokenHint = idTokenClaim.Value;
                    }
                }

                return Task.CompletedTask;
            }
        },
    });

    app.UseCookieAuthentication(
        new CookieAuthenticationOptions
        {
            AuthenticationType = DefaultAuthenticationTypes.ApplicationCookie,
            CookieManager = new SystemWebCookieManager(),
            LoginPath = new PathString("/"),
            SlidingExpiration = true,
            CookieSecure = GlobalConstants.Settings.IsEnvironmentDev ? CookieSecureOption.SameAsRequest : CookieSecureOption.Always,
            Provider = new CookieAuthenticationProvider
            {
                OnApplyRedirect = context =>
                {
                    if (IsClientPortalRequest(context.Request))
                    {
                        context.Response.Redirect("/public/ClientPortalLogin");
                    }
                    else if (!IsAjaxRequest(context.Request) && !IsPublicApiRequest(context.Request) && !IsWopiRequest(context.Request))
                    {
                        context.Response.Redirect(context.RedirectUri);
                    }
                }
            }
        });

    // For Two Factor Log In
    app.UseTwoFactorSignInCookie(DefaultAuthenticationTypes.TwoFactorCookie, TimeSpan.FromMinutes(10));

    OAuthOptions = new OAuthAuthorizationServerOptions
    {
        AllowInsecureHttp = allowInsecureHttp, // NOTE: Only for unit tests
        TokenEndpointPath = new PathString("/token"),
        AccessTokenExpireTimeSpan = TimeSpan.FromDays(1),
        Provider = new CustomBearerTokenProvider()
    };

    // Configure Bearer Token Generation
    app.UseOAuthAuthorizationServer(OAuthOptions);

    // Needed to Authenticate Bearer Tokens
    app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions());

    // Set Cookies are the default type
    app.SetDefaultSignInAsAuthenticationType(DefaultAuthenticationTypes.ApplicationCookie);
}

Here is a screenshot of our okta application configuration: enter image description here

chronolinq
  • 113
  • 1
  • 11
  • Is an authentication cookie being created by the server and saved on the browser? Can you view it using developer tools in the browser? – TejSoft Mar 26 '20 at 03:28
  • Whe I inspect the response's cookie, there is one cooke with a name of "OpenIdConnect.nonce.lmY6ycbG52f%2BQeGNuvsb32vwVZnKZSPVof0xJ7VE%2FgY%3D" which is the name of the request's cookie and the value is blank – chronolinq Mar 26 '20 at 23:46
  • @chronolinq did you ever resolve this issue ? I am updating a legacy ASPNET MVC 5 app to use OpenIdConnect and have the exact same symptoms - auth works but it redirects to the Home controller with no ApplicationCookie set and so redirects back to the Idp login page which auths straight away, redirects back to Home etc etc - I dont know why the ApplicationCookie is not being set, the OpenIdConnect.nonce and OpenIdConnect.nv cookies are set during the auth process and cleared on Home redirect from /signin-oidc (which OpenIdConnect middleware must be handling) - keen to hear from you :) – steve May 06 '21 at 08:55

1 Answers1

1

I had the same problem and this is what I discovered. The issue was not in my wire up code, it was actually in my controller class. I had an attribute on my controller action that was requiring the user to be in certain roles. This is ultimately what caused my redirect loop.

If you have that, you need to get rid of it and find a different way of handling the roles.

This shows the offending code: Circled code is what I removed.

Here is the link to the article that caused me to have my "aha" moment:

https://www.blinkingcaret.com/2016/01/20/authorization-redirect-loops-asp-net-mvc/

Hope this helps someone, took me too long to figure this out.

StarGazer
  • 11
  • 4