Skip to content

[SDK] Add JWKS generation scriptΒ #63

@bitbeckers

Description

@bitbeckers

Describe the feature you'd like to request

For local testing oauth, users need jwks public/private pairs. Let help them get those

Describe the solution you'd like

Add a script, maybe executable with npx @hypercerts-org/sdk-core generate-jwks

maybe something like this

#!/usr/bin/env tsx
/**
 * Generate JWK keypair for ATProto OAuth
 *
 * This script generates an ES256 (P-256) keypair for use with ATProto OAuth.
 * - Private keyset: Output to console for copying to .env
 * - Public keyset: Written to public/jwks.json for serving via endpoint
 *
 * Usage:
 *   pnpm generate:jwks
 */

import { generateKeyPair, exportJWK } from "jose";
import { writeFileSync } from "fs";
import { join } from "path";
import { randomUUID } from "crypto";

async function generateJWKs() {
  console.log("πŸ” Generating ES256 (P-256) keypair for ATProto OAuth...\n");

  // Generate ES256 keypair
  const { publicKey, privateKey } = await generateKeyPair("ES256", {
    extractable: true,
  });

  // Generate a unique key ID
  const kid = randomUUID();

  // Export keys to JWK format
  const publicJWK = await exportJWK(publicKey);
  const privateJWK = await exportJWK(privateKey);

  // Add required fields
  publicJWK.kid = kid;
  publicJWK.alg = "ES256";
  publicJWK.use = "sig";

  privateJWK.kid = kid;
  privateJWK.alg = "ES256";
  privateJWK.use = "sig";

  // Create keysets
  const privateKeyset = {
    keys: [privateJWK],
  };

  const publicKeyset = {
    keys: [publicJWK],
  };

  // Write public keyset to file
  const publicPath = join(process.cwd(), "public", "jwks.json");
  writeFileSync(publicPath, JSON.stringify(publicKeyset, null, 2));
  console.log(`βœ… Public keyset written to: ${publicPath}\n`);

  // Output private keyset for .env
  console.log("πŸ“‹ Copy this PRIVATE keyset to your .env.local file:");
  console.log(
    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
  );
  console.log(`ATPROTO_JWK_PRIVATE='${JSON.stringify(privateKeyset)}'`);
  console.log(
    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n",
  );

  console.log("⚠️  IMPORTANT:");
  console.log("  1. Add the ATPROTO_JWK_PRIVATE to your .env.local file");
  console.log("  2. NEVER commit the private keyset to version control");
  console.log("  3. The public keyset (public/jwks.json) is safe to commit");
  console.log(
    "  4. Generate new keys for each environment (dev/staging/prod)\n",
  );

  console.log("πŸ“ Also add this to your .env.local:");
  console.log(
    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━",
  );
  console.log('NEXT_PUBLIC_ATPROTO_JWKS_URI="http://localhost:3000/jwks.json"');
  console.log(
    "━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n",
  );

  console.log("✨ JWK generation complete!\n");
}

generateJWKs().catch((error) => {
  console.error("❌ Error generating JWKs:", error);
  process.exit(1);
});

Describe alternatives you've considered

.

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions