# JWT

> 

A JSON Web Token ([RFC 7519](https://www.rfc-editor.org/rfc/rfc7519)) is a short string that carries a JSON payload across systems — typically for authentication, authorization, or short-lived messages. Every JWT is either:

- a **JSON Web Signature** (JWS) — the payload is readable, but tampering is detectable, or
- a **JSON Web Encryption** (JWE) — the payload is unreadable without the right key.

Both share the same outer shape: three to five base64url-encoded segments joined by dots. What differs is what happens between `sign()` / `verify()` (JWS) and `encrypt()` / `decrypt()` (JWE).

## Anatomy

A typical **compact JWS** has three parts:

```text
eyJhbGci...   .eyJzdWIi...   .MEUCIQDwW0...
<header>      .<payload>      .<signature>
```

- **Header** — algorithm (`alg`), type (`typ`), key id (`kid`), etc.
- **Payload** — the JSON claims, base64url-encoded.
- **Signature** — a MAC or digital signature over `header.payload`.

A **compact JWE** has five parts:

```text
eyJhbGci... .e1h3WklRxw...  .48V1_ALb6... .5eym8TW_c... .XFBoMYUZo...
<header>    .<encryptedKey> .<iv>         .<ciphertext> .<tag>
```

The payload is gone — replaced by ciphertext that only the recipient's key can unlock, plus an IV and an authentication tag. The `encryptedKey` segment is the Content Encryption Key, itself wrapped by the recipient's key.

<note>

The compact form is the most common, but both specs define richer **JSON Serializations** too — General (multi-signer / multi-recipient) and Flattened (single-signer / single-recipient, but in JSON shape). unjwt supports these through [`signMulti`/`verifyMulti`](/jwt/jws/multi-signature) and [`encryptMulti`/`decryptMulti`](/jwt/jwe/multi-recipient).

</note>

## When to sign vs. encrypt

<table>
<thead>
  <tr>
    <th>
      If the data is…
    </th>
    
    <th>
      Use…
    </th>
  </tr>
</thead>

<tbody>
  <tr>
    <td>
      Non-sensitive identity data (user id, role)
    </td>
    
    <td>
      <a href="/jwt/jws">
        JWS
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Inspectable by the client (preferences, flags)
    </td>
    
    <td>
      <a href="/jwt/jws">
        JWS
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Readable by third parties (federated identity)
    </td>
    
    <td>
      <a href="/jwt/jws">
        JWS
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Sensitive (tokens, PII, secrets, refresh state)
    </td>
    
    <td>
      <a href="/jwt/jwe">
        JWE
      </a>
    </td>
  </tr>
  
  <tr>
    <td>
      Both confidential <strong>
        and
      </strong>
      
       provably from you
    </td>
    
    <td>
      Nested JWT (JWS-in-JWE)
    </td>
  </tr>
</tbody>
</table>

JWS is the default. Encrypt only when the content itself needs to be hidden.

<tip>

A **nested JWT** — signing a payload, then encrypting the resulting JWS — gives you both authenticity and confidentiality. Do it by encrypting a signed token: pass the JWS string as the payload to `encrypt()` with `typ: "JWT"`/`cty: "JWT"` headers. unjwt doesn't add syntax sugar for this because it's already one line.

</tip>

## What unjwt gives you

For JWS (signed tokens):

- [`sign()`](/jwt/jws/signing) / [`verify()`](/jwt/jws/verifying) — compact serialization, the common case.
- [`signMulti()`](/jwt/jws/multi-signature) / [`verifyMulti()`](/jwt/jws/multi-signature) / [`verifyMultiAll()`](/jwt/jws/multi-signature) — General JSON Serialization with multiple signers.

For JWE (encrypted tokens):

- [`encrypt()`](/jwt/jwe/encrypting) / [`decrypt()`](/jwt/jwe/decrypting) — compact serialization.
- [`encryptMulti()`](/jwt/jwe/multi-recipient) / [`decryptMulti()`](/jwt/jwe/multi-recipient) — General JSON Serialization with multiple recipients.
- [ECDH-ES](/jwt/jwe/ecdh-es) and [password-based](/jwt/jwe/algorithms#password-based-pbes2) flows as first-class paths.

Both pages below give you the overview and basic usage; sub-pages go into each function in detail.

## Common options, both sides

A few options show up identically on both signing and encryption:

- **expiresIn** — adds an `exp` claim relative to `iat`. Accepts `30 /* seconds */`, `"30s"`, `"10m"`, `"2h"`, `"7D"`, `"1W"`, `"3M"`, `"1Y"` and the long-form `"7days"` / `"3months"` / `"1year"` (no space between number and unit).
- **currentDate** — override "now" for `iat`/`exp` calculation (useful in tests).
- **protectedHeader** — extra header parameters (`kid`, `typ`, `cty`, custom fields). `alg`/`enc` are managed by the library and can't be overridden here.

Claim validation is shared too — see [`JWTClaimValidationOptions`](/utilities#validatejwtclaims).

## Next

- [JWS →](/jwt/jws) — signing and verifying.
- [JWE →](/jwt/jwe) — encrypting and decrypting.
