Draft: issue for EduardoPires/EquinoxProject
Type: bug report, with a PR to follow
Status: DRAFT, not posted
Verified on: master @ fde9a95, .NET SDK 9.0.316, unmodified clone
Title
Every request returns HTTP 500 when Facebook/Google credentials aren't configured
Body
AddSocialAuthenticationSupport registers the Facebook and Google handlers
unconditionally, so their options are always validated. Because remote authentication
options are validated lazily, on first use rather than at startup, so an app with no
social credentials starts perfectly cleanly and then fails every single request.
src/Equinox.Infra.CrossCutting.Identity/Configuration/AspNetIdentityConfig.cs:
builder.Services.AddAuthentication()
.AddFacebook(o =>
{
o.AppId = builder.Configuration["Authentication:Facebook:AppId"];
o.AppSecret = builder.Configuration["Authentication:Facebook:AppSecret"];
})
.AddGoogle(googleOptions =>
{
googleOptions.ClientId = builder.Configuration["Authentication:Google:ClientId"];
googleOptions.ClientSecret = builder.Configuration["Authentication:Google:ClientSecret"];
});
Why this affects real deployments
appsettings.json, the base file used by Production and any environment without its
own overrides, has no Authentication section at all:
| File | Authentication section |
|---|---|
appsettings.json |
absent |
appsettings.Development.json |
present ("SetYourDataHere" placeholders) |
appsettings.Staging.json |
present |
appsettings.Testing.json |
present |
So the configuration only exists in environments a developer runs locally. Deploy to Production and every request 500s.
What makes it hard to spot
The startup log is completely clean:
info: Microsoft.Hosting.Lifetime[14]
Now listening on: http://0.0.0.0:5000
info: Microsoft.Hosting.Lifetime[0]
Application started. Press Ctrl+C to shut down.
Any health check based on "did the process start" or "is the port open" reports success. The failure only appears on an actual request:
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[1]
An unhandled exception has occurred while executing the request.
System.ArgumentNullException: Value cannot be null. (Parameter 'AppId')
at Microsoft.AspNetCore.Authentication.Facebook.FacebookOptions.Validate()
at Microsoft.AspNetCore.Authentication.RemoteAuthenticationOptions.Validate(String scheme)
It also throws inside the error handler, so UseExceptionHandler("/error/500") cannot
render either:
fail: Microsoft.AspNetCore.Diagnostics.ExceptionHandlerMiddleware[3]
An exception was thrown attempting to execute the error handler.
System.ArgumentNullException: Value cannot be null. (Parameter 'AppId')
Reproduction
Isolated from the database configuration by running in Development (so SQLite is used) and
removing only the Authentication section, which is exactly the state of appsettings.json:
git clone --depth 1 https://github.com/EduardoPires/EquinoxProject.git
cd EquinoxProject
dotnet build Equinox.sln
python3 - <<'PY'
import json, io
p = 'src/Equinox.UI.Web/appsettings.Development.json'
d = json.load(io.open(p, encoding='utf-8-sig'))
d.pop('Authentication', None)
json.dump(d, open(p, 'w'), indent=2)
PY
cd src/Equinox.UI.Web
dotnet run --no-build --urls http://0.0.0.0:5000
Application started. <- healthy
curl -o /dev/null -w '%{http_code}' http://localhost:5000/
500
Suggested fix
Register each provider only when its credentials are present. Nothing else changes:
configured deployments behave exactly as before, and the placeholder values in
appsettings.Development.json keep working.
var facebookAppId = builder.Configuration["Authentication:Facebook:AppId"];
var facebookSecret = builder.Configuration["Authentication:Facebook:AppSecret"];
var googleClientId = builder.Configuration["Authentication:Google:ClientId"];
var googleSecret = builder.Configuration["Authentication:Google:ClientSecret"];
var authentication = builder.Services.AddAuthentication();
if (!string.IsNullOrWhiteSpace(facebookAppId) && !string.IsNullOrWhiteSpace(facebookSecret))
{
authentication.AddFacebook(o =>
{
o.AppId = facebookAppId;
o.AppSecret = facebookSecret;
});
}
if (!string.IsNullOrWhiteSpace(googleClientId) && !string.IsNullOrWhiteSpace(googleSecret))
{
authentication.AddGoogle(googleOptions =>
{
googleOptions.ClientId = googleClientId;
googleOptions.ClientSecret = googleSecret;
});
}
An app shouldn't hard-fail because an optional feature is unconfigured, and the current behaviour is especially awkward because the login page still advertises the providers.
Happy to open a PR with this if you'd like it. I have it running against a deployed instance.
Notes for me, not for the issue
- File the issue first, offer the PR. Sending an unsolicited PR to a maintained project reads worse than asking.
- Deliberately not mentioned: the SQL Server/SQLite provider split
(
DatabaseConfig.csandAspNetIdentityConfig.csboth branching onIsDevelopment()). That is arguably intentional, since they expect SQL Server in production, and mixing it in would muddy a clear, unambiguous bug. Separate issue if it's worth raising at all. - The repro isolates social auth from the database on purpose. Running
masterdirectly in a non-Development environment crashes earlier, onPendingModelChangesWarning: The model for context 'EquinoxContext' has pending changes, which is the provider mismatch and would obscure this bug entirely. - Do not claim "affects everyone". SQL Server deployments get past the database and then hit this; SQLite-in-production deployments never get that far.
Writing is AI assisted. Thoughts and publishing are human-gated.
Rendered from jloor/equinox-jsxcore at f34e312. The markdown in that repository is the source of truth; if this page disagrees with it, this page is stale.