> ## Documentation Index
> Fetch the complete documentation index at: https://docs.air3.com/llms.txt
> Use this file to discover all available pages before exploring further.

# SDK Authentication (Partner JWT)

> Sign a Partner JWT from your backend to authenticate AIR Kit SDK operations — covers supported signing algorithms, required claims, and JWKS hosting setup.

<Warning>
  **BLOCKING — Partner JWKS endpoint required.** `issueCredential`, `verifyCredential`, and on-demand issuance all fail until you:<br />

  * Host a **public HTTPS JWKS URL**
  * Register it in **Dashboard → Account → General → JWKS URL**
  * Sign your Partner JWT with a `kid` that matches a key in that JWKS.
    Localhost is not reachable from AIR servers — use an HTTPS tunnel (ngrok, cloudflared) or deploy. See [JWKS endpoint setup](/airkit/usage/jwks-setup).
</Warning>

You need to generate and use the JWT when:

* Authenticating a User
* Performing credentials-related operations such as issuing or verifying credentials

For **server-side issuance** without user presence, sign a Partner JWT with `scope: "issue"` and `typ: "JWT"`. The JWT no longer carries an `email` claim — the credential recipient is identified when you call `initialize-user` (the email is passed in the request body), not through a JWT claim. See [Issuing Credentials](/airkit/usage/credential/issuing-credentials#server-side-issuance) for concepts and [Issuance API Reference](/airkit/usage/credential/issuance-api) for endpoint usage.

**JWT Details**

* **Signing algorithms supported:** ES256, RS256
* **Expiry**: 5 min (recommended)
* **Claims**: varies depending on the operation. You would always need to include your `partnerId` as one of the claims
* **Header**: You must include a `kid` (Key ID) header to indicate which key was used to sign the JWT. AIR Kit uses `kid` to select the matching key from your JWKS endpoint.
* **JWKS URL**: AIR Kit validates your JWT using JWK standards ([RFC 7517](https://datatracker.ietf.org/doc/html/rfc7517)). You must publish your public key at a JWKS URL and register it in the dashboard. See [JWKS endpoint setup](/airkit/usage/jwks-setup) for the full procedure.

To learn more about JWT, visit [jwt.io](https://www.jwt.io).

## JWKS endpoint (required for all credential SDK operations)

`issueCredential`, `verifyCredential`, and on-demand issuance all rely on the same Partner JWT trust model — AIR Kit fetches your registered JWKS URL and validates every JWT against it. The endpoint must be:

* A **public HTTPS URL** reachable from AIR servers (localhost is not enough).
* Registered in **Dashboard → Account → General Settings → JWKS URL** as the full URL your app actually serves.
* Returning a JSON document whose `keys[].kid` matches the `kid` you set in your JWT header.

The canonical Next.js route is `app/api/.well-known/jwks/route.ts`, used by every issuer and verifier in [`air-examples`](https://github.com/MocaNetwork/air-examples). For implementation, dashboard registration, local HTTPS tunnels, and the `kid` rule, see the [JWKS endpoint setup](/airkit/usage/jwks-setup) page.

## Issuance JWT

For AIR Kit `issueCredential` and
[On-demand issuance](/airkit/usage/credential/issuing-credentials#on-demand-issuance), use a Partner JWT with
these fields. For on-demand issuance, the recipient is resolved through the
`initialize-user` call, so **no `email` claim is required in the JWT**.

| Category  | Required fields               | Notes                                                                        |
| --------- | ----------------------------- | ---------------------------------------------------------------------------- |
| Claims    | `partnerId`, `scope: "issue"` | No `email` claim — the recipient is passed to `initialize-user`, not the JWT |
| Header    | `kid`, `typ: "JWT"`           | `kid` identifies which key in your JWKS is used                              |
| Algorithm | `RS256` or `ES256`            | Keep consistent with your JWKS key type                                      |
| Expiry    | `exp` (recommended 5 minutes) | Short-lived token recommended                                                |

JWKS reminders for issuance:

* `kid` in JWT header must match a key ID exposed by your JWKS endpoint
* JWKS endpoint must be publicly reachable by AIR Kit

### Next.js Partner JWT endpoint

Install `jose`:

```bash theme={null}
pnpm add jose
```

Create a server-only endpoint that returns a five-minute issuance token:

```ts app/api/partner-jwt/route.ts theme={null}
import { NextResponse } from "next/server";
import * as jose from "jose";

function wrapPrivateKeyPem(body: string): string {
  const trimmed = body.trim();
  if (trimmed.includes("BEGIN")) return trimmed;

  return `-----BEGIN PRIVATE KEY-----\n${trimmed}\n-----END PRIVATE KEY-----`;
}

export async function POST() {
  try {
    const privateKeyBody = process.env.PARTNER_PRIVATE_KEY;
    const algorithm = process.env.SIGNING_ALGORITHM;
    const partnerId = process.env.NEXT_PUBLIC_PARTNER_ID;

    if (!privateKeyBody || !algorithm || !partnerId) {
      return NextResponse.json(
        { error: "Missing Partner JWT configuration" },
        { status: 500 },
      );
    }

    const privateKey = await jose.importPKCS8(
      wrapPrivateKeyPem(privateKeyBody),
      algorithm,
    );
    const now = Math.floor(Date.now() / 1000);

    const token = await new jose.SignJWT({
      partnerId,
      scope: "issue",
    })
      .setProtectedHeader({
        alg: algorithm,
        kid: partnerId,
        typ: "JWT",
      })
      .setIssuedAt(now)
      .setExpirationTime(now + 5 * 60)
      .sign(privateKey);

    return NextResponse.json({ token });
  } catch {
    return NextResponse.json(
      { error: "Failed to sign Partner JWT" },
      { status: 500 },
    );
  }
}
```

Keep `PARTNER_PRIVATE_KEY` server-only. The JWT `kid` must match a key in the
JWKS registered for the Partner ID.

## Generating Partner JWTs

### Generating an RS256 Key Pair

To generate a private/public key pair, you may use OpenSSL:

```sh theme={null}
# Generate a 2048-bit RSA private key
openssl genpkey -algorithm RSA -out private.key -pkeyopt rsa_keygen_bits:2048

# Extract the public key in PEM format
openssl rsa -pubout -in private.key -out public.key
```

* `private.key`: Use this file as your signing key in backend code.
* `public.key`: Use this to configure your JWKS endpoint for JWT verification.

> **Tip:** Keep your private key secure and never share it publicly.

### Examples

Below are backend code examples for generating a JWT using ES256 or RS256 algorithms, including the `kid` (Key ID) header.

<Tabs>
  <Tab title="Node.js">
    ```js theme={null}
    const jwt = require("jsonwebtoken");
    const fs = require("fs");

    const privateKey = fs.readFileSync("path/to/private.key");
    const payload = {
      partnerId: "your-partner-id",
      // other claims as needed
      exp: Math.floor(Date.now() / 1000) + 5 * 60 // 5 minutes expiry
    };

    const token = jwt.sign(payload, privateKey, {
      algorithm: "RS256",
      header: {
        kid: "your-key-id"
      }
    });
    console.log(token);
    ```
  </Tab>

  <Tab title="Java">
    ```java theme={null}
    import com.auth0.jwt.JWT;
    import com.auth0.jwt.algorithms.Algorithm;
    import java.util.HashMap;
    import java.util.Map;

    Algorithm algorithm = Algorithm.RSA256(null, privateKey); // Use your private key
    Map<String, Object> headerClaims = new HashMap<>();
    headerClaims.put("kid", "your-key-id");

    String token = JWT.create()
                    .withHeader(headerClaims)
                    .withClaim("partnerId", "your-partner-id")
                    .withExpiresAt(new Date(System.currentTimeMillis() + 5 * 60 * 1000))
                    .sign(algorithm);

    System.out.println(token);
    ```
  </Tab>

  <Tab title="C#">
    ```csharp theme={null}
    using System;
    using System.IdentityModel.Tokens.Jwt;
    using System.Security.Claims;
    using Microsoft.IdentityModel.Tokens;
    using System.Collections.Generic;

    // Load your private key and create signing credentials
    var securityKey = new RsaSecurityKey(yourPrivateRsa);
    var credentials = new SigningCredentials(securityKey, SecurityAlgorithms.RsaSha256);

    var claims = new[] {
            new Claim("partnerId", "your-partner-id"),
            // other claims
    };

    var header = new JwtHeader(credentials);
    header["kid"] = "your-key-id";

    var token = new JwtSecurityToken(
            header,
            new JwtPayload(
                    claims: claims,
                    expires: DateTime.UtcNow.AddMinutes(5),
                    notBefore: null,
                    issuedAt: null,
                    audience: null,
                    issuer: null
            )
    );

    var jwt = new JwtSecurityTokenHandler().WriteToken(token);
    Console.WriteLine(jwt);
    ```
  </Tab>

  <Tab title="Go">
    ```go theme={null}
    import (
            "fmt"
            "time"
            "github.com/golang-jwt/jwt/v5"
    )

    func main() {
            privateKey := []byte("your-private-key") // Use PEM for RS256/ES256
            claims := jwt.MapClaims{
                    "partnerId": "your-partner-id",
                    "exp":       time.Now().Add(5 * time.Minute).Unix(),
            }
            token := jwt.NewWithClaims(jwt.SigningMethodRS256, claims)
            token.Header["kid"] = "your-key-id"
            signedToken, err := token.SignedString(privateKey)
            if err != nil {
                    panic(err)
            }
            fmt.Println(signedToken)
    }
    ```

    > **Note:** Replace `"your-partner-id"`, `"your-key-id"`, and private key paths with your actual values. For ES256, use the appropriate signing method and key type.
  </Tab>
</Tabs>
