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

# Custom JWT Authentication

> Configuring JWT-based authentication with Crossmint

Oftentimes you may already have JWT tokens for your users. Popular authentication libraries such as Firebase, NextAuth, Stytch, or Privy, already use JWTs to represent the log in state of a user.

In such cases, you can directly pass Crossmint that exact same JWT on API calls, and Crossmint will be able to validate them.

However, you may not currently have JWTs, or you may wish to create a custom JWT scoped only for Crossmint, for additional security reassurance. This guide details the steps necessary to integrate your custom JWT auth with Crossmint.

### Issue a Custom JWT for Crossmint

On this integration path, you must perform four high level steps:

* Create a public/private keypair
* Generate JWTs
* Expose a JWKS endpoint
* Pass the user's JWT to Crossmint when using relevant APIs

#### Create the Public/Private keypair

To generate a private key for JWT (JSON Web Token) encryption, you can use several methods depending on your preferred programming language and toolset.

<Tabs>
  <Tab title="cli">
    Here's how you can do it using openssl, a widely used tool for cryptographic operations:

    ```shell theme={null}
    # Generate the RSA private key
    openssl genpkey -algorithm RSA -out private_key.pem -pkeyopt rsa_keygen_bits:2048

    # Convert the private key to a base64 encoded string
    openssl base64 -in private_key.pem -out private_key_base64.txt
    ```

    Store the base64 encoded version of the private key to your environment file and then remove the `private_key_base64.txt` file from your system.

    ```bash .env theme={null}
    JWT_PRIVATE_KEY=encoded_private_key_value
    ```

    ```shell theme={null}
    rm private_key_base64.txt
    ```

    Next, you must extract the public key from the private key:

    ```shell theme={null}
    # Extract the public key from the private key
    openssl rsa -pubout -in private_key.pem -out public_key.pem

    # Convert the public key to a base64 encoded string
    openssl base64 -in public_key.pem -out public_key_base64.txt
    ```

    Store the encoded public key to your environment file also:

    ```bash .env theme={null}
    JWT_PUBLIC_KEY=encoded_public_key_value
    ```

    This approach helps ensure that your private key is stored securely. The corresponding public key can be used to verify the JWTs signed with the private key.
  </Tab>

  <Tab title="nodejs">
    Here's how you can do it using jose, a widely used javascript library for cryptographic operations:

    ```js nodejs theme={null}
    // npm install jose
    import { exportPKCS8, exportSPKI, generateKeyPair } from "jose";

    const alg = "RS256";

    // These should be generated once and then stored securely.
    const { publicKey, privateKey } = await generateKeyPair(alg);

    const publicKeyBuffer = await exportSPKI(publicKey);
    const privateKeyBuffer = await exportPKCS8(privateKey);

    const publicKeyBase64 = publicKeyBuffer.toString('base64');
    const privateKeyBase64 = privateKeyBuffer.toString('base64');

    console.log('Public Key (Base64):', publicKeyBase64);
    console.log('Private Key (Base64):', privateKeyBase64);
    ```

    Store the base64 encoded version of the private and public keys to your environment file.

    ```bash .env theme={null}
    JWT_PUBLIC_KEY=encoded_public_key_value
    JWT_PRIVATE_KEY=encoded_private_key_value
    ```
  </Tab>
</Tabs>

#### Creating the JSON Web Token

JSON Web Tokens, or JWT for short, are a standard way to encode a series of claims in a payload, that you sign, and can be verified later by a separate party (verifier, in this case, Crossmint) by using public key cryptography.

Crossmint requires you to create a JWT with the following claims:

| Claim | Type                            | Required | Content                                                                                                             |
| ----- | ------------------------------- | -------- | ------------------------------------------------------------------------------------------------------------------- |
| iss   | string                          | yes      | Your Crossmint project ID                                                                                           |
| sub   | string                          | yes      | Unique identifier for this user. Use the same userId you use elsewhere when identifying this user in Crossmint APIs |
| aud   | string \| array (string)        | yes      | "crossmint.com"                                                                                                     |
| exp   | int (unix timestamp in seconds) | no       | The expiration time (unix seconds). Set it at a minimum to around 10m after it was issued.                          |
| nbp   | int (unix timestamp in seconds) | no       | Not Before time (unix seconds). Tokens with this claim are valid from that moment forward.                          |
| iat   | int (unix timestamp in seconds) | no       | The time in which the token was emitted.                                                                            |

