Cloudflare Workers上でWeb Crypto APIで公開鍵と秘密鍵を作成し、joseでJWTの作成と検証をしてみた。

import { Hono } from "hono";
import { RegExpRouter } from "hono/router/reg-exp-router";
import {SignJWT, jwtVerify} from "jose";

export const app = new Hono({ router: new RegExpRouter() });

app.get("/token", async (c) => {
  let token;
  let keyPair;
  let strPublicKey;
  try {
    keyPair = await crypto.subtle.generateKey(
      {
        name: "RSA-PSS",
        modulusLength: 2048, //can be 1024, 2048, or 4096
        publicExponent: new Uint8Array([0x01, 0x00, 0x01]),
        hash: { name: "SHA-256" }, //can be "SHA-1", "SHA-256", "SHA-384", or "SHA-512"
      },
      true,
      ["sign", "verify"]
    );

    let exported = await crypto.subtle.exportKey("spki", keyPair.publicKey);
    let exportedAsString = ab2str(exported);
    let exportedAsBase64 = btoa(exportedAsString);
    strPublicKey = `-----BEGIN PUBLIC KEY-----${exportedAsBase64}-----END PUBLIC KEY-----`;

    token = await new SignJWT({ "urn:example:claim": true })
      .setProtectedHeader({ alg: "PS256" })
      // .setProtectedHeader({ alg: "PS512" })
      .setIssuedAt()
      .setIssuer("urn:example:issuer")
      .setAudience("urn:example:audience")
      .setExpirationTime("2h")
      .sign(keyPair.privateKey);
    const { payload, protectedHeader } = await jwtVerify(
      token,
      keyPair.publicKey,
      {
        issuer: "urn:example:issuer",
        audience: "urn:example:audience",
      }
    );

    return c.json({
      token,
      payload,
      protectedHeader,
      publicKey: strPublicKey,
    });
  } catch (e) {
    return c.html(JSON.stringify(e));
  }
});

function ab2str(buf) {
  return String.fromCharCode.apply(null, new Uint8Array(buf));
}

export default app;

以下、実行した結果

{
  "token": "eyJhbGciOiJQUzI1NiJ9.eyJ1cm46ZXhhbXBsZTpjbGFpbSI6dHJ1ZSwiaWF0IjoxNjU2NTEyNzMwLCJpc3MiOiJ1cm46ZXhhbXBsZTppc3N1ZXIiLCJhdWQiOiJ1cm46ZXhhbXBsZTphdWRpZW5jZSIsImV4cCI6MTY1NjUxOTkzMH0.ENAV1ikjtBNxlysex3b2FTd7hyVeswRGPXqb9Bu2VcOkBKC_zps7ypzCaIKIsgZ4cX55oQwBxEQbdeY3gYUH9HUJDEdv1K7zRxVDbcuzilRGtgpXfeZg7ymRHjDZLhX8DQN5QinVH_pyypu4mu3yEOrR3DzTtsjajRpfUknTW5LptKlE90MZY7PETrDZQXz2-CrRflTGtAHuLUcBYC_VKJrXhMnCV412VXV62TsrOxHeW_8WzOBtDBscIV5H4qH_NFRrAzsx2rk1YD09rpIwFmtU0LYrbGItCqis07u0j29wmADLq2FKJBuDRHeiRpQGnOUQ258pnDdW0w_fIdOWbw",
  "payload": {
    "urn:example:claim": true,
    "iat": 1656512730,
    "iss": "urn:example:issuer",
    "aud": "urn:example:audience",
    "exp": 1656519930
  },
  "protectedHeader": {
    "alg": "PS256"
  },
  "publicKey": "-----BEGIN PUBLIC KEY-----MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhO/yo4bsP7Dtz/bFuleWo8y3sFCOFPDFZg5lSJsIU9OoXWZ+gFs31amWAmVsiVPhjAZY2y/RgsVAOHcVX+5k/aGXvLLhDGeyrfA7oLy6O7koMuTNkhz+LocHjw2Lku7iYduM4LKD7luRPm2sA+8hjXi0Z068vjRsfa1rIn9iMmWswG6lDPxA8XnTqsCcdQQUc3WZxDEetmxdmH61N2IWJZ39dWlo9bDlIoU79y9n0y2LfusByEBBEKDHL1+aB5Ua4xsXM/cGW8UZkGlMioo6v76nUWVgq24TCZFmR6M6MCk03pS4mup2fa+Nn7GqLet9/SHB+w7ZoZ38EJEUbRRCQQIDAQAB-----END PUBLIC KEY-----"
}

jwt.ioでも検証してみた。

jwt