feat(auth): JWT verifiers, claim/header enums, and docs split#20
Conversation
Add a generic JWS verifier layer mirroring the existing Issuers tree: Verifier (base) owns compact-JWS decode, the alg-confusion guard, and standard exp/nbf/iat/iss/aud claim validation with clock-skew leeway; Verifiers\Asymmetric (RS256) and Verifiers\Symmetric (HS256) delegate only the signature check. Stays dependency-free (native openssl/hash). Introduce Enums\Claim (RFC 7519 + OAuth2/OIDC claim names) and Enums\Header (RFC 7515 JOSE params) and thread them through both the verifier and the issuers, replacing scattered string literals. Enums and string-keyed array spread require PHP 8.1, so bump the constraint to >=8.1. Split the oversized README into a lean overview plus a docs/ folder (hashing, proofs, store, jwt) and replace the dead Travis badge with the monorepo GitHub Actions badge.
…ge at the auth repo
…ash via initializer
Greptile SummaryThis PR adds a JWT verifier layer (
Confidence Score: 5/5The new verifier layer is well-structured and the security-critical paths (alg guard, signature verification, claim enforcement) are all correct and covered by tests. The algorithm-confusion guard, constant-time HMAC comparison, exp/nbf/iat leeway logic, and the immutable constructor design are all implemented correctly. The two findings are minor documentation and consistency nits that do not affect runtime behavior. packages/auth/src/Auth/Verifiers/Asymmetric.php is missing declare(strict_types=1); packages/auth/src/Auth/Verifier.php has a misleading verify() docblock. Important Files Changed
Reviews (4): Last reviewed commit: "(refactor): make the verifier immutable ..." | Re-trigger Greptile |
Address review findings in the verifier and Password proof: - Require "exp" and always enforce "nbf"/"iat": allowExpired() now relaxes only the expiry check, so a not-yet-valid or future-issued token is still rejected, and a token with no "exp" no longer verifies forever. - Add setType() to pin the "typ" header (e.g. at+jwt), preventing one token kind from being accepted in place of another. - Reject JWS header/claims segments that decode to a JSON array rather than an object. - Password: keep the active hash aligned with the registry so a custom registry's algorithm is actually used and removeHash()'s current-hash guard matches. Adds regression tests for each.
Replace the verifier's fluent setters (setIssuer/setAudience/setType/ allowExpired/setLeeway) with readonly constructor parameters passed via named arguments. Mutable fluent setters on a shared instance are a coroutine footgun — another coroutine can flip the expectations mid-verification — so the configuration is now set once and held read-only. Concrete verifiers take the key plus the optional expectations and forward them to the base constructor; tests and docs use the named-argument form.
What
Adds a generic, dependency-free JWS verifier layer to
utopia-php/auth(the mirror of the existing issuers), hardens it, modernizes the package, and splits the docs.Verifiers (new)
Verifier(base) — splits the compact JWS, base64url/JSON-decodes, enforces thealg-confusion guard, and validates standard claims. ConcreteVerifiers\Asymmetric(RS256, public key) andVerifiers\Symmetric(HS256, shared secret) delegate only the signature check.Verifiers\VerificationExceptionis thrown on any failure;verify()otherwise returns the decoded claims.Verifiers\Asymmetric::getKeyId()derives thekidthe same way the issuer does.issuer,audience,type,allowExpired,leeway) is passed via readonly constructor params with named args — no fluent setters that a shared instance could have flipped mid-verification:expis required and must be in the future;nbf/iatare always enforced when present;allowExpired: truerelaxes only the expiry check (e.g. an OIDCid_token_hint). Thetypheader can be pinned to stop one token kind being accepted in place of another, and header/claims segments that decode to a JSON array (not an object) are rejected.openssl/hash).Enums (new)
Enums\Claim(RFC 7519 registered claims + OAuth2/OIDC) andEnums\Header(RFC 7515 JOSE params), threaded through both the verifier and the issuers in place of scattered string literals.Modernization
readonly+ constructor property promotion for set-once key material across the issuers and verifiers;Proofdefaults its hash via anewinitializer.Passwordnow keeps its active hash aligned with its registry (a custom registry's algorithm is actually used, andremoveHash's current-hash guard matches).phpconstraint to >= 8.1 (enums + string-keyed array spread).Tooling
declare(strict_types=1),finaltest classes, redundant-assertion cleanup, etc.).Docs
docs/folder (hashing,proofs,store,jwt); removes the dead Travis build badge.Testing
bin/monorepo check auth— pint + phpstan + rector all pass.bin/monorepo test auth— 156 tests, 380 assertions passing, including verifier round-trips and negative paths (tampered signature/claims, wrong key, expired/allowExpired, missingexp, futurenbf/iat,iss/aud/typ/algmismatch, non-object segments, malformed).Notes
utopia-php/authpackage only. A companion cloud-side refactor (consuming these verifiers, deduping the access-token issuer, droppingAhc\Jwt) lives in a separate repo and depends on a tagged release of this package, so it ships separately.