Auth path is silent on success — can't tell PAT pass-through from JWT fallback in logs #60

Closed
opened 2026-07-25 14:56:54 +00:00 by mathias · 1 comment
Owner

Problem

While verifying #59 (v0.12.0/v0.12.1) end to end, I could not confirm from server logs which auth path a given /mcp request actually took. chassisauth.BearerMiddleware only logs on rejection (mcp auth rejected reason=... status=401); PassthroughMiddleware logs nothing at all, success or failure.

Concretely: after re-authing the claude.ai connector, I wanted to confirm whether the resulting calls were using the new PAT pass-through (caller's own Gitea PAT, own identity) or falling through to the existing JWT/shared-token path. The only way to reason about it was inference (a JWT would fail ValidateToken against Gitea and fall through) — there was no direct signal in the logs either way.

Proposed fix

Add one structured log line per successful /mcp auth, distinguishing the path taken, e.g.:

INFO mcp auth accepted path=passthrough caller=alice token_fp=...
INFO mcp auth accepted path=jwt caller=mathiasbq token_fp=...
INFO mcp auth accepted path=static token_fp=...

token_fp should be a fingerprint/hash, never the raw token (matches the existing rejection log's token_fp convention).

Acceptance criteria

  • A test asserting PassthroughMiddleware logs path=passthrough with the resolved caller on a successful PAT validation.
  • A test (or manual verification) confirming the existing JWT/static success paths in mcp-chassis's BearerMiddleware emit an equivalent path=jwt/path=static accepted line — may require a small mcp-chassis change if that library doesn't expose a success hook today.

Refs

  • gitea-mcp#59 (PAT pass-through), v0.12.0/v0.12.1
  • gitea-mcp#57/#58 (related: existing rejection-path logging had its own correctness bugs)
## Problem While verifying #59 (v0.12.0/v0.12.1) end to end, I could not confirm from server logs which auth path a given `/mcp` request actually took. `chassisauth.BearerMiddleware` only logs on *rejection* (`mcp auth rejected reason=... status=401`); `PassthroughMiddleware` logs nothing at all, success or failure. Concretely: after re-authing the claude.ai connector, I wanted to confirm whether the resulting calls were using the new PAT pass-through (caller's own Gitea PAT, own identity) or falling through to the existing JWT/shared-token path. The only way to reason about it was inference (a JWT would fail `ValidateToken` against Gitea and fall through) — there was no direct signal in the logs either way. ## Proposed fix Add one structured log line per successful `/mcp` auth, distinguishing the path taken, e.g.: ``` INFO mcp auth accepted path=passthrough caller=alice token_fp=... INFO mcp auth accepted path=jwt caller=mathiasbq token_fp=... INFO mcp auth accepted path=static token_fp=... ``` `token_fp` should be a fingerprint/hash, never the raw token (matches the existing rejection log's `token_fp` convention). ## Acceptance criteria - A test asserting `PassthroughMiddleware` logs `path=passthrough` with the resolved caller on a successful PAT validation. - A test (or manual verification) confirming the existing JWT/static success paths in `mcp-chassis`'s `BearerMiddleware` emit an equivalent `path=jwt`/`path=static` accepted line — may require a small `mcp-chassis` change if that library doesn't expose a success hook today. ## Refs - gitea-mcp#59 (PAT pass-through), v0.12.0/v0.12.1 - gitea-mcp#57/#58 (related: existing rejection-path logging had its own correctness bugs)
mathias added the risk/highsize/stype/feat labels 2026-08-27 05:25:40 +00:00
Author
Owner

Done. v0.15.0 here, mcp-chassis v0.7.0/v0.7.1 underneath.

All three paths now announce themselves on success:

INFO mcp auth accepted auth_path=passthrough caller=alice     token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=jwt         caller=mathiasbq token_fp=... remote=... path=/mcp
INFO mcp auth accepted auth_path=static                       token_fp=... remote=... path=/mcp

token_fp is a truncated SHA-256, never the raw token — the convention this issue asked to match.

Both acceptance criteria

A test asserting PassthroughMiddleware logs path=passthrough with the resolved caller on a successful PAT validation.

internal/auth/passthrough_logging_test.go. It also asserts the PAT appears in no field of the line, not just that it is absent from token_fp.

A test (or manual verification) confirming the existing JWT/static success paths in mcp-chassis's BearerMiddleware emit an equivalent line — may require a small mcp-chassis change if that library doesn't expose a success hook today.

It did require one. mcp-chassis v0.7.0 adds accept() alongside the existing deny(), tested there in auth/bearer_logging_test.go. No hook needed in the end — the middleware owns both verdicts, so it logs both.

Two decisions worth recording

The field is auth_path, not path. path was already taken by the request path in every existing line. Reusing it would have made the accepted and rejected lines mean different things by the same key, which is worse than a slightly longer name.

Fingerprint is exported from the chassis rather than reimplemented here. Pass-through runs ahead of BearerMiddleware, so its line comes from a different package. Two different fingerprints for the same token cannot be correlated — which is the only reason the field exists. mcp-chassis v0.7.1 exports it.

Pass-through still logs nothing on failure, deliberately. A token that is not a valid PAT falls through to the chassis, which owns that verdict. Logging here too would double-count every static-token request, since all of them reach this middleware first. Asserted with its own test, because "logs nothing on the failure path" is otherwise indistinguishable from "was never called".

Why this one was worth more than it looks

The other two paths tell you how a request authenticated. Pass-through tells you who — it is the path where a request acts as a specific Gitea user against a real forge. An audit trail that cannot say which identity performed a write is not an audit trail, and until now that line did not exist at all.

Closing.


Created via git-mcp on behalf of @mathias

## Done. `v0.15.0` here, `mcp-chassis v0.7.0`/`v0.7.1` underneath. All three paths now announce themselves on success: ``` INFO mcp auth accepted auth_path=passthrough caller=alice token_fp=... remote=... path=/mcp INFO mcp auth accepted auth_path=jwt caller=mathiasbq token_fp=... remote=... path=/mcp INFO mcp auth accepted auth_path=static token_fp=... remote=... path=/mcp ``` `token_fp` is a truncated SHA-256, never the raw token — the convention this issue asked to match. ### Both acceptance criteria > A test asserting `PassthroughMiddleware` logs `path=passthrough` with the resolved caller on a successful PAT validation. `internal/auth/passthrough_logging_test.go`. It also asserts the PAT appears in **no** field of the line, not just that it is absent from `token_fp`. > A test (or manual verification) confirming the existing JWT/static success paths in `mcp-chassis`'s `BearerMiddleware` emit an equivalent line — **may require a small `mcp-chassis` change if that library doesn't expose a success hook today**. It did require one. `mcp-chassis v0.7.0` adds `accept()` alongside the existing `deny()`, tested there in `auth/bearer_logging_test.go`. No hook needed in the end — the middleware owns both verdicts, so it logs both. ### Two decisions worth recording **The field is `auth_path`, not `path`.** `path` was already taken by the request path in every existing line. Reusing it would have made the accepted and rejected lines mean different things by the same key, which is worse than a slightly longer name. **`Fingerprint` is exported from the chassis rather than reimplemented here.** Pass-through runs ahead of `BearerMiddleware`, so its line comes from a different package. Two different fingerprints for the same token cannot be correlated — which is the only reason the field exists. `mcp-chassis v0.7.1` exports it. **Pass-through still logs nothing on failure, deliberately.** A token that is not a valid PAT falls through to the chassis, which owns that verdict. Logging here too would double-count every static-token request, since all of them reach this middleware first. Asserted with its own test, because "logs nothing on the failure path" is otherwise indistinguishable from "was never called". ### Why this one was worth more than it looks The other two paths tell you *how* a request authenticated. Pass-through tells you *who* — it is the path where a request acts as a specific Gitea user against a real forge. An audit trail that cannot say which identity performed a write is not an audit trail, and until now that line did not exist at all. Closing. --- _Created via git-mcp on behalf of @mathias_
Sign in to join this conversation.
1 Participants
Notifications
Due Date
No due date set.
Dependencies

No dependencies set.

Reference: mathias/gitea-mcp#60