caddy icon indicating copy to clipboard operation
caddy copied to clipboard

Add `token_auth` with timing-safe comparison to compliment `basic_auth`

Open coolaj86 opened this issue 2 months ago • 5 comments

Issue Details

Recap of real-life discussion with @mholt, and a follow up to the security vulnerability created by solutions like https://caddy.community/t/how-to-verify-a-header-to-validate-an-api-key/6626, which we discussed at https://caddy.community/t/a-post-in-how-to-verify-a-header-to-validate-an-api-key-requires-staff-attention/33063/2 (private link).

We have basic_auth which performs timing safe comparisons and works for standard HTTP Basic Auth.

We also have arbitrary header matchers, but they are not timing safe.

What I would like is the ability to do a timing-safe comparison that can support other standard and non-standard but common auth token formats such as:

  • Authorization: Token $GITHUB_API_KEY (pre-OIDC standardization)
  • Authorization: Bearer $OLLAMA_API_KEY (post-OIDC standardization, ubiquitous)
  • X-API-Key: $XCODE_LLM_API_KEY (also quite common across many products)

I believe this will best serve users as a built-in token_auth directive as it is very simple and shouldn't require all of a custom xcaddy build.

token_auth /api/expensive/* sha256 {
  X-API-Auth {env.OLLAMA_API_KEY}
}

I could also see having functionality to distinguish between multiple keys to be available via {http.auth.token.id}, for parity with basic_auth.

token_auth /api/expensive/* sha256 {
  X-API-Auth {env.OLLAMA_API_KEY} ollama
  X-API-Auth {env.OPENAI_API_KEY} openai
}

I disagree with the idea of a generic constant time header matcher - to me that sounds like a solution in search of a problem in the same way that it would to have created a generic base64 encoder module rather than basic_auth.

I also disagree with the idea of a more complex token module that would handle arbitrary types of message digests.

This is a simple, ubiquitous problem with a well-understand and industry accepted solution.

On the security front I would suggest using sha256(guess) == sha256(answer) for the constant-time mechanism. If I understand correctly, one of the issues with the SPECTRE family of vulnerabilities is that timing-safe functions are not always timing safe due to optimizations in at the CPU level. I know that's also true in compilers, though the Go authors probably use tricks with bitwise operations that bust compiler optimization.

Assistance Disclosure

Ai NOT used

If AI was used, describe the extent to which it was used.

No response

coolaj86 avatar Nov 12 '25 03:11 coolaj86

Hi AJ, thanks for opening an issue for this!

I disagree with the idea of a generic constant time header matcher - to me that sounds like a solution in search of a problem in the same way that it would to have created a generic base64 encoder module rather than basic_auth.

I am still leaning towards this though; can you explain why it is a "solution in search of a problem"? It sounds like the problem is right here, what you posted.

mholt avatar Nov 12 '25 20:11 mholt

Hey again Matt,

I've provided a specific problem and a specific solution analogous to basic auth.

I'm afraid that the burden would be on you to find a generic set of not-well-specified problems that need a generic, not-well-specified solution.

I'm proposing a directive that is easy to identify that we have clear evidence people have been looking for and is a de facto standard in the industry.

I see muddying that into a generic function for arbitrary logic as just something that will be at best missed by the people who need it - because most people aren't sophisticated and security aware enough to imagine looking for a constant time comparator - as seen in the forum post I referenced.

Thanks for the reply.

coolaj86 avatar Nov 13 '25 15:11 coolaj86

I do appreciate the proposed solution. I still would be interested in exploring multiple approaches so we can converge on the best one.

I'm afraid that the burden would be on you to find a generic set of not-well-specified problems that need a generic, not-well-specified solution.

That's fair; but to clarify, it's not solely my burden, anyone who is interested in this can also participate.

The other option is to implement a new provider for the authentication module: https://caddyserver.com/docs/json/apps/http/servers/errors/routes/handle/authentication/#providers

basic_auth is one such provider, and it has its own Caddyfile directive because it's common enough.

A new authentication provider could also be given a Caddyfile directive, that would do essentially what you want, except perhaps for this part: "... a more complex token module that would handle arbitrary types of message digests." (I dunno how much "more complex" it would be yet.)

mholt avatar Nov 13 '25 18:11 mholt

... disagree with the idea of a more complex token module ...

coolaj86 avatar Nov 19 '25 17:11 coolaj86

Right -- to clarify, I don't think it would add so much more complexity (I mean, it could, I haven't spiked it out yet), so it shouldn't be something you disagree with. i.e. it is a possible way forward.

mholt avatar Nov 20 '25 03:11 mholt