auth.d-ma.be now runs Dex (OIDC/OAuth 2.0 AS). All MCP servers should accept JWTs issued by Dex or a static bearer token (backward compat). Two MCP servers in this repo need updating:
supervisor — internal/mcp/server.go, checkAuth function
// Validator wraps a JWKS cache and validates bearer tokens.typeValidatorstruct{...}funcNewValidator(issuerURLstring)(*Validator,error)func(v*Validator)Validate(ctxcontext.Context,rawTokenstring)(validbool,subjectstring,errerror)
Dependency: github.com/lestrrat-go/jwx/v2
New env vars (both binaries):
DEX_ISSUER_URL — e.g. https://auth.d-ma.be
JWKS discovery: {DEX_ISSUER_URL}/.well-known/openid-configuration → jwks_uri.
Use jwk.NewCache with 1 h refresh interval.
Validation requirements:
iss == DEX_ISSUER_URL
aud contains the server's client ID (configurable, e.g. supervisor / brain)
exp not expired
Signature valid against JWKS
If DEX_ISSUER_URL is not set, skip JWT validation entirely and fall back to static token only (safe default during rollout).
Note: SUPERVISOR_MCP_TOKEN enforcement is currently off in k3s (env var not set). JWT auth can be wired up independently; static token enforcement can be turned on together.
Testing
Unit test: mock JWKS endpoint, assert valid JWT passes and tampered/expired JWT returns 401
Unit test: static token passes, wrong token returns 401
Unit test: DEX_ISSUER_URL unset → only static token auth applies
Integration: GET /.well-known/oauth-protected-resource on each server returns correct JSON
Related
Dex deployed at auth.d-ma.be via k3s/apps/auth/ in mathias/infra
Same pattern applied to mathias/gitea-mcp (separate issue there)
## Context
`auth.d-ma.be` now runs Dex (OIDC/OAuth 2.0 AS). All MCP servers should accept JWTs issued by Dex **or** a static bearer token (backward compat). Two MCP servers in this repo need updating:
1. **supervisor** — `internal/mcp/server.go`, `checkAuth` function
2. **brain** — `ingestion/internal/mcp/auth.go`, `BearerAuth` middleware
Both currently do plain static string compares. The new chain adds JWT validation before the static fallback.
---
## Changes required
### 1. Supervisor — `internal/mcp/server.go`
Current `checkAuth`:
```go
func checkAuth(token, expected string) bool {
return subtle.ConstantTimeCompare([]byte(token), []byte(expected)) == 1
}
```
New behaviour:
```
if token is valid JWT signed by Dex → allow
else if subtle.ConstantTimeCompare(token, SUPERVISOR_MCP_TOKEN) == 1 → allow
else → 401
```
Keep the existing `SUPERVISOR_MCP_TOKEN` env var path intact — it's the static fallback.
### 2. Brain — `ingestion/internal/mcp/auth.go`
Current `BearerAuth` middleware does a plain string compare. Apply the same JWT-or-static chain:
```
if token is valid JWT signed by Dex → allow
else if token == BRAIN_MCP_TOKEN (constant-time) → allow
else → 401
```
### 3. Shared JWT validation package
Create `internal/auth/jwt.go` (or equivalent shared location) implementing:
```go
// Validator wraps a JWKS cache and validates bearer tokens.
type Validator struct { ... }
func NewValidator(issuerURL string) (*Validator, error)
func (v *Validator) Validate(ctx context.Context, rawToken string) (valid bool, subject string, err error)
```
Dependency: `github.com/lestrrat-go/jwx/v2`
New env vars (both binaries):
- `DEX_ISSUER_URL` — e.g. `https://auth.d-ma.be`
JWKS discovery: `{DEX_ISSUER_URL}/.well-known/openid-configuration` → `jwks_uri`.
Use `jwk.NewCache` with 1 h refresh interval.
Validation requirements:
- `iss` == `DEX_ISSUER_URL`
- `aud` contains the server's client ID (configurable, e.g. `supervisor` / `brain`)
- `exp` not expired
- Signature valid against JWKS
If `DEX_ISSUER_URL` is not set, skip JWT validation entirely and fall back to static token only (safe default during rollout).
### 4. `/.well-known/oauth-protected-resource` endpoints (RFC 9728)
Register on each server's HTTP mux (outside auth-protected routes):
**Supervisor:**
```
GET /.well-known/oauth-protected-resource
{
"resource": "https://supervisor-mcp.d-ma.be",
"authorization_servers": ["https://auth.d-ma.be"]
}
```
**Brain:**
```
GET /.well-known/oauth-protected-resource
{
"resource": "https://brain-mcp.d-ma.be",
"authorization_servers": ["https://auth.d-ma.be"]
}
```
Values from env vars (`MCP_RESOURCE_URL`, `DEX_ISSUER_URL`).
---
## k3s manifest updates (in `mathias/infra`)
After code merged and images rebuilt:
- `k3s/apps/supervisor/deployment.yaml`: add `DEX_ISSUER_URL: https://auth.d-ma.be`
- `k3s/apps/infra-mcp/deployment.yaml` (brain): add `DEX_ISSUER_URL: https://auth.d-ma.be`
Note: `SUPERVISOR_MCP_TOKEN` enforcement is currently **off** in k3s (env var not set). JWT auth can be wired up independently; static token enforcement can be turned on together.
---
## Testing
- Unit test: mock JWKS endpoint, assert valid JWT passes and tampered/expired JWT returns 401
- Unit test: static token passes, wrong token returns 401
- Unit test: `DEX_ISSUER_URL` unset → only static token auth applies
- Integration: `GET /.well-known/oauth-protected-resource` on each server returns correct JSON
---
## Related
- Dex deployed at `auth.d-ma.be` via `k3s/apps/auth/` in `mathias/infra`
- Same pattern applied to `mathias/gitea-mcp` (separate issue there)
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.
Context
auth.d-ma.benow runs Dex (OIDC/OAuth 2.0 AS). All MCP servers should accept JWTs issued by Dex or a static bearer token (backward compat). Two MCP servers in this repo need updating:internal/mcp/server.go,checkAuthfunctioningestion/internal/mcp/auth.go,BearerAuthmiddlewareBoth currently do plain static string compares. The new chain adds JWT validation before the static fallback.
Changes required
1. Supervisor —
internal/mcp/server.goCurrent
checkAuth:New behaviour:
Keep the existing
SUPERVISOR_MCP_TOKENenv var path intact — it's the static fallback.2. Brain —
ingestion/internal/mcp/auth.goCurrent
BearerAuthmiddleware does a plain string compare. Apply the same JWT-or-static chain:3. Shared JWT validation package
Create
internal/auth/jwt.go(or equivalent shared location) implementing:Dependency:
github.com/lestrrat-go/jwx/v2New env vars (both binaries):
DEX_ISSUER_URL— e.g.https://auth.d-ma.beJWKS discovery:
{DEX_ISSUER_URL}/.well-known/openid-configuration→jwks_uri.Use
jwk.NewCachewith 1 h refresh interval.Validation requirements:
iss==DEX_ISSUER_URLaudcontains the server's client ID (configurable, e.g.supervisor/brain)expnot expiredIf
DEX_ISSUER_URLis not set, skip JWT validation entirely and fall back to static token only (safe default during rollout).4.
/.well-known/oauth-protected-resourceendpoints (RFC 9728)Register on each server's HTTP mux (outside auth-protected routes):
Supervisor:
Brain:
Values from env vars (
MCP_RESOURCE_URL,DEX_ISSUER_URL).k3s manifest updates (in
mathias/infra)After code merged and images rebuilt:
k3s/apps/supervisor/deployment.yaml: addDEX_ISSUER_URL: https://auth.d-ma.bek3s/apps/infra-mcp/deployment.yaml(brain): addDEX_ISSUER_URL: https://auth.d-ma.beNote:
SUPERVISOR_MCP_TOKENenforcement is currently off in k3s (env var not set). JWT auth can be wired up independently; static token enforcement can be turned on together.Testing
DEX_ISSUER_URLunset → only static token auth appliesGET /.well-known/oauth-protected-resourceon each server returns correct JSONRelated
auth.d-ma.beviak3s/apps/auth/inmathias/inframathias/gitea-mcp(separate issue there)