In addition, you must ensure you follow the following configurations when creating the tokens:

| Config                | Options supported                                 | Recommended |
| --------------------- | ------------------------------------------------- | ----------- |
| Encoding              | base64                                            | base64      |
| JWS signing algorithm | [RS256](https://github.com/panva/jose/issues/262) | RS256       |
| Encryption            | none                                              | none        |

Below is some backend example code to generate a valid JWT for a given user.

First, ensure you have the jose package installed: `npm install jose`.

```javascript theme={null}
import { SignJWT, jwtVerify, importPKCS8, importSPKI } from "jose";
import dotenv from "dotenv";
import { Buffer } from "buffer";

// Load environment variables
dotenv.config();

// Function to generate a JWT using RS256
async function generateJWT(userId) {
    // Decode the private key from base64 and import it
    const privateKeyPEM = Buffer.from(process.env.JWT_PRIVATE_KEY, "base64").toString("utf8");
    const privateKey = await importPKCS8(privateKeyPEM, "RS256");

    // Create and sign the JWT
    const jwt = await new SignJWT()
        .setProtectedHeader({ alg: "RS256" })
        .setIssuer(process.env.CROSSMINT_PROJECT_ID)
        .setSubject(userId)
        .setAudience("crossmint.com")
        .setExpirationTime("10m")
        .sign(privateKey);

    return jwt;
}
```

#### How to Test

Use the [jwt.io](https://jwt.io/) token debugger to decode and inspect your tokens.

You can also test locally on your machine decoding your own token and ensuring the fields are properly decoded, using `jose`.

```javascript theme={null}
import { jwtVerify, importSPKI } from "jose";
import dotenv from "dotenv";

// Load environment variables
dotenv.config();

// JWT received from request. Replace this with the actual token you receive.
const jwt = "<your_jwt_here>";

async function verifyJWT(token) {
    // Decode the public key from base64 and import it
    const publicKeyPEM = Buffer.from(process.env.JWT_PUBLIC_KEY, "base64").toString("utf8");
    const publicKey = await importSPKI(publicKeyPEM, "RS256");

    // Verify the JWT
    try {
        const { payload, protectedHeader } = await jwtVerify(token, publicKey, {
            issuer: process.env.CROSSMINT_PROJECT_ID,
            audience: "crossmint.com",
        });

        // Log the verified payload and header
        console.log("Protected Header:", protectedHeader);
        console.log("Payload:", payload);
    } catch (error) {
        console.error("Error verifying JWT:", error);
    }
}

// Call the function with the JWT
verifyJWT(jwt);
```

#### Expose a JWKS Endpoint

In the step earlier, you learned how to create JWT tokens that Crossmint can interpret. However, in order for Crossmint to validate that these tokens are coming from your project and not an impersonator, Crossmint must validate their signatures against your public key.

You must communicate to Crossmint what your public keys are by using a standard JSON Web Key Set endpoint (JWKS). This is a public API endpoint on your server where you can broadcast what are the current keys valid for your tokens.

The reason why this is implemented as an API, instead of you passing Crossmint a static public key in the console, is in order to support key rotation of your JWT keys in the future.

Below is a very basic example

```javascript theme={null}
import { exportJWK } from "jose";

// retrieve public key generated previously
const { publicKey } = await getMyJWTKeyPair();

// Router provided as pseudo-code, adapt to specific framework.
router.get("/.well-known/jwks.json", async (req, res) => {
    // fetch the public key from env
    const publicKeyPEM = Buffer.from(process.env.JWT_PUBLIC_KEY, "base64").toString("utf8");

    // Import the SPKI
    const publicKey = await importSPKI(publicKeyPEM, "RS256");

    // Then export it as a JSON Web Key
    const publicJwk = await exportJWK(publicKey);

    res.send({ keys: [publicJwk] });
});
```

Once your endpoint is set up, share the URL with your Crossmint Customer Success Engineer.

#### Passing the JWT to Crossmint in API Calls

The last step to complete the loop is that when you're invoking a Crossmint API, you must pass the JWT.

At this time, the only API that uses it is `getOrCreateWallet()` in the Smart Wallet SDK, which takes the JWT as an input.

### Advanced Topics

#### Key Rotation

The private key used for signing your signatures could, over time, be compromised.

Crossmint recommends that you rotate your keys every 6 months to a year.

When performing a rotation, keep the old key if possible as a valid key for a day or so, to ensure none of your existing tokens expire.
