I'm creating a middleware in nestjs and here is how the structure looks like
export class TestModule implements NestModule {
configure(consumer: MiddlewareConsumer) {
consumer
.apply(TestMiddleware)
.forRoutes('/test/:id')
}
}
I'm using fastify Adaptor and I've a question that how can I access the route param in my middleware.
I've tried accessing req.params but it comes empty and can't see any other property having the route param in it.
UPDATE
Apparently I've found a way to write middleware in fastify which allowed me to access proper fastify request body and response body with params resolved.
For that I've used the package called fastify-plugin
In the middleware file you can use it like this
export default fastifyPlugin(
function (fastify, options, next: VoidFunction) {
fastify.addHook('onRequest', (req, res, done) => {
done();
});
next();
},
{
name: 'auth',
},
);
Related
I'm just learning fastify and I'm not sure how to achieve what I want:
I have this route:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify,
],
}, this.configHandler.getConfiguration.bind(this.configHandler));
The pre handler does get executed and contains the known parameters like the request and the reply.
I want to pass a custom parameter to my preHandler function. Currently the preHandler is verifying the jwt token passed in the auth header. What I want to achieve is to pass scopes to the handler which may also be checked.
My preHandler currently is a plugin registered like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', async function (request: FastifyRequest, reply: FastifyReply) {
//jwtVerficiation happens here
//scope verification should follow
})
}
So overall: I have to add scopes somewhere at the route and I have to get those scopes somwhere inside my preHandler.
Any idea how I can do that?
Thanks!
You can define your decorate function like this:
const jwtVerifyPlugin: FastifyPluginAsync = async (fastify: FastifyInstance, options: FastifyPluginOptions) => {
fastify.decorate('jwtVerify', function (options?: { scopes?: string[] }) {
return async function (request: FastifyRequest, reply: FastifyReply, done: HookHandlerDoneFunction) {
if (options?.scopes) {
// access scopes here
}
done();
};
})
}
and then use it like this:
this.fastify.get('/ping', {
preHandler: [
this.fastify.jwtVerify({ scopes: ['admin'] }),
],
}, this.configHandler.getConfiguration.bind(this.configHandler));
I have a Nodejs typescript authentication system that works using passport.
My problem is that when I use req.user in a route I get this error on req.user: Object is possibly 'undefined'.
This is a normal behavior of Typescript but I am using a middleware to protect routes that I want to use req.user in them and this way req.user cannot be undefined.
This is where I extend Express.User Type:
import { users } from "#prisma/client";
declare global {
namespace Express {
interface User extends users {}
}
}
This is the middleware that I'm using to protect routes for logged in users:
export function checkIsAuthenticated(req: Request, res: Response, next: NextFunction) {
if (req.isAuthenticated()) {
if (!req.user) req.logOut();
else return next();
}
res.status(400).json({
errors: [{ message: "no user logged in" }],
});
}
And this is the route for getting the user info:
export function userRoute(req: Request, res: Response) { // defining the route
res.json({
id: req.user.id, // I'm getting the error in these 4 lines
username: req.user.username, //
email: req.user.email, //
role: req.user.role, //
});
}
router.get("/user", checkIsAuthenticated, userRoute); // using the route
I don't want to check if user is defined because I don't want to do it in every route and that's not a good practice. That's why middleware are for.
I'm not good at Typescript so I need some help to fix it. Thanks
I don't want to check if user is defined because I don't want to do it in every route and that's not a good practice.
My first solution trying to accommodate that requirement didn't work, so I've removed it. I've left my second solution, which is:
But, I'd quibble that there's nothing wrong with checking the request for a user and giving yourself a nice useful error message if you've accidentally used one of these handlers on a route that you forgot to put the authentication on. That would look like this:
type RequestWithUser = Request & {user: typeOfUserObject};
function assertHasUser(req: Request): asserts req is RequestWithUser {
if (!( "user" in req)) {
throw new Error("Request object without user found unexpectedly");
}
}
Then your handler for those routes:
export function userRoute(req: Request, res: Response) {
assertHasUser(req);
// ...you can use `req.user` here...
});
Playground example
(You don't really need the explicit RequestWithUser type, you could just use asserts req is Request & {user: typeOfUserObject}.)
export function userRoute(req: Request, res: Response) { // defining the route
res.json({
id: req.user!.id, // you tell typescript that req.user for sure not. null
username: req.user!.username, //
email: req.user!.email, //
role: req.user!.role, //
});
}
router.get("/user", checkIsAuthenticated, userRoute);
I've got a Node app using express-openapi-validator that takes a an api spec file (which is a .yml file), with request and response validation. The express-openapi-validator package routes the request to a handler file (defined in the spec). This is what one of the handlers might look like:
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
I've got an API key feature, where users can get a new API key, and the other endpoints that need the caller to have the API key in the request headers to validate the request.
I know it should be possible to use middleware to validate the request, but I can't figure out how to use custom middleware with the express-openapi-validator package on select endpoints.
For eg:
GET /apikey = does not require api key
GET /resource = requires api key
How do I configure this?
Here's what the openapi validator code in my app.js looks like:
new OpenApiValidator({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
})
.install(app)
.then(() => {
app.use((err, _, res) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
});
});
I actually ended up finding a solution for this myself.
First of all, I'm using version 4.10.5 of express-openapi-validator, so the code above is slightly different.
Here's what it looks like now:
// index.js
app.use(
OpenApiValidator.middleware({
apiSpec,
validateResponses: true,
operationHandlers: path.join(__dirname, './handlers'),
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
}),
);
app.use((err, req, res, next) => {
res.status(err.status || 500).json({
message: err.message,
errors: err.errors,
});
The way I ended up using middleware in my routes is below:
I've added a securitySchemes section in my swagger.yml file, like so:
components:
securitySchemes:
verifyApiKey:
type: apiKey
in: header
name: x-api-key
bearerAuth:
type: http
scheme: bearer
bearerFormat: JWT
There's a bit more information about it here: https://swagger.io/docs/specification/authentication/
On each route that needs the middleware, I'm adding a security section, like so:
/team:
post:
security:
- bearerAuth: []
description: Create a new team
operationId: createTeam
x-eov-operation-id: createTeam
x-eov-operation-handler: team
As you can see in my code above (in the index.js file), I've got a validateSecurity key, with a handlers key that then has the correlating keys that are in my swagger.yml (verifyApiKey and bearerAuth). These functions get the request and scope to check if they're valid. These functions return a boolean value, so true means that the middleware lets the request through, and false means a 403 response will be returned.
validateSecurity: {
handlers: {
verifyApiKey(req, scopes) {
return middleware.verifyApiKey(req)
},
bearerAuth(req, scopes) {
return middleware.verifyToken(req)
}
}
},
Please respond if I've got anything above wrong, or if the explanation can be clearer. If you have questions, please post them below.
You can simply pass array of handlers instead of just 1 function, like in express.
So in you code, the getUsers function that probably is what the x-eov-operation-id refers to, would be an array of 2 functions:
const getUsers = [
apiKeyMiddleware,
(req, res) => {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
];
I was in a similar situation as you, using OpenAPI/Swagger packages like that limited my ability to add specific middleware per endpoint, so my solution was I created an npm module called #zishone/chaindler.
You can use it like this:
const { Chain } = require('#zishone/chaindler');
function getUsers(req, res) {
const { 'x-user-id': userId } = req.headers
res.status(200).json(`Your userId is ${userId}`)
}
function postUsers(req, res) {
// ...
}
function mw1(req, res, next) {
next()
}
function mw2(req, res, next) {
next()
}
module.exports = {
getUsers: new Chain(mw1, mw2).handle(getUsers),
postUsers: new Chain(mw1).handle(postUsers)
}
Basically it just chains the middlewares then calls them one by one then call the handler/controller last.
I am trying to implement an express API that authenticates via a jwt token and I'm using the express-jwt middleware to authenticate access to a method:
import express from 'express'
import jwt from 'express-jwt'
const app = express()
app.get('/protected', jwt({ secret: 'jwtSecret', algorithms: ['HS256'] }), prot)
const prot = (
req: express.Request,
res: express.Response,
_next: express.NextFunction
): void => {
res.json({
msg: req.user,
})
}
app.listen(3000)
But I get this error:
Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs>'.ts(2339)
I've tried attaching req: express.Request & { user: unknown }, but then the function doesn't fit with express anymore.
Any ideas what I can do here. If worse comes to worse, can I just tell TypeScript to shut up somehow, since I know that the field will be there (even though I know that's not the point of TypeScript)
user is not an existing property on req object. You can make your own typings for req object by creating a .d.ts file on your project, for example, create a express.d.ts file and it will contains:
declare namespace Express {
export interface Request {
user: SomeType
}
}
Demo: https://repl.it/repls/OldlaceWhoppingComputationalscience
Try deleting the express.d.ts and the error will appears again.
declare global {
namespace Express {
interface Request {
user?: UserPayload;
}
}
}
this is an example of UserPaylaod
interface UserPayload {
id: string;
email: string;
}
I have a graphql endpoint which is authorised by a JWT. My JWT strategy verifies the JWT then adds the user object to the request object.
In a restful route, I would access my users' data like so:
router.get('/', (req, res, next) => {
console.log('user', req.user)
}
I want to access req.user object within my graphql resolver in order to extract the users' ID. However, when I try log the context variable, it is always empty.
Do I need to configure my graphql endpoint to pass through the req data to the resolver?
My app.js has my graphql set up like this:
import { graphqlExpress, graphiqlExpress } from 'apollo-server-express';
app.use('/graphql', [passport.authenticate('jwt', { session: false }), bodyParser.json()], graphqlExpress({ schema }));
Then I have my resolvers like so:
const resolvers = {
Query: {
user: async (obj, {email}, context) => {
console.log('obj', obj) // undefined
console.log('email', email) // currently passed through in graphql query but I want to replace this with the user data passed in req / context
console.log('context', context) // {}
return await UserService.findOne(email)
},
};
// Put together a schema
const schema = makeExecutableSchema({
typeDefs,
resolvers,
});
How can I access my JWT user data in my resolvers?
Apparently you need to pass through context manually like so:
app.use('/graphql', [auth_middleware, bodyParser.json()], (req, res) => graphqlExpress({ schema, context: req.user })(req, res) );
Found the answer here if anybody is interested: