skip to Main Content

I have the following Next.js/Clerk.js middleware:

import { authMiddleware } from "@clerk/nextjs";
import { jwtVerify } from "jose";

export default authMiddleware({
  publicRoutes: ["/", "/contact", "/pricing", "/api/webhooks/user", "/api/reviews/add", "/api/user"],
  afterAuth (auth, req, evt) {
    const secret = new TextEncoder().encode (process.env.CLERK_PUBLIC_KEY)
    const decoded = jwtVerify ("eyJhbGciOiJSUzI1NiIsInR5cCI6IkpXVCJ9.eyJkZXYiOiJkdmJfMlpFZkVyUG91dHBuaFptQXZzdk0zMkVadkVhIiwiaWQiOiJjbGllbnRfMlpFZkpieUFQUXRWbVB4Q3R2SER4SE04a2tCIiwicm90YXRpbmdfdG9rZW4iOiJmaWNid25rZDE0aTVpNm5rZzJicjc1ODl5NWZqeGloemloZDk2dnprIn0.qURs213vHtEe_2DTmOVN8jCPCJhQfIIEjWLMIXgIVYs86U1J3P5BV9EHexjvXda416D4wHAFdxUhUzjKj42CNM4TYTrrsXRT4m_fMNq78NrvwMf7ge2tmcSYNf04c7gqInQzJMNiKILZbQXN0yxExZ1lBbPesg-ZCsx5HZ1544-g0yrcZvxu7HkSwIG56C3ITae51rtMj4lpxyYUxdR9MZ0JZ-HH2XlCT_F3BMDUn_IHNXj2IDF6gI-1kx3UWwYZ5uCyTipbsuOwgFCINFA2m8h3IM0jS9KXGLNrixaej9M0uDEcYkxVRvSwNKJfeHmnhJefYEk82192XpmXJDft8Q", secret)
    console.log(decoded)
  }
});

export const config = {
  matcher: ["/((?!.*\..*|_next).*)", "/", "/(api|trpc)(.*)"],
};

I’m using hose to verify a the JWT. But when the middleware is called I get an error:

Promise {
  [Symbol(async_id_symbol)]: 290979,
  [Symbol(trigger_async_id_symbol)]: 290975,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: undefined,
  [Symbol(kResourceStore)]: {
  headers: [Getter],
  cookies: [Getter],
  mutableCookies: [Getter],
  draftMode: [Getter]
}
}
TypeError: Key for the RS256 algorithm must be of type CryptoKey. Received an instance of Uint8Array

What’s wrong here?

2

Answers


  1. The error indicates that the jwtVerify function from the jose library expects a key of type CryptoKey for the RS256 algorithm, but you are providing a Uint8Array.

    In your code, you are using new TextEncoder().encode(process.env.CLERK_PUBLIC_KEY) to convert your key to a Uint8Array. However, for RS256 JWT verification, you need to convert this key to a CryptoKey.

    You can achieve this by using the Web Crypto API to import the key. Here’s how you can modify your code:

    First, import the key as a CryptoKey:

    import { createPublicKey } from 'crypto';
    
    // Assuming your public key is in PEM format
    const publicKey = createPublicKey({
      key: process.env.CLERK_PUBLIC_KEY,
      format: 'pem',
      type: 'spki'
    });
    

    Then, use this publicKey with jwtVerify:

    const decoded = await jwtVerify(token, publicKey);
    

    Your updated middleware should look something like this:

    import { authMiddleware } from "@clerk/nextjs";
    import { jwtVerify } from "jose";
    import { createPublicKey } from 'crypto';
    
    export default authMiddleware({
      publicRoutes: ["/", "/contact", "/pricing", "/api/webhooks/user", "/api/reviews/add", "/api/user"],
      afterAuth: async (auth, req, evt) => {
        const publicKey = createPublicKey({
          key: process.env.CLERK_PUBLIC_KEY,
          format: 'pem',
          type: 'spki'
        });
    
        const decoded = await jwtVerify("your_jwt_token", publicKey);
        console.log(decoded);
      }
    });
    
    export const config = {
      matcher: ["/((?!.*\..*|_next).*)", "/", "/(api|trpc)(.*)"],
    };
    
    Login or Signup to reply.
  2. Use the jose library like this. Look inside your JWT first, to get the correct algorithm from the JWT header, and the issuer / audience from the payload:

    import {createRemoteJWKSet, jwtVerify} from 'jose';
    
    const jwksUri = 'https://api.clerk.dev/v1/jwks';
    const jwks = createRemoteJWKSet(new URI(jwksUri));
    
    async function validateJWT(accessToken: string): Promise<JwtClaims> {
    
      const options = {
        algorithms: ['RS256'],
        issuer: 'myissuer',
        audience: 'myaudience'
      };
    
      const result = await jwtVerify(accessToken, jwks, options);
      return result.payload
    }
    

    The first time you call validateJWT the library will download token signing public keys and cache them. This will simplify your code and also make it more resilient, since it will continue to work when clerk renews its token signing keypair.

    Login or Signup to reply.
Please signup or login to give your own answer.
Back To Top
Search