Login is stuck in an endless redirect loop, using Asp.NET MVC

Hi all,

We have spent the last couple of weeks making a web app that we published for beta last Friday. We followed the guide on https://developer.okta.com/quickstart/#/okta-sign-in-page/dotnet/aspnet4 to integrate it with Okta, and everything worked fine in localhost. However, after we published to Microsoft Azure as an App Service, the login process had some various results.

  • On my colleagues’ laptops, the callback page returns a 302 but the user is redirected between callback and Okta.
  • On my laptop where I am testing, the callback page returns a 404, but it will loop back and forth between the callback page and the Okta page regardless.
  • On my home PC, I was actually able to login in the weekend.
    In localhost, everything works as it should.

Today, I tried to download the example project directly from github (https://github.com/oktadeveloper/okta-aspnet-mvc-example). I only changed the app settings, and tried to run. This fresh project seems to have the same redirect issue for us for both localhost and our Azure web app; stuck on redirect after login.

My issue seems to maybe be incompletely discussed here: Too Many Redirects
It looks like both my projects have the same issue, so to answer the last post here, everything looks exactly as the github page. Needless to say, I am a bit confused of this whole thing, having localhost work in one place and not the other, and experiencing different results everywhere.

Does anyone have any suggestions, or needs more details? Since not even the demo example are working, could there be any settings in Okta we are messing up? We use the same application on both projects.

Thanks in advance for any help :slight_smile:

Hi @marius, that is indeed weird. I appreciate all the detail you’ve posted. What’s strange is that we haven’t been able to reproduce the issue by pushing the example project to Azure App Service.

Can you share more about how your application and code are configured? It would be helpful to see both the settings in the Okta Application details and Startup.cs. If you don’t want to post those publicly, feel free to send them to me in a private message. It would also be helpful to see a Fiddler trace of the bad redirect loop.

Sorry that it’s giving you trouble. I’m sure we can get it figured out. :slight_smile:

Thanks for helping me :slight_smile:
I will send you a private message with some additional info. Since the demo project is open for all and give me the same error, I can use this - so the startup file I try with is the same one.

I also made another discovery. During the loop, if I press back, it will display an error message. However, if I refresh the page, I am logged in, and logging out and in seems to be working as normal for that browser. Using a new browser (or a new PC) reproduces the problem.

EDIT: It seems the issue on the fresh published server was a http/https issue. When publishing a new app service to Azure, unless specified, this will be http. Switching this to https and loging in seems to always work :slight_smile: I already tried this in the main project, so didnt think much of it until now.
Our main project is using a custom domain. I am gonna look some more into if this may cause any troubles, before sending any PMs.

No problem. It definitely could be caused by an incorrect HTTPS configuration. Let me know if you find anything!

I have been working on this pretty much all day. The Github demo seems to be a http/https-thing. After I discovered this, I dismissed it and retried my main project.

I am still not able to reproduce the issue on command, but if I clear my browser caches or switch between browsers a couple of times, it will get stuck in the loop on the 2nd or 3rd attempt almost always, also in localhost, which means I have been somewhat able to debug it for a little bit!

Since the demo project worked, I have for now also dismissed the idea that something is configured weird in Okta. I have compared NuGets, but it looks like all the relevant packages are up to date in both projects. I have copy-pasted the Startup.cs file from the demo project, and added a few lines to get some additional claims, but adding these lines also worked in the demo project. AppSettings are updated with correct URLs as well.

I have put breakpoints in the AuthorizationCodeReceived and RedirectToIdentityProvider in the Startup file, and it will jump back and forth between these two during the loop. This will happen during a redirect from the Okta login page back to my pages.

Using the Google Chrome Network tab, I have also captured a list of the URLs being called in a successful login VS unsuccessful:

Successful
MyPage (status code 302)
authorize?client_id=0oa1…
login.html?form=URI=…
… (various png, jpeg, font)
authn
sessionCookieRedirect?checkAccountSet…
redirect?okta_key=wYm…
callback
MyPage (status code 200)

Unsuccessful
MyPage (status code 302)
authorize?client_id=0oa1…
login.html?form=URI=…
… (various png, jpeg, font)
authn
sessionCookieRedirect?checkAccountSet…
redirect?okta_key=wYm…
callback
MyPage (status code 302)
authorize?client_id=0oa1…
callback
MyPage (status code 302)
authorize?client_id=0oa1…
callback
MyPage (status code 302)
… and so on.

I have also added a filter/attribute to handle roles, called “OktaAuthorize”. This is the biggest change between my main project and my biggest suspicion when it comes to whats messing things up, although it does not do much more than just check claims and get the role of the user. If I use the OktaAuthorize attribute instead of Authorize and set a breakpoint in it, it will be added to the redirect loop, along with AuthorizationCodeReceived and RedirectToIdentityProvider, and I can see that the HttpContext.Current.GetOwinContext().Authentication.User.Claims returns an empty list. On a successful login, this list includes all the claims from the Startup.cs.

PS: Both the OktaAuthorize and the normal Authorize filters can make the login an infinite loop.

Here is my OktaAuthorize file:
public class OktaAuthorizeAttribute : AuthorizeAttribute
{

    public TechstepUserRole UserRole { get; private set; }

    public OktaAuthorizeAttribute() : this (TechstepUserRole.All) { }

    public OktaAuthorizeAttribute(TechstepUserRole userRole)
    {
        UserRole = userRole;
    }

    public override void OnAuthorization(System.Web.Mvc.AuthorizationContext filterContext)
    {
        var claims = HttpContext.Current.GetOwinContext().Authentication.User.Claims;

        // Check claims and get the role
        bool valid = Verify(claims);

        if (claims.Any() && !valid)
        {
            var result = new ContentResult { Content = "unauthorized" };
            filterContext.Result = result;
        }
        else if (valid)
        {
            return;
        }
        else
        {
            base.OnAuthorization(filterContext);
        }
    }
}

And here is my Startup.cs, just incase:
public class Startup
{
// These values are stored in Web.config. Make sure you update them!
private readonly string clientId = ConfigurationManager.AppSettings[“okta:ClientId”];
private readonly string redirectUri = ConfigurationManager.AppSettings[“okta:RedirectUri”];
private readonly string authority = ConfigurationManager.AppSettings[“okta:OrgUri”];
private readonly string clientSecret = ConfigurationManager.AppSettings[“okta:ClientSecret”];
private readonly string postLogoutRedirectUri = ConfigurationManager.AppSettings[“okta:PostLogoutRedirectUri”];

    /// <summary>
    /// Configure OWIN to use OpenID Connect to log in with Okta.
    /// </summary>
    /// <param name="app"></param>
    public void Configuration(IAppBuilder app)
    {
        app.SetDefaultSignInAsAuthenticationType(CookieAuthenticationDefaults.AuthenticationType);

        app.UseCookieAuthentication(new CookieAuthenticationOptions());

        app.UseOpenIdConnectAuthentication(new OpenIdConnectAuthenticationOptions
        {
            ClientId = clientId,
            ClientSecret = clientSecret,
            Authority = authority,
            RedirectUri = redirectUri,
            ResponseType = OpenIdConnectResponseType.CodeIdToken,
            Scope = OpenIdConnectScope.OpenIdProfile,
            PostLogoutRedirectUri = postLogoutRedirectUri,
            TokenValidationParameters = new TokenValidationParameters
            {
                NameClaimType = "name"
            },

            Notifications = new OpenIdConnectAuthenticationNotifications
            {
                AuthorizationCodeReceived = async n =>
                {
                    // Exchange code for access and ID tokens
                    var tokenClient = new TokenClient(authority + "/v1/token", clientId, clientSecret);
                    var tokenResponse = await tokenClient.RequestAuthorizationCodeAsync(n.Code, redirectUri);

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

                    var userInfoClient = new UserInfoClient(authority + "/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));

                    // Get claims from Access Token
                    var handler = new JwtSecurityTokenHandler();
                    var tkn = handler.ReadToken(tokenResponse.AccessToken) as JwtSecurityToken;
                    var mytosSubsId = tkn.Claims.FirstOrDefault(c => c.Type == "mytosSubsId") ?? new Claim("mytosSubsId", "");
                    claims.Add(new Claim("mytos_subs_id", mytosSubsId.Value));
                    var agentSalesId = tkn.Claims.FirstOrDefault(c => c.Type == "agentSalesId") ?? new Claim("agentSalesId", "");
                    claims.Add(new Claim("agent_sales_id", agentSalesId.Value));
                    var techstepUserId = tkn.Claims.FirstOrDefault(c => c.Type == "techstepUserId") ?? new Claim("techstepUserId", "");
                    claims.Add(new Claim("techstep_user_id", techstepUserId.Value));

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

                    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;
                }
            },
        });
    }
}

Hope you can spot something that may cause some trouble!

This sounds very similar to the issue I’ve been helping @JDizonCK with: Local to Test/QA Server Application Testing

In both cases the application works fine locally but gets stuck in a loop on IIS. I don’t think it’s anything wrong with the Okta configuration (as you said).

@marius and @JDizonCK, can you give me some additional info?

  • What server OS and version of IIS your code is running on (if you know it)
  • The IIS logs during the redirect loop (if you have access to them)
  • What browser you are using to test with

Sure I’ll message / email it to you.

I will try find this out, and message them to you as well.
For now, I can tell my problem is encountered in all browsers I have tried. One login may work. And a second login may work. But once one login stops working, it will stop working for all browsers (unless already logged in from a previous attempt).

@marius We solved the infinite loop issue on our end. @nate.barbettini pointed me to an article https://github.com/IdentityServer/IdentityServer3/issues/542#issuecomment-76750192 and did the solution of adding a dummy “Session_Start” method in the Global.asax.cs

The fix is a code to be added in your MVC global.asax.cs

void Session_Start(object sender, EventArgs e)
{
    // place holder to solve endless loop issue
}

Hope this helps you.

This seems to have done the trick for us as well! I am not able to reproduce the issue anymore on my end, and I have tried with several laptops and browsers :smiley:

Thanks a lot for the tip! I will post a confirmation after the Easter hollidays :slight_smile:

1 Like

Hello Folks,
Hope you all are doing well.
I am running Asp.Net webform application for OKTA login but facing the issue after the click on Login button.Once Click on Login button below error is coming,
if (tokenResponse.IsError)
{ throw new Exception(tokenResponse.Error); }

Please find screen shots for same.
Please help me out or any suggestion if you have.


To resolve this issue: you can upgrade your application to use ASP.NET Core. If you must continue stay on ASP.NET, perform the following:

Update your application’s Microsoft.Owin.Host.SystemWeb package be at least version and Modify your code to use one of the new cookie manager classes, for example something like the following:

    app.UseCookieAuthentication(new CookieAuthenticationOptions 
    { 
        AuthenticationType = "Cookies", 
        CookieManager = new Microsoft.Owin.Host.SystemWeb.SystemWebChunkingCookieManager() 
    });

Reference Link