v1.public tokens include base64-encoded 'null' footer when no footer is provided
Hi! I maintain rusty_paseto, a Rust implementation of PASETO, and encountered an interoperability issue that appears to stem from footer handling in this library.
Issue Description
When generating v1.public tokens without a footer, this library produces 4-part tokens ending with .bnVsbA (base64url-encoded "null"):
v1.public.{payload}.{signature}.bnVsbA
According to the official PASETO v1 specification, tokens should be formatted as:
-
Empty footer:
v1.public.{base64url(message||signature)}(3 parts) -
Non-empty footer:
v1.public.{base64url(message||signature)}.{base64url(footer)}(4 parts)
The spec explicitly states:
If
fis: Empty: return "h|| base64url(m||sig)"\n> Iffis: Non-empty: return "h|| base64url(m||sig) ||." || base64url(f)"
Reproduction
v1 := paseto.NewV1()
token, err := v1.Sign(privateKey, payloadJSON, nil) // nil footer
// Results in: v1.public.{payload}.{signature}.bnVsbA
Impact
This creates interoperability issues with spec-compliant PASETO implementations that expect 3-part tokens when no footer is provided. The library does correctly verify tokens from other implementations (3-part tokens work fine), so this is specifically a generation issue.
Interoperability Test Results
I verified this bidirectionally between o1egl/paseto and rusty_paseto:
- Go → Rust verification: ✗ Fails (spec-compliant implementations reject the extra footer component)
- Rust → Go verification: ✓ Works (this library correctly handles 3-part tokens)
Suggested Fix
When the footer parameter is nil or empty, the library should generate 3-part tokens without appending the base64-encoded "null" footer component.
Happy to provide additional details or test cases if helpful. Thanks for maintaining this library!