Koda (Garmin Connect IQ app, Forerunner 965) shows "Native login failed -400" after its first sync.
Traefik access log, 2026-09-29: every failing sync does GET /rest/getPlaylist.view (200) and, in the same second, POST /auth/login → 302, 0 bytes (the navidrome-gate redirect), then no stream requests:
13:18:13 POST /auth/login 302 0
13:18:35 POST /auth/login 302 0
13:24:11 POST /auth/login 302 0
13:26:20 POST /auth/login 302 0
13:34:10 POST /auth/login 302 0
Connect IQ error -400 is INVALID_HTTP_BODY_IN_NETWORK_RESPONSE ("Response body data is invalid for the request type", Toybox.Communications). Only /rest and /share bypass the gate today, so Navidrome's native login is unreachable for any client that can't follow the SSO redirect. (The first sync at 11:00 UTC made no /auth/login call, which is why it worked; what makes Koda switch to the native API is on the app side.)
Change
One IngressRoute (navidrome-native-api, priority 1000):
→ navidrome:80 via the existing navidrome-strip-identity middleware.
Requests without an Authentik session cookie (watch, apps) reach Navidrome directly and authenticate with the Navidrome password (built-in login rate limit 5/20s), then JWT for /api. Same exposure class as /rest today.
Requests with the cookie (the web UI, cookie authentik_proxy_<id>, Domain=renner.uno, observed live) don't match and keep going through the gate, so SSO for the web UI is unchanged.
X-Authentik-Username is blanked on this route, so the ExtAuth header can't be spoofed.
/app (the UI itself) stays gated.
Matcher syntax checked against the Traefik v3 rules reference (cluster runs traefik v3.7.4). Entrypoint-default middlewares (security headers, rate limit) still apply on websecure.
Verification done
kubectl kustomize kubernetes/apps/navidrome builds; server-side dry-run of ingress.yaml accepts the IngressRoute.
After merge (I'll check)
curl -X POST https://navidrome.renner.uno/auth/login without a cookie → JSON from Navidrome (not a 302).
Same request with an authentik_proxy_ cookie → still handled by the gate.
Web UI still logs in via SSO; watch sync completes (Traefik log shows /auth/login 200 and stream calls).
## Problem
Koda (Garmin Connect IQ app, Forerunner 965) shows **"Native login failed -400"** after its first sync.
Traefik access log, 2026-09-29: every failing sync does `GET /rest/getPlaylist.view` (200) and, in the same second, `POST /auth/login` → **302, 0 bytes** (the navidrome-gate redirect), then no `stream` requests:
```
13:18:13 POST /auth/login 302 0
13:18:35 POST /auth/login 302 0
13:24:11 POST /auth/login 302 0
13:26:20 POST /auth/login 302 0
13:34:10 POST /auth/login 302 0
```
Connect IQ error -400 is `INVALID_HTTP_BODY_IN_NETWORK_RESPONSE` ("Response body data is invalid for the request type", [Toybox.Communications](https://developer.garmin.com/connect-iq/api-docs/Toybox/Communications.html)). Only `/rest` and `/share` bypass the gate today, so Navidrome's native login is unreachable for any client that can't follow the SSO redirect. (The first sync at 11:00 UTC made no `/auth/login` call, which is why it worked; what makes Koda switch to the native API is on the app side.)
## Change
One `IngressRoute` (`navidrome-native-api`, priority 1000):
```
Host(`navidrome.renner.uno`) && (PathPrefix(`/auth`) || PathPrefix(`/api`)) && !HeaderRegexp(`Cookie`, `authentik_proxy_`)
```
→ `navidrome:80` via the existing `navidrome-strip-identity` middleware.
- **Requests without an Authentik session cookie** (watch, apps) reach Navidrome directly and authenticate with the Navidrome password (built-in login rate limit 5/20s), then JWT for `/api`. Same exposure class as `/rest` today.
- **Requests with the cookie** (the web UI, cookie `authentik_proxy_<id>`, Domain=renner.uno, observed live) don't match and keep going through the gate, so SSO for the web UI is unchanged.
- `X-Authentik-Username` is blanked on this route, so the ExtAuth header can't be spoofed.
- `/app` (the UI itself) stays gated.
Matcher syntax checked against the [Traefik v3 rules reference](https://doc.traefik.io/traefik/reference/routing-configuration/http/routing/rules-and-priority/) (cluster runs traefik v3.7.4). Entrypoint-default middlewares (security headers, rate limit) still apply on `websecure`.
## Verification done
- `kubectl kustomize kubernetes/apps/navidrome` builds; server-side dry-run of `ingress.yaml` accepts the IngressRoute.
## After merge (I'll check)
- `curl -X POST https://navidrome.renner.uno/auth/login` without a cookie → JSON from Navidrome (not a 302).
- Same request with an `authentik_proxy_` cookie → still handled by the gate.
- Web UI still logs in via SSO; watch sync completes (Traefik log shows `/auth/login` 200 and `stream` calls).
🤖 Generated with [Claude Code](https://claude.com/claude-code)
Koda (Garmin Connect IQ) calls POST /auth/login during sync. Only /rest
and /share bypassed the navidrome-gate, so the gate answered 302 with an
empty body and the watch failed with -400
INVALID_HTTP_BODY_IN_NETWORK_RESPONSE ("Native login failed -400").
An IngressRoute sends /auth and /api straight to Navidrome when the
request carries no authentik_proxy_ session cookie; requests with the
cookie (the web UI) keep going through the gate, so SSO is unchanged.
X-Authentik-Username is stripped on this route as on /rest.
Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
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.
Problem
Koda (Garmin Connect IQ app, Forerunner 965) shows "Native login failed -400" after its first sync.
Traefik access log, 2026-09-29: every failing sync does
GET /rest/getPlaylist.view(200) and, in the same second,POST /auth/login→ 302, 0 bytes (the navidrome-gate redirect), then nostreamrequests:Connect IQ error -400 is
INVALID_HTTP_BODY_IN_NETWORK_RESPONSE("Response body data is invalid for the request type", Toybox.Communications). Only/restand/sharebypass the gate today, so Navidrome's native login is unreachable for any client that can't follow the SSO redirect. (The first sync at 11:00 UTC made no/auth/logincall, which is why it worked; what makes Koda switch to the native API is on the app side.)Change
One
IngressRoute(navidrome-native-api, priority 1000):→
navidrome:80via the existingnavidrome-strip-identitymiddleware./api. Same exposure class as/resttoday.authentik_proxy_<id>, Domain=renner.uno, observed live) don't match and keep going through the gate, so SSO for the web UI is unchanged.X-Authentik-Usernameis blanked on this route, so the ExtAuth header can't be spoofed./app(the UI itself) stays gated.Matcher syntax checked against the Traefik v3 rules reference (cluster runs traefik v3.7.4). Entrypoint-default middlewares (security headers, rate limit) still apply on
websecure.Verification done
kubectl kustomize kubernetes/apps/navidromebuilds; server-side dry-run ofingress.yamlaccepts the IngressRoute.After merge (I'll check)
curl -X POST https://navidrome.renner.uno/auth/loginwithout a cookie → JSON from Navidrome (not a 302).authentik_proxy_cookie → still handled by the gate./auth/login200 andstreamcalls).🤖 Generated with Claude Code
Koda (Garmin Connect IQ) calls POST /auth/login during sync. Only /rest and /share bypassed the navidrome-gate, so the gate answered 302 with an empty body and the watch failed with -400 INVALID_HTTP_BODY_IN_NETWORK_RESPONSE ("Native login failed -400"). An IngressRoute sends /auth and /api straight to Navidrome when the request carries no authentik_proxy_ session cookie; requests with the cookie (the web UI) keep going through the gate, so SSO is unchanged. X-Authentik-Username is stripped on this route as on /rest. Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>