1515 * integration tests override via {@code @TestPropertySource}.
1616 *
1717 * @param issuer Canonical issuer URI; populates the {@code iss} claim.
18+ * @param apiBasePath Public path prefix the reverse proxy strips before requests reach this app
19+ * (e.g. {@code /api} when Traefik strips {@code /api}); empty when the app is
20+ * served at the origin root (local dev). Prepended when building the absolute,
21+ * browser-reachable OAuth URLs — the authorization-request {@code redirect_uri}
22+ * and the {@code /oauth2/authorization} init redirect — so the IdP and the
23+ * callback land back on the proxied API path, not the SPA. It cannot be inferred
24+ * from the request: prod runs {@code forward-headers-strategy: native} (Tomcat
25+ * {@code RemoteIpValve}, kept for the pre-auth IP rate-limit trust model — see
26+ * {@code ProxyTrustGuard}), which restores forwarded host/proto but NOT
27+ * {@code X-Forwarded-Prefix}. Normalized to leading-slash / no-trailing-slash.
1828 * @param audience Default {@code aud} claim for SPA cookies.
1929 * @param accessTtl Cookie-JWT lifetime.
2030 * @param cookieName Access-token cookie name (the {@code __Host-} prefix is
6979@ ConfigurationProperties (prefix = "hephaestus.auth" )
7080public record AuthProperties (
7181 @ DefaultValue ("http://localhost:8080" ) URI issuer ,
82+ @ DefaultValue ("" ) String apiBasePath ,
7283 @ DefaultValue ("hephaestus-spa" ) String audience ,
7384 @ DefaultValue ("15m" ) Duration accessTtl ,
7485 @ DefaultValue (DEFAULT_COOKIE_NAME ) String cookieName ,
@@ -86,6 +97,23 @@ public record AuthProperties(
8697 */
8798 public AuthProperties {
8899 loginProviders = loginProviders == null ? Map .of () : loginProviders ;
100+ apiBasePath = normalizeApiBasePath (apiBasePath );
101+ }
102+
103+ /**
104+ * Coerce {@code apiBasePath} to the leading-slash / no-trailing-slash form the OAuth-URL builders
105+ * concatenate, so {@code api}, {@code /api} and {@code /api/} are equivalent and {@code /} or blank
106+ * mean root — a misconfigured value can't silently produce {@code hostapi/…} or a double slash.
107+ */
108+ private static String normalizeApiBasePath (String value ) {
109+ if (value == null ) {
110+ return "" ;
111+ }
112+ String trimmed = value .trim ().replaceAll ("/+$" , "" );
113+ if (trimmed .isEmpty ()) {
114+ return "" ;
115+ }
116+ return trimmed .startsWith ("/" ) ? trimmed : "/" + trimmed ;
89117 }
90118
91119 /**
0 commit comments