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;
}
Related
Below is my Next.js (backend API) code to verify recaptcha token (created from the client side) and send a mail.
import { NextApiRequest, NextApiResponse } from "next";
import NextCors from 'nextjs-cors';
import { recaptchaAxios } from "../../axios/axiosBackend";
import sendGridMail from '#sendgrid/mail';
sendGridMail.setApiKey(process.env.SENDGRID_API_KEY);
interface FormData {
contactName: string;
contactEmail: string;
contactPhone: string;
contactSubject: string;
contactMessage: string;
token: string;
}
export default async (req: NextApiRequest, res: NextApiResponse) => {
await NextCors(req, res, {
// Options
methods: ['GET','POST'],
origin: '*',
optionsSuccessStatus: 200, // some legacy browsers (IE11, various SmartTVs) choke on 204
});
const formData: FormData = req.body;
console.log("form Data >>>>>>>>>>>>>>",formData)
const human = await validateHuman(formData.token);
if (!human) {
res.status(400);
return res.json({ success: false, errors: ["You are not authenticated"] });
}
const message = {
to: process.env.SENDGRID_MAIL_RECEIVER,
from: process.env.SENDGRID_MAIL_SENDER, // Change to your verified sender
subject: formData.contactSubject,
text: `Name: ${formData.contactName}\n
Contact: ${formData.contactPhone} \n
Email: ${formData.contactEmail} \n
Message: ${formData.contactMessage}`,
html: `Name: ${formData.contactName}
Contact: ${formData.contactPhone}
Email: ${formData.contactEmail}
Message: ${formData.contactMessage}`,
}
try {
await sendGridMail.send(message);
res.status(200);
return res.json({ success: true, errors: [] });
} catch (error) {
console.log(error);
res.status(500);
return res.json({ success: false, errors: ['Error occured while trying to send your details. Please contact your Administrator.']});
}
};
async function validateHuman(token: string): Promise<boolean> {
const secret = process.env.RECAPTCHA_SECRET_KEY;
const response = await recaptchaAxios.post(`/siteverify?secret=${secret}&response=${token}`,{}, {});
const success = response.data['success'];
console.log("server siteverify >>>>>>>>>>>>>",response);
return success;
}
recaptchaAxios has the baseURL as below
const recaptchaAxios = axios.create({
baseURL: `https://www.google.com/recaptcha/api`,
});
I have deployed the same code in vercel as well as using AWS Amplify.
In vercel when called to the above mail API, the Recaptcha token is verified and the mail is sent.
But unfortunately in AWS it gives the error
{ success: false, errors: ["You are not authenticated"] }
I have added all the environment variables in AWS which I have in vercel and the values are the same.
All the domains are added in reCaptch v3 console for the site.
So at this point I am stuck on why in AWS gives the error, but not vercel for the same code base
Is there anything that I am missing in AWS??
Cheers
My first pointer would be to console.log the environment variables on script load, also each time the recaptcha validation is triggered. This way you can be sure the ENV vars are all loaded correctly. You would be suprised to have a small case sensitivity typo, leave you without an important env variable.
Otherwise, I would check if I need to allow outgoing traffic (firewall rules) on AWS amplify, but this is less common, since AWS Amplify spawns a public site.
Issue was in the below code
const secret = process.env.RECAPTCHA_SECRET_KEY;
Even though the RECAPTCHA_SECRET_KEY was available in the environment variables in AWS, it was not accessible.
Fix was to introduce this key in next.config.js file
module.exports = {
images: {
domains: [],
},
env: {
RECAPTCHA_SECRET_KEY: process.env.RECAPTCHA_SECRET_KEY,
},
};
This solved the problem
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',
},
);
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 want to add a middleware to check the header values. if the expected do not present in the header then I want throw an error on API response. Following code is throwing error and I can see it in the console but I want to send this error to the user.
const server = new Server({
host: config.get('host'),
port: config.get('port' )
});
await server.register(require('#hapi/inert')); // eslint-disable-line #typescript-eslint/no-var-requires
server.register({
plugin: AuthService
})
server.route(
// mapping route paths with context path
routes.map((datum) => {
return {
...datum,
path: `${config.get('basePath')}${datum.path}`
};
})
);
plugin
import { notFound } from '#hapi/boom';
export const AuthService = {
name: 'authService',
version: '1.0.0',
register: async function (server: any, options: any) {
throw notFound(`Unauthorized user not found`);
}
};
Well, I think you are thinking it the express way too much. You should look at the authentication part of Hapi.
You can create a custom strategy and return an error if the header is not present.
Also, you can check how to use a Joi.schema() for all your routes.
I have the following controller.
#controller('/users')
class UsersController {
#httpGet('/', authMiddleware({ role: 'ADMIN' }))
public get() { ... }
}
I have implemented a custom AuthenticationProvider, which returns a principal containing details about the currently authenticated user, including the user's roles.
....
return new Principal({
firstName: "John",
lastName: "Smit",
roles: ["ADMIN"]
});
...
This all works fine, but I am wondering how I can retrieve the principal from the authMiddleware which is used by the above GET route.
For now I have an ugly hack which uses internals of InversifyJS.
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext: interfaces.HttpContext =
Reflect.getMetadata(
"inversify-express-utils:httpcontext",
req
);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
The custom authentication provider uses the authorization header to authenticate the user and returns a principal. I don't want to do this work again in the middleware, I just want to retrieve the principal.
This hack works, but I was wondering if someone knows a cleaner way of obtaining the HttpContext in this middleware.
I know you can access the HttpContext and thus the principal (user) if you extend from the BaseMiddleware, but then it's not clear to me how you pass configuration (parameters) to it, such as the desired role. Related to the following issue on InversifyJS.
https://github.com/inversify/InversifyJS/issues/673
This is not supported, but I can see why it is needed. We cannot pass the httpContext to the middleware as an argument because we want to keep the standard Express middleware compatible. This means that the only option is doing something like what you have done but ideally we should encapsulate it using some helper.
We need to implement something like the following getHttpContext function:
import * as express from "express";
import { getHttpContext } from "inversify-express-utils";
function authMiddlewareFactory() {
return (config: { role: string }) => {
return (
req: express.Request,
res: express.Response,
next: express.NextFunction
): void => {
const httpContext = getHttpContext(req);
const principal: interfaces.Principal = httpContext.user;
if (!principal.isInRole(config.role)) {
res.sendStatus(HttpStatus.UNAUTHORIZED);
return;
}
next();
};
};
}
Until this is implemented I don't see any problems with your implementation other than the information leakage of the inversify internals.