10

I'm using Passport to handle authentication with an Express application. This sets the user on the Express response. I'm using TypeScript, so setting the request type to Request in the route definitions will error because the user object does not exist on the Express Request. There are numerous questions about extending the request, either by declaration merging or extending the interface but these cause another error. My file looks like this:

import { Router, Request, Response }  from 'express'
import { User as CustomUser } from './user'

interface IRequest extends Request {
  user: CustomUser
}

const router: Router = Router()

router.get('/', requiresLogin, (req: IRequest, res: Response) => {
  console.log(req.user)
  res.sendStatus(204)
})

But now I'm getting the following TypeScript on the express callback:

Argument of type '(req: IRequest, res: Response) => void' is not assignable to parameter of type 'RequestHandlerParams'. Type '(req: IRequest, res: Response) => void' is not assignable to type '(RequestHandler | ErrorRequestHandler)[]'. Property 'includes' is missing in type '(req: IRequest, res: Response) => void'.

I've recently upgraded to TypeScript v2.8.3, and never had the problem previously.

Geraint Anderson
  • 3,234
  • 4
  • 28
  • 49
  • What version of `@types/express` are you using? I just tried the code above with TypeScript 2.8.3 and Express types 4.11.1, and did not get any compiler errors. – vesse May 22 '18 at 19:04
  • I'm using `@types/express` v4.11.1 and `express` v4.16.3. As far as I can tell there's nothing in `tsconfig.json` that would cause issues (e.g. I'm not specifying `typeRoots`. – Geraint Anderson May 23 '18 at 11:04
  • What does the signature of `requiresLogin` look like? – vesse May 23 '18 at 11:11
  • I'm getting a similar issue with 2.8.1 – Jimmy Breck-McKye May 30 '18 at 16:29
  • OK - this is strange. I fixed the issue by copying a compiling route into the file, using that, then rewriting its body. It's now functionally identical, but this time TSC passes absolutely fine. I feel there may be something strange happening inside TSC itself. – Jimmy Breck-McKye May 30 '18 at 16:52
  • @GeraintAnderson - may have a fix, see https://github.com/DefinitelyTyped/DefinitelyTyped/issues/26146#issuecomment-393386416 – Jimmy Breck-McKye May 31 '18 at 09:37
  • 1
    Did you ever find a solution for this? I am using TypeScript v3.4.5 and @types/express 4.17.0 and seeing the same issue – shreddish Nov 15 '19 at 15:46

1 Answers1

2

I just had a similar problem and solved it like this:

import { Router, Request, Response } from 'express';
import { User as CustomUser } from './user';

const router: Router = Router();

router.get('/', requiresLogin, (req: Request, res: Response) => {
  console.log(req.user as CustomUser);
  res.sendStatus(204);
});

Instead of augmenting the Express.js Request type, I simply told TypeScript that the req.user is going to be of type CustomUser using a type assertion. This seems like the exact scenario that the reference in the docs "Sometimes you’ll end up in a situation where you’ll know more about a value than TypeScript does."

https://www.typescriptlang.org/docs/handbook/basic-types.html#type-assertions

Now I know what you're thinking. "What if there is no user? Doesn't this defeat the purpose of Typescript?". Well, let's looks at this. If you are using Passport.js, you should not get this far without a user. This code should be somewhere inside your Passport config:

if (!user) {
  /** Set status code to Unauthorized */
  res.status(401);

  return next(err);
}

As for defeating Typescript... maybe you're right. If someone has a better answer for this scenario though, I am all ears.

Borduhh
  • 1,975
  • 2
  • 19
  • 33