diff --git a/Core/Config/AppDomainOptions.cs b/Core/Config/AppDomainOptions.cs
index 57d1e95..d140333 100644
--- a/Core/Config/AppDomainOptions.cs
+++ b/Core/Config/AppDomainOptions.cs
@@ -26,4 +26,11 @@ public class AppDomainOptions
return domain + "/" + path.TrimStart('/');
}
+
+ ///
+ /// The configured domain as an absolute base (scheme + host [+ port]), or
+ /// null when no domain is set or it can't be parsed.
+ ///
+ public Uri? GetBaseUri() =>
+ BuildUrl("/") is { } url && Uri.TryCreate(url, UriKind.Absolute, out var uri) ? uri : null;
}
diff --git a/Program.cs b/Program.cs
index b2dcfcf..fe10c37 100644
--- a/Program.cs
+++ b/Program.cs
@@ -309,6 +309,23 @@ var app = builder.Build();
// Must run before anything that inspects the request scheme/IP (auth, OIDC, cookies).
app.UseForwardedHeaders();
+// When App__Domain is set, rewrite every request's scheme + host to the public domain. The
+// framework builds absolute URLs (the cookie login redirect, the OIDC redirect_uri, …) from
+// Request.Scheme/Host; behind a proxy that doesn't forward the Host header these are the
+// internal host (server IP:port), so loading https:/// would 302 to http://:8080.
+// Forcing the host here keeps every generated URL on the public domain. Must run before auth.
+var publicBaseUri = appDomain.GetBaseUri();
+if (publicBaseUri is not null)
+{
+ var publicHost = HostString.FromUriComponent(publicBaseUri);
+ app.Use((context, next) =>
+ {
+ context.Request.Scheme = publicBaseUri.Scheme;
+ context.Request.Host = publicHost;
+ return next(context);
+ });
+}
+
// ── First-run bootstrap ───────────────────────────────────────────────────────
// Seed a local admin when no users exist yet, so a plain-HTTP / LAN deployment that
// can't use Microsoft OIDC (which requires HTTPS + a matching Entra redirect URI) can
diff --git a/README.md b/README.md
index 91000e9..80642e8 100644
--- a/README.md
+++ b/README.md
@@ -46,6 +46,8 @@ These are separate and registered on **different** Entra apps. Don't conflate th
> **HTTPS note.** The sign-in app is a confidential (Web) client, so Entra requires its `/signin-oidc` redirect URI to be **HTTPS** — plain HTTP is allowed only for `http://localhost`, not a LAN host/IP. To run OIDC on a plain-HTTP LAN deployment, put the app behind an HTTPS-terminating reverse proxy: register `https://your-host/signin-oidc`, and the app honours `X-Forwarded-Proto` (see `UseForwardedHeaders`) to build the correct `https` redirect. Without a proxy, OIDC sign-in won't work over a non-localhost HTTP host — use the local email/password login instead.
+> **Reverse-proxy host.** Set `App__Domain` so the app builds every redirect (cookie login, OIDC) against the public domain regardless of what host the proxy forwards. Without it, a proxy that doesn't forward the `Host` header makes the app 302 to the internal `IP:port` it actually received.
+
Persistent state (profiles, settings, templates, logs, exports, certs) lives in `DataFolder`.
## Installation — Docker (prebuilt image)