How to properly pass a user and run middleware on apollo server v4

How to properly pass a user and run middleware on apollo server v4


1

I am creating an apollo server v4 gql gateway with the express.js integration.
I have user typedefs and user resolvers working and creating a jwt when a user is created.
I then try to use my jwtMiddleware middleware on the /graphql endpoint so that it can validate a bearer token is being passed in to prevent unauthenticated requests to some other queries I have created.

In my index.ts:

import express, { Request, Response, Application } from 'express';
import gqlServerV1 from './gql/v1/gqlServerV1';
import { mongoClient } from './mongodb/mongoclient';
import cors from 'cors';
import pkg from 'body-parser';
import { expressMiddleware } from '@apollo/server/express4';
import jwtMiddleware from './middleware/verifyJwt';
const { json } = pkg;

// Create express app
const app: Application = express();
const port: number = 5950;
const startUp = async () => {
  // Fire up mongodb client
  await mongoClient();

  // Apply gql server to work with express as middleware w/ apollo-server v4
  // Migrate away from v3: https://www.apollographql.com/docs/apollo-server/migration/#migrate-from-apollo-server-express
  await gqlServerV1.start();

  app.use(
    '/v1/graphql',
    cors<cors.CorsRequest>(),
    json(),
    jwtMiddleware,
    expressMiddleware(gqlServerV1, {
      context: async ({ req }) => ({
        token: req.headers.token,
        user: req.user,
      }),
    })
  );

  console.log(`server started at https://localhost:${port}`);
};

app.listen(port, async () => {
  await startUp();
});

jwtMiddleware file

import jwt from 'jsonwebtoken';
import dotenv from 'dotenv';

dotenv.config();

const JWT_SECRET = process.env.JWT_SECRET || 'defaultSecret';

const jwtMiddleware = async (req, res, next, context) => {
  const authorizationHeader = req.headers.authorization;

  console.log('heress');

  if (!authorizationHeader || !authorizationHeader.startsWith('Bearer ')) {
    // Token is missing or in an invalid format
    return next();
  }

  const token = authorizationHeader.replace('Bearer ', '');

  try {
    // Verify the JWT token
    const decodedToken = jwt.verify(token, JWT_SECRET);

    console.log(decodedToken);

    // Get the user data from the request context
    const user = context.user;

    // Continue processing the request if the user is authenticated
    if (user) {
      next();
    } else {
      // The user is not authenticated, so return an error
      return res.status(401).json({ message: 'Unauthorized' });
    }
  } catch (error) {
    // The JWT token is invalid, so return an error
    return res.status(401).json({ message: 'Invalid token' });
  }
};

export default jwtMiddleware;

The issue I am facing:

when starting my server I see this error coming from index.ts when passing req.user

TSError: ⨯ Unable to compile TypeScript:
src/index.ts:37:19 - error TS2339: Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'.

37         user: req.user,
  1. I never see any console logs from my middleware being output.

This is my gql server in a separate file:

import { ApolloServer } from '@apollo/server';
import addressTypeDefs from './typeDefs/addressTypeDefs';
import userTypeDef from './typeDefs/userTypeDef';
import resolvers from './resolvers';

interface MyContext {
  token?: String;
  user?: Object;
}

// Create gql server
const gqlServerV1 = new ApolloServer<MyContext>({
  typeDefs: [addressTypeDefs, userTypeDef],
  resolvers,
  introspection: false,
});

export default gqlServerV1;

package json scripts:

  "scripts": {
    "start": "ts-node-dev --watch src src/index.ts",
    "dev:server": "nodemon --watch './**/*.ts' --exec 'node --experimental-specifier-resolution=node --loader ts-node/esm' src/index.ts",
    "test": "jest --detectOpenHandles --config jest.config.ts ./__tests__",
    "watch": "nodemon --watch './**/*.{ts,graphql}' --exec 'node --experimental-specifier-resolution=node --no-warnings --loader ts-node/esm' src/index.ts",
    "dev": "tsc-watch --onSuccess "npm run watch""
  },

I also have a custom.d.ts file to try to extend express:

interface JwtPayload {
  // Customize the properties based on JWT payload
  email: string;
}

// Extend the express request type
declare namespace Express {
  interface Request {
    user: JwtPayload;
  }
}

  1. How do I properly pass a req.user ?
  2. Why is my middleware never running to validate the user/token?

Thanks!

1 Answer
1


0

I figured out the problem and will leave the question posted so others can learn. The issue I was facing was because ts-node was not able to interpret the type I was extending and adding user to i.e. the request. Here’s what I did.

  1. Create a new dir and d.ts file at root/src/@types/express/index.d.ts.
  2. In ts config make sure I have my custom types come before any others in the typeRoot config. Like so:
{
  "compilerOptions": {
    "rootDirs": ["src"],
    "outDir": "dist",
    "lib": ["es2020"],
    "target": "es2020",
    "module": "esnext",
    "moduleResolution": "node",
    "esModuleInterop": true,
    "types": ["node", "jest", "express"],
    "typeRoots": ["./src/@types", "./node_modules/@types"],
    "skipLibCheck": true /* Skip type checking all .d.ts files. */
  },
  "include": ["src/**/*.ts"]
}

  1. Inside of the index.d.ts I imported a custom interface I have set up in my models from mongodb and set that as the type like so:
import * as express from 'express';
import { User } from '../../models/User';

declare global {
  namespace Express {
    interface Request {
      user?: User;
    }
  }
}

  1. In my verifyJwt middleware, I removed context from the middleware signature and replaced instances of context with req instead.

After this, I restarted the ts server as well as my local server and Voila, it worked and the dumb Property 'user' does not exist on type 'Request<ParamsDictionary, any, any, ParsedQs, Record<string, any>>'. error was gone. AND my middleware began working and I started seeing console logs from the middleware as verification.

Hope this helps someone else.



Leave a Reply

Your email address will not be published. Required fields are marked *