# What is a JSON Web Token (JWT)?

> A JWT is a compact, signed set of claims: who issued it, who it is about and when it expires. How to read one, what to check, and why it isn't encrypted.

URL: https://crunu.com/learn/jwt
Language: en
Updated: 2026-10-30

A JSON Web Token (JWT) is a short string made of a header, a set of claims and a signature, which lets one system make statements, such as "this token was issued by GitHub for a job in repository `kiteline/web-app`, and it expires at 10:05", that another system can check without calling the first one.

## The structure

RFC 7519 defines a JWT as "a compact, URL-safe means of representing claims to be transferred between two parties" ([RFC 7519](https://www.rfc-editor.org/rfc/rfc7519)). A signed JWT has three parts separated by dots: `header.payload.signature`, each base64url-encoded. Decoded, the payload of a CI token might look like this:

```json
{
  "iss": "https://token.actions.githubusercontent.com",
  "sub": "repo:kiteline/web-app:ref:refs/heads/main",
  "aud": "https://app.crunu.com",
  "exp": 1793352300,
  "iat": 1793352000,
  "jti": "example-unique-id",
  "repository": "kiteline/web-app",
  "ref": "refs/heads/main"
}
```

The RFC registers seven claim names: `iss` "identifies the principal that issued the JWT", `sub` the principal that is its subject, `aud` "the recipients that the JWT is intended for", `exp` the time after which it "MUST NOT be accepted", `nbf` the time before which it must not be accepted, `iat` when it was issued, and `jti` a unique identifier. Providers add their own claims, such as `repository` above. Kiteline is a fictional company.

## Signed is not secret

Most JWTs are signed, not encrypted. Anyone holding one can decode the payload and read every claim; the signature only proves who made it and that it hasn't changed. So:

- don't put secrets in claims;
- treat the whole token as a credential while it is valid, because whoever holds it can present it.

The RFC also allows encrypted JWTs and an "Unsecured JWT" with no protection at all; the second must never be accepted where identity matters.

## What a receiver must check

1. **The signature**, against the issuer's published keys, with the algorithm you expect rather than the one the token claims.
2. **`iss`**, exactly.
3. **`aud`**, so a token minted for one service can't be replayed against another.
4. **`exp` and `nbf`**, with a small allowance for clock skew.
5. **The claims that matter to you**, such as the repository and branch. For CI, that is mostly the [subject claim](https://crunu.com/learn/oidc-subject-claim).
6. **Single use**, where replay matters: remember `jti` values until they expire.

## Common mistakes

- **Checking the signature but not the audience.** A valid token for a different receiver passes.
- **Logging the token.** A JWT printed in a build log works for anyone until it expires.
- **Long expiry.** CI tokens should last minutes, not days.

## How Crunu uses JWTs

Crunu accepts the JWTs that GitHub Actions, GitLab CI, Bitbucket Pipelines and other OIDC issuers give to CI jobs, as part of a **CI binding (OIDC)** on a service token. The server checks the signature against the provider's keys, the issuer, audience and expiry, and matches the claims against your rules. Each JWT can be used once: a replay is refused and raises an alert. Bitbucket's tokens carry no `jti`, so the server recognizes a replay by a fingerprint of the token's signed part. Use your server address, such as `https://app.crunu.com`, as the audience.

The claims each CI provider includes, and how to pin them, are in [CI/CD secrets with OIDC](https://crunu.com/guides/ci-cd-secrets-with-oidc).

## Sources

- [RFC 7519: JSON Web Token (JWT)](https://www.rfc-editor.org/rfc/rfc7519), checked October 30, 2026.
