Asp.net core Microsoft OIDC library OnAuthorizedCode recieved event getting fired twice

643 Views Asked by At

I have an OIDC application, which was giving me a correlation error for some time. We have managed to resolve it by passing the correlation cookie properly. Our infrastructure has the following network topology.

Openshift Container(OIDC App) -> Reverser Proxy/Api gateway -> Identity Provider.

If we use boilerplate code, the callback path picks up the host of open shift, hence the redirection not working correctly. We are now building the URL on the event onRedirectToIdentitityProvider.

We are also using OnAuthorizationCodeReceived event to make the /Token call as the default configuration was not working out.

We get callback properly with Authorization code but we see onAuthorizationCodeRecieved getting fired twice. This is happening only in the test environment with the reverse proxy setup. In the development box, there is no issue but it does not have the reverse proxy set up(Now, a reverse proxy is just a passthrough so not sure how that could break things).

Code Snippet Below

 public void ConfigureServices(IServiceCollection services)
        {
            string _authorityAPI = Configuration.GetValue<string>("authority");
            string _org = Configuration.GetValue<string>("orgDomain");
            string clientId = Configuration.GetValue<string>("ClientId");
            string clientSecret = Configuration.GetValue<string>("ClientSecret");


            services.Configure<CookiePolicyOptions>(options =>
            {
                options.CheckConsentNeeded = context => true;
                options.MinimumSameSitePolicy = SameSiteMode.None;
                options.Secure = CookieSecurePolicy.Always;
                options.HandleSameSiteCookieCompatibility();
           

            
            services.AddAuthentication(auth =>
            {
                auth.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme;
                auth.DefaultChallengeScheme = OpenIdConnectDefaults.AuthenticationScheme;
                auth.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie(options =>
            {
                options.LoginPath = "/Profile/Index/";
                options.LogoutPath = "/Profile/Logout/";
            })
           
            .AddOpenIdConnect(options =>
            {

                options.ClientId = clientId;
                options.ClientSecret = clientSecret;
                options.Authority = Configuration.GetValue<string>("Authority");
                options.CallbackPath = "/web/auth/callback";
                options.ResponseType = OpenIdConnectResponseType.Code;
                options.MetadataAddress = string.Format("{0}/.well-known/openid-configuration", _authorityAPI);
                options.TokenValidationParameters.ValidateIssuer = false;
                //options.GetClaimsFromUserInfoEndpoint = true;
                options.RequireHttpsMetadata = true;
                options.SaveTokens = true;
                options.Scope.Add("openid");
                options.Scope.Add("email");
                options.Scope.Add("profile");
                options.NonceCookie.SameSite = SameSiteMode.None;
                options.CorrelationCookie.SameSite = SameSiteMode.None;
                options.NonceCookie.Path = "/";
                options.CorrelationCookie.Path = "/";
                
                options.ConfigurationManager = new ConfigurationManager<OpenIdConnectConfiguration>(options.MetadataAddress, new ApiGatewayRetriever(_authorityAPI, _org));
                options.Events = new OpenIdConnectEvents()
                {
                    OnRedirectToIdentityProvider = (context) =>
                    {
                        if (context.ProtocolMessage.RequestType == OpenIdConnectRequestType.Authentication)
                        {
                           
                                context.ProtocolMessage.Parameters.Clear();
                                context.ProtocolMessage.IssuerAddress = centralLogin + "?RedirectURL=https://" + GetRequestHostName(context, Configuration);
                                return Task.FromResult(0);
                            
                        }
                        
                        return Task.FromResult(0);
                    }
                    ,
                    OnAuthorizationCodeReceived = authorizationCtx =>
                    {
                        try
                        {
                            BellLogger.WriteLog("OnAuthorizationCodeReceived start:" + authorizationCtx.TokenEndpointRequest.Code, Framework.Common.LogType.Info);

                            HttpClient httpClient = new HttpClient();
                            TokenClientOptions tokenClientOptions = new TokenClientOptions()
                            {
                                ClientId = clientId,
                                ClientSecret = clientSecret,
                                Address = string.Format("{0}/v1/token", _authorityAPI)
                            };
                            var tokenClient = new TokenClient(httpClient, tokenClientOptions);

                            var tokenResponse = await tokenClient.RequestAuthorizationCodeTokenAsync(authorizationCtx.TokenEndpointRequest.Code, authorizationCtx.TokenEndpointRequest.RedirectUri).Result;
                    authorizationCtx.HandleCodeRedemption(tokenResponse.AccessToken, tokenResponse.IdentityToken);

                           
                        }
                        catch (Exception ex)
                        {
                            Logger.WriteLog(ex, Framework.Common.LogType.Error);
                        }

                        return Task.FromResult(0);
                    }
                };

            });
            services.AddSession(options =>
            {
                options.Cookie.Name = ".lLogin.Session";
                options.IdleTimeout = TimeSpan.FromSeconds(10);
                options.Cookie.IsEssential = true;
            });
            services.ConfigureApplicationCookie(options =>
            {
                options.Cookie.HttpOnly = true;
                options.Cookie.SecurePolicy = Microsoft.AspNetCore.Http.CookieSecurePolicy.Always;
            });
            services.Configure<ForwardedHeadersOptions>(options =>
            {
                options.ForwardedHeaders = ForwardedHeaders.All;
            });
            services.AddControllersWithViews();
            services.AddSingleton<IHttpContextAccessor, HttpContextAccessor>();
        }

    
        private void CheckSameSite(HttpContext httpContext, CookieOptions options)
        {
            if (options.SameSite == SameSiteMode.None)
            {
                var userAgent = httpContext.Request.Headers["User-Agent"].ToString();
                // TODO: Use your User Agent library of choice here.
                if (userAgent.Contains("Chrome/5") || userAgent.Contains("Chrome/6") || userAgent.Contains("Chrome/9"))
                {
                    // For .NET Core < 3.1 set SameSite = (SameSiteMode)(-1)
                    options.SameSite = SameSiteMode.Unspecified;
                }
            }
        }


    
            app.UseHttpsRedirection();
            app.UseStaticFiles();
            //app.UseAntiXssMiddleware();
            app.UseCookiePolicy();
            app.UseAuthentication();
            app.UseRouting();
            app.UseSession();
            app.UseAuthorization();
            app.UseForwardedHeaders();

            app.UseEndpoints(endpoints =>
            {
                endpoints.MapControllerRoute(
                    name: "default",
                    pattern: "{controller=Home}/{action=Index}/{id?}");
            });
        }
2

There are 2 best solutions below

1
Gary Archer On

This might be a redirect issue and it is worth understanding things in terms of URLs rather than C# code. A response to the authorization code flow looks something like this:

It is possible that the reverse proxy - or the web server - or the openid provider - expects the response to have (or not have) a trailing backslash. Eg the first URL causes a redirect to the second:

I would put some log statements in OnAuthorizationCodeReceived and maybe also trace browser / HTTP traffic to see if this type of thing is happening.

0
Jith123 On

This may not be the answer to the above question, however we solved the problem.

We removed all the custom implementation of events including onAuthorizationCodeReceived.

The issue with the default configuration was, when the reverse proxy routes the call to the OCP environment, the host header was getting added as of the OCP. Reverse proxy added the header of the proper host and then everything started working fine. Host header was the important value.