Users of your web applications nowadays expect a lot of convenience and a good user experience. One aspect is authentication and authorization.
Many web apps started with local user databases or with organisational accounts, LDAP/AD for example. As security and UX requirements grow single-sign-on (SSO) and two-factor-authentication (2FA) quickly become hot topics.
To meet all the requirements and expectations integrating something like OpenID Connect (OIDC) looks like a good choice. The good news are that the already is mature support for .NET. In essence you simply add Microsoft.AspNetCore.Authentication.OpenIdConnect to your dependencies and configure it according to your needs mostly following official documentation.
I did all that for one of our applications and it was quite straightforward until I encountered some pitfalls (that may be specific to our deployment scenario but maybe not):
Pitfall 1: Using headers behind proxy
Our .NET 8 application is running behind a nginx reverse proxy which provides https support etc. OpenIDConnect uses several X-Forwarded-* headers to contruct some URLs especially the redirect_uri. To apply them to our requests we just apply the forwarded headers middleware: app.UseForwardedHeaders().
Unfortunately, this did not work neither for me nor some others, see for example https://github.com/dotnet/aspnetcore/issues/58455 and https://github.com/dotnet/aspnetcore/issues/57650. One workaround in the latter issue did though:
// TODO This should not be necessary because it is the job of the forwarded headers middleware we use above.
app.Use((context, next) =>
{
app.Logger.LogDebug("Executing proxy protocol workaround middleware...");
if (string.IsNullOrEmpty(context.Request.Headers["X-Forwarded-Proto"]))
{
return next(context);
}
app.Logger.LogDebug("Setting scheme because of X-Forwarded-Proto Header...");
context.Request.Scheme = (string) context.Request.Headers["X-Forwarded-Proto"] ?? "http";
return next(context);
});
Pitfall 2: Too large cookies
Another problem was, that users were getting 400 Bad Request – Request Header Or Cookie Too Large messages in their browsers. Deleting cookies and tuning nginx buffers and configuration did not fix the issue. Some users simply had too many claims in their organisation. Fortunately, this can be mitigated in our case with a few simple lines. Instead of simply using options.SaveTokens = true in the OIDC setup we implemented in OnTokenValidated:
var idToken = context.SecurityToken.RawData;
context.Properties!.StoreTokens([
new AuthenticationToken { Name = "id_token", Value = idToken }
]);
That way, only the identity token is saved in a cookie, drastically reducing the cookie sizes while still allowing proper interaction with the IDP, to perform a “full logout” for example .
Pitfall 3: Logout implementation in Frontend and Backend
Logging out of only your application is easy: Just call the endpoint in the backend and call HttpContext.SignOutAsync(CookieAuthenticationDefaults.AuthenticationScheme)there. On success clear the state in the frontend and you are done.
While this is fine on a device you are using exclusively it is not ok on some public or shared machine because your OIDC session is still alive and you can easily get back in without supplying credentials again by issueing another OIDC/SSO authentication request.
For a full logout three things need to be done:
- Local logout in application backend
- Clear client state
- Logout from the IDP
Trying to do this in our webapp frontend lead to a CORS violation because after submitting a POST request to the backend using a fetch()-call following the returned redirect in Javascript is disallowed by the browser.
If you have control over the IDP, you may be able to allow your app as an origin to mitigate the problem.
Imho the better option is to clear the client state and issue a javascript redirect by setting window.location.href to the backend-endpoint. The endpoint performs the local application logout and sends a redirect to the IDP logout back to the browser. This does not violate CORS and is very transparent to the user in that she can see the IDP logout like it was done manually.

