SpikerSoft.Api/Infrastructure/Authentication.cs (ConfigureKeycloakJwtBearer) drives two validation controls from config, and the effective production values (from appsettings.json; appsettings.Production.json does not override them, and no Keycloak__* env var does either) are:
Exploitability (evaluated — currently defense-in-depth, NOT an active bypass)
RequireHttpsMetadata=false: the OIDC metadata/JWKS is fetched from MetadataAddress = https://ids.spikersoft.com/realms/spikersoft/.well-known/openid-configuration. Because that URL is already HTTPS, the key material is fetched over TLS regardless of the flag — there is no MITM window today. The flag only fails to reject a hypothetical future http metadata URL.
ValidateIssuer=false: signing keys come from that single realm's JWKS and ValidateIssuerSigningKey defaults to true, so a token must be signed by the realm's private key to pass. An attacker cannot forge one, and there is no second trusted realm, so skipping the iss check does not enable a bypass today.
Both are, however, secure-default deviations that a security review flags and that become exploitable under config drift (e.g. someone points server-url at an http/internal Keycloak).
Recommended fix (NOT applied here — auth-config change with regression risk)
Enable both in the production layer only (leave dev/base as-is, where Keycloak may be http):
Keycloak:require-https = true — safe; prod server-url is already HTTPS, so no behaviour change, just fail-secure.
Keycloak:validate-issuer = true — verify first: confirm the realm's issued token iss equals the metadata issuer (https://ids.spikersoft.com/realms/spikersoft). If Keycloak's frontend URL (KC_HOSTNAME) ever differs from server-url, enabling this would reject valid tokens and cause an auth outage. Roll out behind a canary.
Not fixed blind here specifically to avoid a production auth regression.
## Finding
`SpikerSoft.Api/Infrastructure/Authentication.cs` (`ConfigureKeycloakJwtBearer`) drives two validation controls from config, and the effective **production** values (from `appsettings.json`; `appsettings.Production.json` does not override them, and no `Keycloak__*` env var does either) are:
- `Keycloak:require-https = False` -> `RequireHttpsMetadata = false`
- `Keycloak:validate-issuer = False` -> `ValidateIssuer = false`
## Exploitability (evaluated — currently defense-in-depth, NOT an active bypass)
- **RequireHttpsMetadata=false:** the OIDC metadata/JWKS is fetched from `MetadataAddress = https://ids.spikersoft.com/realms/spikersoft/.well-known/openid-configuration`. Because that URL is already HTTPS, the key material is fetched over TLS regardless of the flag — there is no MITM window today. The flag only fails to *reject* a hypothetical future http metadata URL.
- **ValidateIssuer=false:** signing keys come from that single realm's JWKS and `ValidateIssuerSigningKey` defaults to true, so a token must be signed by the realm's private key to pass. An attacker cannot forge one, and there is no second trusted realm, so skipping the `iss` check does not enable a bypass today.
Both are, however, secure-default deviations that a security review flags and that become exploitable under config drift (e.g. someone points `server-url` at an http/internal Keycloak).
## Recommended fix (NOT applied here — auth-config change with regression risk)
Enable both in the **production** layer only (leave dev/base as-is, where Keycloak may be http):
- `Keycloak:require-https = true` — safe; prod `server-url` is already HTTPS, so no behaviour change, just fail-secure.
- `Keycloak:validate-issuer = true` — **verify first**: confirm the realm's issued token `iss` equals the metadata issuer (`https://ids.spikersoft.com/realms/spikersoft`). If Keycloak's frontend URL (KC_HOSTNAME) ever differs from `server-url`, enabling this would reject valid tokens and cause an auth outage. Roll out behind a canary.
Not fixed blind here specifically to avoid a production auth regression.
Verify-first step completed; hardening up as backend PR #417.
Verification performed (the gate this ticket set before enabling validate-issuer):
Live discovery document: issuer = https://ids.spikersoft.com/realms/spikersoft — and the JwtBearer handler validates iss against exactly the issuer it fetched from metadata, so expected==actual by construction.
Repo-wide sweep: the public HTTPS host is the onlyKeycloak:server-url configured anywhere (Api base + Development; no worker or service uses an internal http://keycloak URL for token acquisition). Every token presented to the API therefore carries the matching iss. The KC_HOSTNAME-mismatch outage scenario cannot occur under current config.
PR scope: appsettings.Production.json gains a Keycloak section with require-https=true + validate-issuer=true (dev/base untouched, per this ticket's recommendation), documented in-file with the verification and the do-not-point-at-internal-URL caveat. Plus JwtValidationProductionConfigTests — loads the real base+Production layering and asserts both flags and the https server-url precondition, so the config drift this ticket flags as the actual risk now fails the build.
Merge is the deploy gate (nothing hot-patched). Will close on merge.
**Verify-first step completed; hardening up as backend PR [#417](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/417).**
Verification performed (the gate this ticket set before enabling `validate-issuer`):
1. Live discovery document: `issuer` = `https://ids.spikersoft.com/realms/spikersoft` — and the JwtBearer handler validates `iss` against exactly the issuer it fetched from metadata, so expected==actual by construction.
2. Repo-wide sweep: the public HTTPS host is the **only** `Keycloak:server-url` configured anywhere (Api base + Development; no worker or service uses an internal `http://keycloak` URL for token acquisition). Every token presented to the API therefore carries the matching `iss`. The KC_HOSTNAME-mismatch outage scenario cannot occur under current config.
PR scope: `appsettings.Production.json` gains a `Keycloak` section with `require-https=true` + `validate-issuer=true` (dev/base untouched, per this ticket's recommendation), documented in-file with the verification and the do-not-point-at-internal-URL caveat. Plus `JwtValidationProductionConfigTests` — loads the real base+Production layering and asserts both flags and the https `server-url` precondition, so the config drift this ticket flags as the actual risk now fails the build.
Merge is the deploy gate (nothing hot-patched). Will close on merge.
Resolved in backend PR #417 (merged to master 2026-07-18). Production now runs require-https=true + validate-issuer=true (verified safe: discovery issuer matches, no internal Keycloak URL exists anywhere), with JwtValidationProductionConfigTests failing the build on config drift. Closing.
Resolved in backend PR [#417](https://git.spikersoft.com/spikerj/spikersoft-backend/pulls/417) (merged to `master` 2026-07-18). Production now runs `require-https=true` + `validate-issuer=true` (verified safe: discovery issuer matches, no internal Keycloak URL exists anywhere), with `JwtValidationProductionConfigTests` failing the build on config drift. Closing.
Blocking a user prevents them from interacting with repositories, such as opening or commenting on pull requests or issues. Learn more about blocking a user.
Finding
SpikerSoft.Api/Infrastructure/Authentication.cs(ConfigureKeycloakJwtBearer) drives two validation controls from config, and the effective production values (fromappsettings.json;appsettings.Production.jsondoes not override them, and noKeycloak__*env var does either) are:Keycloak:require-https = False->RequireHttpsMetadata = falseKeycloak:validate-issuer = False->ValidateIssuer = falseExploitability (evaluated — currently defense-in-depth, NOT an active bypass)
MetadataAddress = https://ids.spikersoft.com/realms/spikersoft/.well-known/openid-configuration. Because that URL is already HTTPS, the key material is fetched over TLS regardless of the flag — there is no MITM window today. The flag only fails to reject a hypothetical future http metadata URL.ValidateIssuerSigningKeydefaults to true, so a token must be signed by the realm's private key to pass. An attacker cannot forge one, and there is no second trusted realm, so skipping theisscheck does not enable a bypass today.Both are, however, secure-default deviations that a security review flags and that become exploitable under config drift (e.g. someone points
server-urlat an http/internal Keycloak).Recommended fix (NOT applied here — auth-config change with regression risk)
Enable both in the production layer only (leave dev/base as-is, where Keycloak may be http):
Keycloak:require-https = true— safe; prodserver-urlis already HTTPS, so no behaviour change, just fail-secure.Keycloak:validate-issuer = true— verify first: confirm the realm's issued tokenissequals the metadata issuer (https://ids.spikersoft.com/realms/spikersoft). If Keycloak's frontend URL (KC_HOSTNAME) ever differs fromserver-url, enabling this would reject valid tokens and cause an auth outage. Roll out behind a canary.Not fixed blind here specifically to avoid a production auth regression.
Verify-first step completed; hardening up as backend PR #417.
Verification performed (the gate this ticket set before enabling
validate-issuer):issuer=https://ids.spikersoft.com/realms/spikersoft— and the JwtBearer handler validatesissagainst exactly the issuer it fetched from metadata, so expected==actual by construction.Keycloak:server-urlconfigured anywhere (Api base + Development; no worker or service uses an internalhttp://keycloakURL for token acquisition). Every token presented to the API therefore carries the matchingiss. The KC_HOSTNAME-mismatch outage scenario cannot occur under current config.PR scope:
appsettings.Production.jsongains aKeycloaksection withrequire-https=true+validate-issuer=true(dev/base untouched, per this ticket's recommendation), documented in-file with the verification and the do-not-point-at-internal-URL caveat. PlusJwtValidationProductionConfigTests— loads the real base+Production layering and asserts both flags and the httpsserver-urlprecondition, so the config drift this ticket flags as the actual risk now fails the build.Merge is the deploy gate (nothing hot-patched). Will close on merge.
Resolved in backend PR #417 (merged to
master2026-07-18). Production now runsrequire-https=true+validate-issuer=true(verified safe: discovery issuer matches, no internal Keycloak URL exists anywhere), withJwtValidationProductionConfigTestsfailing the build on config drift. Closing.