clerk / javascript

Official Javascript repository for Clerk authentication
https://clerk.com
MIT License
1.09k stars 240 forks source link

Fastify plugin doesn't run before request validation #3423

Closed 5-tom closed 4 months ago

5-tom commented 4 months ago

Preliminary Checks

Reproduction

https://stackblitz.com/edit/stackblitz-starters-x6zbd9?file=server.js

Publishable key

pk_test_d29uZHJvdXMtbGVlY2gtOTQuY2xlcmsuYWNjb3VudHMuZGV2JA

Description

Steps to reproduce:

With the following code, I'm unable to perform the authentication check before the request is validated.

  1. Setup a fastify endpoint using the stackblitz link
  2. Perform an unauthenticated POST request with an invalid request body to the endpoint you've setup
  3. Watch to see what status code you recieve

Expected behavior:

The request should be checked to see if it can proceed before the request body is validated against a schema. I should get a 403 before a 400 or 500 etc.

Actual behavior:

Using my code snippet above, I get a 400 status code from zod. Status codes cannot be set after it's been sent, so I never receive a 403. The order of operations is incorrect.

It may relate to this https://github.com/clerk/javascript/issues/1350

Environment

Need to install the following packages:
envinfo@7.13.0
Ok to proceed? (y) 

  System:
    OS: Linux 6.1 Debian GNU/Linux 12 (bookworm) 12 (bookworm)
    CPU: (8) x64 11th Gen Intel(R) Core(TM) i7-1165G7 @ 2.80GHz
    Memory: 7.62 GB / 15.35 GB
    Container: Yes
    Shell: 5.9 - /bin/zsh
  Binaries:
    Node: 22.1.0 - ~/.local/share/nvm/versions/node/v22.1.0/bin/node
    npm: 10.7.0 - ~/.local/share/nvm/versions/node/v22.1.0/bin/npm
  npmPackages:
    @clerk/clerk-react: latest => 5.1.0 
    @clerk/fastify: ^1.0.10 => 1.0.10 
    @emotion/react: latest => 11.11.4 
    @emotion/styled: latest => 11.11.5 
    @fastify/mongodb: ^8.0.0 => 8.0.0 
    @fastify/multipart: ^8.2.0 => 8.2.0 
    @fastify/swagger: ^8.14.0 => 8.14.0 
    @fastify/swagger-ui: ^3.0.0 => 3.0.0 
    @fontsource/jetbrains-mono: ^5.0.20 => 5.0.20 
    @mui/material: latest => 5.15.18 
    @playwright/test: latest => 1.44.0 
    @testing-library/react: latest => 15.0.7 
    @types/cookies: latest => 0.9.0 
    @types/jsonwebtoken: latest => 9.0.6 
    @vitejs/plugin-react: latest => 4.2.1 
    @vitest/coverage-istanbul: latest => 1.6.0 
    cookies: latest => 0.9.1 
    dotenv: latest => 16.4.5 
    esbuild: latest => 0.21.3 
    fastify: ^4.27.0 => 4.27.0 
    fastify-plugin: ^4.5.1 => 4.5.1 
    fastify-type-provider-zod: ^1.2.0 => 1.2.0 
    jsdom: latest => 24.0.0 
    jsonwebtoken: latest => 9.0.2 
    msw: latest => 2.3.0 
    nodemon: latest => 3.1.0 
    react: latest => 18.3.1 
    react-dom: latest => 18.3.1 
    react-router-dom: latest => 6.23.1 
    serverless-http: latest => 3.2.0 
    typescript: latest => 5.4.5 
    vite: latest => 5.2.11 
    vitest: latest => 1.6.0 
    zod: latest => 3.23.8 
    zodix: latest => 0.4.4
5-tom commented 4 months ago

I found a solution. Move the check to a preValidation option:

const opts = {
    schema: {
        consumes: ["multipart/form-data"],
        body: FORM_SCHEMA
    },
    preValidation: (req, reply, done) => {
        const auth = getAuth(req);
        if (!auth.userId) {
            return reply.code(403).send();
        }
        done();
    }
};
// ...
fastify.post("/form", opts, // ...
dimkl commented 4 months ago

Hello @5-tom,

Your solution is correct (you should also keep the hookName: "onRequest"). To give you more context, the clerkPlugin registration on the onRequest hook means that the Clerk middleware responsible for determining the state of the request is executed before the body is parsed/validated. The Clerk middleware does not enforce the requests to be authenticated, it identifies the state and leaves the handling to the implementor using the getAuth(req) and checks.