Microsoft OIDC library OnAuthorizationCodeReceived getting fired twice
I have an OIDC application, which was giving me correlation error for some time. We have managed to resolve it by passing correlation cookie properly. Our infrastructure has the following network topology.
Openshift Container(OIDC App) -> Reverser Proxy/Api gateway -> Identity Provider.
If we use boiler plate code, the callback path picks up the host of openshift, 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 set up. In development box, there is no issue but it does not have the reverse proxy set up(Now, 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?}");
});
}