I’m fairly new to Fastify, and I’m using it to build an API. I'm having some trouble when using an authentication decorator in a route, which is throwing the following error: "code":"FST_ERR_HOOK_INVALID_HANDLER","name":"FastifyError","statusCode":500},"msg":"preHandler hook should be a function, instead got [object Undefined]".
I've set up a decorator file at @/decorators/authentication.ts
import { FastifyInstance, FastifyReply, FastifyRequest } from "fastify";
import fjwt, { FastifyJWT } from "@fastify/jwt";
import fCookie from "@fastify/cookie";
import { env } from "@/config/constants"; // Ensure the correct path
export default async function authentication(app: FastifyInstance) {
// Register JWT plugin
app.register(fjwt, { secret: String(env.JWT_SECRET_KEY) });
// Register cookies plugin
app.register(fCookie, {
secret: env.JWT_SECRET_KEY,
hook: "preHandler",
});
// Decorate app with the authenticate function
app.decorate(
"authenticate",
async (req: FastifyRequest, reply: FastifyReply) => {
try {
const token = req.cookies.access_token;
if (!token) {
return reply.status(401).send({ message: "Authentication required" });
}
// Verify the token and set the user in the request object
const decoded = req.jwt.verify<FastifyJWT['user']>(token)
req.user = decoded;
} catch (err) {
return reply.status(401).send({ message: "Invalid or expired token" });
}
}
);
}
and using it on @/modules/authentication/authentication.routes.ts
import { FastifyInstance } from "fastify";
import AuthenticationController from "./authentication.controller";
import UserService from "@/data-services/user-service";
import { $ref, userSchemas } from "./authentication.schema";
export default async function authRoutes(fastify: FastifyInstance) {
// Decorate fastify with the user service
fastify.decorate("userService", new UserService());
// Register schemas
userSchemas.forEach((schema) => fastify.addSchema(schema));
const authController = new AuthenticationController(fastify.userService);
// Login route
fastify.post(
"/login",
{
schema: {
body: $ref("loginSchema"),
response: {
201: $ref("loginResponseSchema"),
},
},
},
authController.login.bind(authController)
);
// Register route
fastify.post(
"/registro",
{
schema: {
body: $ref("registerUserSchema"),
response: {
201: $ref("registerUserResponseSchema"),
},
},
},
authController.register.bind(authController)
);
// Get current user route with preHandler for authentication
fastify.get(
"/me",
{
preHandler: [fastify.authenticate],
},
authController.getCurrentUser.bind(authController)
);
}
I set up my server.ts file as this
import Fastify from "fastify";
import { env } from "./config/constants";
import "./types";
import authentication from "@/decorators/authentication";
import routes from "./config/routes";
const app = Fastify({
logger: true,
});
const listeners = ["SIGINT", "SIGTERM"];
listeners.forEach((signal) => {
process.on(signal, async () => {
await app.close();
process.exit(0);
});
});
app.register(authentication);
app.register(routes);
app.listen(
{
port: env.PORT,
},
(err, address) => {
if (err) {
app.log.error(err);
process.exit(1);
}
app.log.info(`server listening on ${address}, PORT: ${env.PORT}`);
}
);
I'm logging a temporary handler on the /me route, but fastify.authenticate is returning as undefined. Can anyone explain why this is happening?
packages versions
"@fastify/cookie": "^10.0.1",
"@fastify/jwt": "^9.0.1",
"fastify": "^5.0.0",
"fastify-zod": "^1.4.0",
also already checked these solutions related stack overflow topic Github related issue but didnot found an answer in any of these
I was trying to authenticate the user using the decorator method i created before, but seems to not work, since my routes complains that authenticate is undefined. I have already registered the authentication decorator before the routes and added logs to confirm that the decorators are being registered properly before the routes.
The issue is that fastify.authenticate
returns undefined
This is because the export default async function authentication(app: FastifyInstance)
plugin does not use the fastify-plugin
module, so when it is registered app.register(authentication);
it creates its own context that does not effect the routes
one.
To fix it you need to wrap the function:
import fp from 'fastify-plugin'
export default fp(
async function authentication(app: FastifyInstance) { ... }
)
Useful readings: