im currently working on a express application that uses typescript. Im currently working on a Authentication Middleware and was wondering if you can make the Middlewares typesafe in a way:
authenticateJwt = (
req: RequestWithToken,
res: Response,
next: () => void
) => {
// Append the decoded req.token to the req header so we can use it internally
const token = req.token;
// #ts-ignore
this.verifyJwt(token)
.then((decoded: Token) => {
req.token = decoded;
next();
})
.catch(() => res.status(401).send('Unauthorized'));
};
now in my routes.ts:
router.get(
'/me',
// #ts-ignore
jwtService.authenticateJwt,
userController.getProfileFromUser
);
I have to write // #ts-ignore because it says that '(req: RequestWithToken, res: Response, next: () => void) => void is not of type RequestHandlerParams
definition of RequestWithToken:
export interface RequestWithToken extends Request {
token: Token;
}
export interface Token {
username: string;
}
create a custom.d.ts
and overwrite the Request Interface of express and express-serve-static-core
declare module 'express' {
interface Request {
token: Token;
}
}
declare module 'express-serve-static-core' {
interface Request {
token: Token;
}
}
this way both the RequestHandlerParams(useually your controller) and RequestHandler(useually your Middleware) are getting your new Request Interface.
then add it to the files section of your tsconfig.json:
"files": [
"src/custom.d.ts"
]
Have you tried :
const token = req.token as Token;
Related
I am using nestjs, graphql, & prisma. I am trying to figure out how to pass my jwt token for each database request to the prisma service iv created. Iv tried an object to the constructor but then wont compile saying I am missing a dependency injection for whatever I reference in the constructor paramter.
#Injectable()
export class PrismaService
extends PrismaClient
implements OnModuleDestroy {
constructor() {
super();
//TODO how do I pass my jwt token to this for each request?
this.$use(async (params, next) => {
if (params.action === 'create') {
params.args.data['createdBy'] = 'jwt username goes here';
}
if (params.action === 'update') {
params.args.data['updatedBy'] = 'jwt username goes here';
}
const result = await next(params);
return result;
});
}
async onModuleDestroy() {
await this.$disconnect();
}
}
Are you using a nest middleware?
JWT is normally passed to a Controller, not a service.
Example:
#Injectable()
export class MyMiddleware implements NestMiddleware {
private backend: any // This is your backend
constructor() {
this.backend = null // initialize your backend
}
use(req: Request, res: Response, next: any) {
const token = <string>req.headers.authorization
if (token != null && token != '') {
this.backend
.auth()
.verifyIdToken(<string>token.replace('Bearer ', ''))
.then(async (decodedToken) => {
const user = {
email: decodedToken.email,
uid: decodedToken.uid,
tenantId: decodedToken.tenantId,
}
req['user'] = user
next()
})
.catch((error) => {
log.info('Token validation failed', error)
this.accessDenied(req.url, res)
})
} else {
log.info('No valid token provided', token)
return this.accessDenied(req.url, res)
}
}
private accessDenied(url: string, res: Response) {
res.status(403).json({
statusCode: 403,
timestamp: new Date().toISOString(),
path: url,
message: 'Access Denied',
})
}
}
So every time I get an API call with a valid token, the token is added to the user[] in the request.
In my Controller Class I can then go ahead and use the data:
#Post()
postHello(#Req() request: Request): string {
return 'Hello ' + request['user']?.tenantId + '!'
}
I just learned about an update in Nest.js which allows you to easily inject the header also in a Service. Maybe that is exactly what you need.
So in your service.ts:
import { Global, INestApplication, Inject, Injectable, OnModuleInit, Scope } from '#nestjs/common'
import { PrismaClient } from '#prisma/client'
import { REQUEST } from '#nestjs/core'
#Global()
#Injectable({ scope: Scope.REQUEST })
export class PrismaService extends PrismaClient implements OnModuleInit {
constructor(#Inject(REQUEST) private readonly request: any) {
super()
console.log('request:', request?.user)
}
async onModuleInit() {
// Multi Tenancy Middleware
this.$use(async (params, next) => {
// Check incoming query type
console.log('params:', params)
console.log('request:', this.request)
return next(params)
})
await this.$connect()
}
async enableShutdownHooks(app: INestApplication) {
this.$on('beforeExit', async () => {
await app.close()
})
}
}
As you can see in the log output, you have access to the entire request object.
I have been trying to use class-validator as middleware to validate some of my data.
I would love to get some advice as to
how can I also validate updates and what's a good validation
Here is the current class validator to validate the req.body sent when trying to register.
export default async (req: Request, res: Response, next: NextFunction) => {
let user = new User();
user.username = req.body.username;
user.email = req.body.email;
user.password = req.body.password;
let fieldErrors = await validate(user);
if (fieldErrors.length > 0) {
let errors = ValidatorErrToFieldErr(fieldErrors);
next(new HttpExeception({ statusCode: httpCode.BAD_REQUEST, errors }));
} else {
next();
}
};
What is a good validation pattern? I controller that handles some of the logic which in turn calls the service to mutate the database.
AuthController.ts
public static Register = async (
req: Request,
res: Response,
next: NextFunction
) => {
try {
req.body.password = await argon2.hash(req.body.password);
let modelUser = await service.addUser(req.body);
let user: IUserMe = {
//reasign user fields
};
req.session.user = {
id: user.id,
username: user.username,
isAdmin: user.isAdmin,
};
res.json({ user });
} catch (error) {
if (error.code === "23505") {
next(
new HttpExeception({
statusCode: httpCode.BAD_REQUEST,
errors: duplicationErrToFieldError(error.detail),
})
);
} else next(new HttpExeception({ statusCode: httpCode.SERVER_ERROR }));
}
};
UserService.ts
async addUser(input: IRegisterInput): Promise<User> {
return await getRepository(User).save(input);
}
So most middleware it’s called inside the routes themselves. I’ll use an example with Express, Passport, and TypeScript since it’s what I know best.
Say i don’t want users to access my “/home” page without signing in. So I write a middleware function:
export default ( req: Request, res: Response, next: NextFunction): void => {
if(req.user != undefined){
next();
}
else{
res.status(401);
}
}
This would be analogous to your class-validator function. Now, we need to make sure that this function runs before any API calls are made to “/home”.
Thus, we write the api route as
import * as express from “express”;
import {Request, Response} from “express”;
import isAuthenticated from “isAuthenticated.ts”;
class HomeRouter{
public path = “/”;
public router = App.router();
constructor(){
this.initRoutes();
}
public initRoutes(){
this.router.get(“/home”, isAuthenticated, (req: Request, res: Response) => {
res.send(“/index.html”);
}
}
}
This will force isAuthenticated to run before any of the logic in the rest of route is executed. If you would like the middleware to apply to every call to the server, just put express.use(isAuthenticated); in your server.ts file. If you’re using a technology different from Express that I’ve failed to identify, I’m sure the premise is the same, and the how will be in the documentation.
So I'm creating authentication logic in my Next.js app. I created /api/auth/login page where I handle request and if user's data is good, I'm creating a httpOnly cookie with JWT token and returning some data to frontend. That part works fine but I need some way to protect some pages so only the logged users can access them and I have problem with creating a HOC for that.
The best way I saw is to use getInitialProps but on Next.js site it says that I shouldn't use it anymore, so I thought about using getServerSideProps but that doesn't work either or I'm probably doing something wrong.
This is my HOC code:
(cookie are stored under userToken name)
import React from 'react';
const jwt = require('jsonwebtoken');
const RequireAuthentication = (WrappedComponent) => {
return WrappedComponent;
};
export async function getServerSideProps({req,res}) {
const token = req.cookies.userToken || null;
// no token so i take user to login page
if (!token) {
res.statusCode = 302;
res.setHeader('Location', '/admin/login')
return {props: {}}
} else {
// we have token so i return nothing without changing location
return;
}
}
export default RequireAuthentication;
If you have any other ideas how to handle auth in Next.js with cookies I would be grateful for help because I'm new to the server side rendering react/auth.
You should separate and extract your authentication logic from getServerSideProps into a re-usable higher-order function.
For instance, you could have the following function that would accept another function (your getServerSideProps), and would redirect to your login page if the userToken isn't set.
export function requireAuthentication(gssp) {
return async (context) => {
const { req, res } = context;
const token = req.cookies.userToken;
if (!token) {
// Redirect to login page
return {
redirect: {
destination: '/admin/login',
statusCode: 302
}
};
}
return await gssp(context); // Continue on to call `getServerSideProps` logic
}
}
You would then use it in your page by wrapping the getServerSideProps function.
// pages/index.js (or some other page)
export const getServerSideProps = requireAuthentication(context => {
// Your normal `getServerSideProps` code here
})
Based on Julio's answer, I made it work for iron-session:
import { GetServerSidePropsContext } from 'next'
import { withSessionSsr } from '#/utils/index'
export const withAuth = (gssp: any) => {
return async (context: GetServerSidePropsContext) => {
const { req } = context
const user = req.session.user
if (!user) {
return {
redirect: {
destination: '/',
statusCode: 302,
},
}
}
return await gssp(context)
}
}
export const withAuthSsr = (handler: any) => withSessionSsr(withAuth(handler))
And then I use it like:
export const getServerSideProps = withAuthSsr((context: GetServerSidePropsContext) => {
return {
props: {},
}
})
My withSessionSsr function looks like:
import { GetServerSidePropsContext, GetServerSidePropsResult, NextApiHandler } from 'next'
import { withIronSessionApiRoute, withIronSessionSsr } from 'iron-session/next'
import { IronSessionOptions } from 'iron-session'
const IRON_OPTIONS: IronSessionOptions = {
cookieName: process.env.IRON_COOKIE_NAME,
password: process.env.IRON_PASSWORD,
ttl: 60 * 2,
}
function withSessionRoute(handler: NextApiHandler) {
return withIronSessionApiRoute(handler, IRON_OPTIONS)
}
// Theses types are compatible with InferGetStaticPropsType https://nextjs.org/docs/basic-features/data-fetching#typescript-use-getstaticprops
function withSessionSsr<P extends { [key: string]: unknown } = { [key: string]: unknown }>(
handler: (
context: GetServerSidePropsContext
) => GetServerSidePropsResult<P> | Promise<GetServerSidePropsResult<P>>
) {
return withIronSessionSsr(handler, IRON_OPTIONS)
}
export { withSessionRoute, withSessionSsr }
I am trying to create this api.ts file and put the redis client on the request to pass it to all the routes. But I am getting an error that there is no client on the Request>. How do I extend the request type and put it on the request in TypeScript?
Here is my file
import { version } from '../../package.json';
import { Router, Request, Response, NextFunction } from 'express';
export default ({ config, client } : any) => {
let api = Router();
api.use((req: Request, res: Response, next: NextFunction) => {
req.client = client;
next()
})
api.get('/', (_req, res) => {
res.json({ version });
});
return api;
}
Try this,
req['client'] = client;
I am using Typescript in Node.js. When you use Express middleware, you often transform the Request object. With Typescript, however, we could not track how the Request object was transformed. If you know the middleware that passed before, is there a way to find out the type of the request from it? If not possible in express, I would like to find another framework where it is possible. Is it possible in Nest (https://github.com/kamilmysliwiec/nest)?
Example Code
import { Request, Response, NextFunction } from 'express';
function userMiddleware(req: Request & User, res: Response, next: NextFunction) {
req.user = {
id: 'user_id',
};
next();
}
interface User {
user: {
id: string;
}
}
interface Middleware {
<T>(req: Request & T, res: Response, next: NextFunction): void;
}
class Controller {
middleware = [userMiddleware];
get = new GetMethod(this.middleware);
post = (req: Request /* I don't know exact req type */, res: Response, next: NextFunction) => {
console.log(req.user) // Error!
}
}
class GetMethod {
constructor(middleware: Middleware[]) {
// How to deduce type of req from Middleware array?
}
}
const controller = new Controller();
express.use('/', controller.middleware, controller.post);
I want to extract type information from Middleware list in Controller class.
First I think the right interface is
interface User {
id: string;
}
Because they're callbacks they'll receive default Request that don't have user in its signature.
Therefore you have 2 options, do a type assertion, or to write a custom declaration. Both a fine if you do them properly.
Type assertion:
interface User {
id: string;
}
const isObject = (value: unknown): value is {[key: string]: unknown} => {
return value && typeof value === 'object';
};
const isReqWithUser = (req: Request): req is Request & {user: User} => {
return isObject(req) && !!req.user;
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
if (isReqWithUser(req)) {
console.log(req.user) // now it works
}
next();
}
}
Custom declaration:
but we need to understand that user not always exist on the request and we should mark it optional.
interface User {
id: string;
}
declare module 'express' {
export interface Request {
user?: User; // adding our custom declaration.
}
}
class Controller {
post = (req: Request, res: Response, next: NextFunction) => {
console.log(req.user) // now it works
next();
}
}