Skip to content

Conversation

dsykes16
Copy link
Contributor

@dsykes16 dsykes16 commented Oct 9, 2025

In service of #439

Adds support for custom headers via decode_with_custom_header to decode and validate JWT, and a standalone header-only decode via decode_custom_header. Those should probably have better names before this is merged. The existing decode and decode_header functions retain full backwards compatibility.

Also adds convenience macros for claims and custom headers, jsonwebtoken::macros::claims and jsonwebtoken::macros::header, and a Header derive macro.

Adds a BasicHeader to fulfill #439 (i.e. implement Hash). I have mixed feelings on that one. I think ideally extras wouldn't exist on the original Header struct, which was itself a breaking change (#420) due to removing the Hash implementation, but removing it now would be a breaking change. Given those constraints, adding a BasicHeader is the only path I could think of to resolve #439 and also to avoid the minor performance hit of using a HashMap (along w/ the inherent risk of a malicious user intentionally sending JWTs with abnormally large headers containing a large number fields).

@mathstuf
Copy link

mathstuf commented Oct 9, 2025

I believe that #445 is far more elegant for this. What do you think?

@dsykes16
Copy link
Contributor Author

dsykes16 commented Oct 9, 2025

@mathstuf, it looks like #445 only adds a standalone function to decode a custom header, it doesn't provide any integration with the existing code or any tests.

It's worth noting that extras exists on Header today (https://github.com/Keats/jsonwebtoken/blob/master/src/header.rs#L186-L190), which supports any arbitrary extra key/value pairs in the header, so the use-case in #435 is already supported today. The motivation for arbitrary header support throughout the main validation/decoding flow as introduced here is to support a custom header struct that implements Hash to fulfill #439, to avoid stringly-typed data structures, and to permit users to avoid the overhead of allocating a HashMap if they're not using extras.

@mathstuf
Copy link

mathstuf commented Oct 9, 2025

Hmm, ok. I'll take a look at extras. It's looking promising. Thanks.

Add true support for custom headers with full backwards compatibility.
Add `BasicHeader` as a backwards-compatible fix for the regression
introduced in Keats#420 which dropped the `Hash` derive trait from `Header`.

Resolves Keats#439
Add `Header` derive macro, `header` attribute macro, and `claims`
attribute macro.

`Header` derive macro implements required traits for custom headers
`header` and `claims` are convenience attribute macros that add
the required derive macros.
@dsykes16 dsykes16 force-pushed the custom-header-support branch from b012686 to a606eac Compare October 10, 2025 04:06
@dsykes16 dsykes16 marked this pull request as ready for review October 10, 2025 04:09
@dsykes16
Copy link
Contributor Author

I believe that #445 is far more elegant for this. What do you think?

I think a single #[header] attribute macro on a struct is about as elegant as it can get.

https://github.com/Keats/jsonwebtoken/blob/a606eac7fffe3f78c1e91c438d58ae9a3fa4ac4d/examples/custom_header.rs

@Keats
Copy link
Owner

Keats commented Oct 10, 2025

No I definitely don't want to add a proc macro

@dsykes16
Copy link
Contributor Author

dsykes16 commented Oct 10, 2025

Any specific concerns around macros? What if they were all behind a feature gate?

Usage is entirely optional, especially for claims structs. This doesn't change any trait bounds on claims structs, it just adds a convenience macro for:
#[derive(Debug, Clone, Default, serde::Serialize, serde::Deserialize)]

For custom headers, the derive macro isn't strictly necessary, but it's a lot more ergonomic to do #[derive(Debug, Clone, Header, Serialize, Deserialize)] (or just #[header]) than to manually implement the traits.

If a user sticks a #[derive(Header)] on a malformed struct (i.e. no alg field), it does generate a useful error message:

Likewise for incorrect type of alg:

This error could admittedly be better since it is a bit misleading with the reference rather than presenting a concrete Algorithm. If it would lead to acceptance of this PR, I'd be happy to add a bit more parsing in the macro to explicitly check the presence and type of the alg field and produce a custom compile error that's more useful.

This also a step towards someday dropping the double-deserialization of Claims, which currently hides the requirement to be deserializable into a jsonwebtoken::validation::ClaimsForValidation, although doing so would be either a breaking change or require the addition of more semi-redundant decode_... functions, thus its omission in this PR.

@Keats
Copy link
Owner

Keats commented Oct 10, 2025

I think the proc macro is overkill for 99% of the users and would rather have people using the type defined in the crate

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Header doesn't implement Hash anymore

3 participants