PHPackages                             k2gl/sd-jwt - PHPackages - PHPackages  [Skip to content](#main-content)[PHPackages](/)[Directory](/)[Categories](/categories)[Trending](/trending)[Leaderboard](/leaderboard)[Changelog](/changelog)[Analyze](/analyze)[Collections](/collections)[Log in](/login)[Sign up](/register)

1. [Directory](/)
2. /
3. [Authentication &amp; Authorization](/categories/authentication)
4. /
5. k2gl/sd-jwt

ActiveLibrary[Authentication &amp; Authorization](/categories/authentication)

k2gl/sd-jwt
===========

Selective Disclosure for JWTs (SD-JWT, RFC 9901) in pure PHP: issue, present, verify

1.0.1(4d ago)090↑300%1MITPHP &gt;=8.1

Since Jul 6Compare

[ Source](https://github.com/k2gl/sd-jwt)[ Packagist](https://packagist.org/packages/k2gl/sd-jwt)[ Docs](https://github.com/k2gl/sd-jwt)[ RSS](/packages/k2gl-sd-jwt/feed)WikiDiscussions Synced today

READMEChangelog (2)Dependencies (7)Versions (3)Used By (1)

k2gl/sd-jwt
===========

[](#k2glsd-jwt)

[![CI](https://camo.githubusercontent.com/d80f314afd8b2e778eb9175d41f9754a27b43d71382f4cee73eeeb018d4e1411/68747470733a2f2f696d672e736869656c64732e696f2f6769746875622f616374696f6e732f776f726b666c6f772f7374617475732f6b32676c2f73642d6a77742f63692e796d6c3f6272616e63683d6d61696e266c6162656c3d4349266c6f676f3d676974687562)](https://github.com/k2gl/sd-jwt/actions)[![Latest Stable Version](https://camo.githubusercontent.com/03b9a39784a7cc5d7a9d14c55a54a745916f69af881fd371105cf3295dc9e46e/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f762f6b32676c2f73642d6a7774)](https://packagist.org/packages/k2gl/sd-jwt)[![Total Downloads](https://camo.githubusercontent.com/e115473c1dc79d9fd7fa05a8b2e5f36440e572eea0bb84ab8978ce4ad72878b5/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f64742f6b32676c2f73642d6a7774)](https://packagist.org/packages/k2gl/sd-jwt)[![PHPStan](https://camo.githubusercontent.com/4f0c17b6f2d649d1bd4381b961b7402dc6acf3544cc143003c551ee06e37c36a/68747470733a2f2f696d672e736869656c64732e696f2f62616467652f5048505374616e2d6c6576656c253230392d326135656137)](https://phpstan.org/)[![License](https://camo.githubusercontent.com/57ddcb2755157fbeb72a1984895bb20d9f7023011d40fcd055a62a76dfbaebd6/68747470733a2f2f696d672e736869656c64732e696f2f7061636b61676973742f6c2f6b32676c2f73642d6a7774)](https://packagist.org/packages/k2gl/sd-jwt)

Selective Disclosure for JWTs ([RFC 9901](https://www.rfc-editor.org/rfc/rfc9901)) in pure PHP: issue SD-JWTs with selectively disclosable claims, build Holder presentations with optional Key Binding, and verify both. This is the credential format at the core of OpenID4VC/HAIP and the EU Digital Identity Wallet.

The test suite is driven by the worked examples embedded in RFC 9901 itself (Sections 4.2 and 5, Appendix A.5 key), verified byte for byte.

Fail-closed by design: every MUST-level rule of the RFC's verification algorithm — duplicate digests, unreferenced Disclosures, reserved claim names, claim collisions, `alg: none`, Key Binding mismatches — throws; nothing is silently accepted.

Install
-------

[](#install)

```
composer require k2gl/sd-jwt
```

Requires PHP 8.1+. Crypto is provided by [k2gl/dsse](https://github.com/k2gl/dsse) signers and verifiers over `ext-openssl` (ES256/ES384/ES512, RS256) and `ext-sodium` (EdDSA).

Usage
-----

[](#usage)

### Issue an SD-JWT

[](#issue-an-sd-jwt)

Wrap everything selectively disclosable in `Sd::hide()` — object properties, array elements, or whole subtrees (nesting produces recursive Disclosures per Section 4.2.6). `Sd::decoy()` inserts decoy digests.

```
use K2gl\SdJwt\Jws\JwsSigner;
use K2gl\SdJwt\Sd;
use K2gl\SdJwt\SdJwtIssuer;

$issuer = new SdJwtIssuer(JwsSigner::es256FromPem($issuerPrivatePem));

$sdJwt = $issuer->issue(
    claims: [
        'iss' => 'https://issuer.example.com',
        'iat' => time(),
        'sub' => 'user_42',
        'cnf' => ['jwk' => $holderPublicJwk],       // enables Key Binding
        'given_name' => Sd::hide('John'),
        'address' => Sd::hide([
            'street_address' => Sd::hide('123 Main St'), // recursive
            'country' => 'US',
        ]),
        'nationalities' => [Sd::hide('US'), Sd::hide('DE'), Sd::decoy()],
    ],
    header: ['typ' => 'example+sd-jwt'],
);

$compact = $sdJwt->toCompact(); // ~~...~
```

### Present selected claims (Holder)

[](#present-selected-claims-holder)

Selection uses JSON Pointers into the fully disclosed payload; parent Disclosures that a nested claim depends on are included automatically.

```
use K2gl\SdJwt\Presentation;

$presentation = Presentation::of($compact);
$presentation->disclosablePaths();  // ['/given_name', '/address', '/address/street_address', ...]

// Without Key Binding:
$toVerifier = $presentation->disclose('/given_name', '/nationalities/0')->toCompact();

// With Key Binding (RFC 9901 Section 4.3):
$toVerifier = $presentation
    ->disclose('/given_name')
    ->withKeyBinding(
        JwsSigner::es256FromPem($holderPrivatePem),
        audience: 'https://verifier.example.org',
        nonce: $nonceFromVerifier,
    );
```

### Verify a presentation (Verifier)

[](#verify-a-presentation-verifier)

```
use K2gl\Dsse\PublicKey;
use K2gl\SdJwt\Exception\SdJwtException;
use K2gl\SdJwt\KeyBinding;
use K2gl\SdJwt\SdJwtVerifier;

$verifier = new SdJwtVerifier;

try {
    $verified = $verifier->verifyPresentation(
        $toVerifier,
        PublicKey::fromJwk($issuerPublicJwk),   // or PublicKey::fromPem(...)
        KeyBinding::required(
            audience: 'https://verifier.example.org',
            nonce: $nonceFromVerifier,
        ),
    );

    $claims = $verified->claims();  // the Processed SD-JWT Payload, digests resolved
} catch (SdJwtException $e) {
    // not verified — bad signature, tampered Disclosure, replayed or missing Key Binding, ...
}
```

Use `KeyBinding::notRequired()` for presentations without Holder binding, and `$verifier->verify($sdJwt, $issuerKey)` on the Holder side after receiving an SD-JWT from an Issuer.

Scope
-----

[](#scope)

- Compact serialization (`~~...~[]`) — issue, present, verify.
- Object-property, array-element, and recursive Disclosures; decoy digests.
- Key Binding JWTs: creation and full Section 7.3 validation (`typ`, algorithm-to-`cnf`match, `iat` window, `aud`, `nonce`, `sd_hash`).
- `_sd_alg`: sha-256 (default), sha-384, sha-512.
- JOSE algorithms: ES256, ES384, ES512, EdDSA, RS256 (signing); verification takes any k2gl/dsse `Verifier`.

The JWS JSON serialization (RFC 9901 Section 8) is not implemented yet.

Design
------

[](#design)

- The encoded Disclosure is authoritative: digests are computed over the base64url string exactly as received, never over re-serialized JSON, so foreign encodings survive verification untouched.
- Processing keeps the JSON object/array distinction (`stdClass` vs list) end to end; `->payload()` returns the lossless view, `->claims()` the convenient array one.
- The verifier is policy-explicit: allowed JOSE algorithms, allowed hash algorithms, clock, and leeway are constructor parameters with safe defaults; Key Binding is decided by the Verifier's policy, never by what the Holder sent.

License
-------

[](#license)

MIT © [Nick Harin](https://github.com/k2gl)

###  Health Score

42

—

FairBetter than 88% of packages

Maintenance99

Actively maintained with recent releases

Popularity12

Limited adoption so far

Community5

Small or concentrated contributor base

Maturity43

Maturing project, gaining track record

How is this calculated?**Maintenance (25%)** — Last commit recency, latest release date, and issue-to-star ratio. Uses a 2-year decay window.

**Popularity (30%)** — Total and monthly downloads, GitHub stars, and forks. Logarithmic scaling prevents top-heavy scores.

**Community (15%)** — Contributors, dependents, forks, watchers, and maintainers. Measures real ecosystem engagement.

**Maturity (30%)** — Project age, version count, PHP version support, and release stability.

###  Release Activity

Cadence

Every ~0 days

Total

2

Last Release

4d ago

### Community

Maintainers

![](https://www.gravatar.com/avatar/6bc4aa529c7f13ea593297497f6eae20d5c07f476baa0a551960d7e6ff1e5413?d=identicon)[k2gl](/maintainers/k2gl)

---

Tags

jwtJWSdigital identitysd-jwtselective-disclosureeudiverifiable-credentialsrfc9901key-bindingopenid4vc

###  Code Quality

TestsPHPUnit

Static AnalysisPHPStan

Code StyleLaravel Pint

Type Coverage Yes

### Embed Badge

![Health badge](/badges/k2gl-sd-jwt/health.svg)

```
[![Health](https://phpackages.com/badges/k2gl-sd-jwt/health.svg)](https://phpackages.com/packages/k2gl-sd-jwt)
```

###  Alternatives

[lcobucci/jwt

A simple library to work with JSON Web Token and JSON Web Signature

7.6k338.7M1.1k](/packages/lcobucci-jwt)[namshi/jose

JSON Object Signing and Encryption library for PHP.

1.8k103.2M104](/packages/namshi-jose)[web-token/jwt-framework

JSON Object Signing and Encryption library for PHP and Symfony Bundle.

95220.7M106](/packages/web-token-jwt-framework)[web-token/jwt-library

JWT library

2015.8M133](/packages/web-token-jwt-library)[bizley/jwt

JWT integration for Yii 2

69478.8k2](/packages/bizley-jwt)[web-token/jwt-bundle

JWT Bundle of the JWT Framework.

132.7M8](/packages/web-token-jwt-bundle)

PHPackages © 2026

[Directory](/)[Categories](/categories)[Trending](/trending)[Changelog](/changelog)[Analyze](/analyze)
