Hiago Prata
Hiago Prata

Reputation: 117

Facebook authentication strategy with Passport.js, Express and TypeScript

I'm trying to set up the Facebook authentication strategy on my app using Passport.js, Express and TypeScript. I could understand the dataflow of the process thanks to this article from Hacker Noon.

But when it comes to the verification callback function, things get a little troublesome. I need to check whether the user is already logged in, thus the access to the Request object is necessary. I've checked in the passport-facebook module docs that passReqToCallback: true can be set on the strategy options to enable this.

However when I pass the req parameter to the callback function, the compiler throws the following error:

Argument of type '(req: Request, accessToken: string, _refreshToken: string, profile: Profile, done: any) => void' is not assignable to parameter of type 'VerifyFunction'.

Looking around the type definitions of the Passport.js module I found this:

export type VerifyFunction =
    (accessToken: string, refreshToken: string, profile: Profile, done: (error: any, user?: any, info?: any) => void) => void;

export type VerifyFunctionWithRequest =
    (req: express.Request, accessToken: string, refreshToken: string, profile: Profile, done: (error: any, user?: any, info?: any) => void) => void;

export class Strategy implements passport.Strategy {
    constructor(options: StrategyOptionWithRequest, verify: VerifyFunctionWithRequest);
    constructor(options: StrategyOption, verify: VerifyFunction);

    name: string;
    authenticate(req: express.Request, options?: object): void;
}

So, in theory, the declaration

new Strategy(fbConfig, (req: Request, accessToken: string, _refreshToken: string, profile: Profile, done: any) => { ... });

should be accepted with no problems.

Here is the full fbConfig declaration:

const fbConfig = {
  clientID: "",
  clientSecret: "",
  callbackURL: "",
  passReqToCallback: true,
  profileFields: [
    "id",
    "name",
    "birhday",
    "gender",
    "email",
    "location",
    "hometown"
  ]
};

And my tsconfig.json:

{
  "compilerOptions": {
    "target": "es6",
    "module": "commonjs",
    "strict": true,
    "noImplicitAny": true,
    "esModuleInterop": true,
    "baseUrl": ".",
    "outDir": "dist",
    "paths": {
      "@models/*": ["./src/models/*"],
      "@configs/*": ["./src/configs/*"],
      "@controllers/*": ["./src/controllers/*"]
    }
  },
  "include": ["src/**/*"],
  "exclude": ["node_modules"]
}

If anyone could help me out with this, I'd appreciate it pretty much!

Upvotes: 0

Views: 2346

Answers (2)

Hiago Prata
Hiago Prata

Reputation: 117

I've tried what Shanon Jackson recommended, but it didn't work. The compiler couldn't recognize the overloaded constructor for the Strategy class.

So what I did was:

new Strategy(
  fbConfig as StrategyOptionWithRequest, 
  (
    req: Request,
    accessToken: string,
    _refreshToken: string,
    profile: Profile,
    done
  ) => { ... }
);

I suppose that casting the fbConfig object to StrategyOptionWithRequest forced the compiler to use the constructor that expected that interface. Then I annotated the types of the callback function parameters, but left done to the compiler inference system to deal with. Annotating it to any seemed to mess a bit with VSCode's IntelliSense system, making it do not display done's expected parameters.

Upvotes: 1

Shanon Jackson
Shanon Jackson

Reputation: 6531

Typescript actually has a NodeJS starter kit with Facebook OAuth completely setup so people can observe how it's implemented here:

https://github.com/microsoft/TypeScript-Node-Starter And you can find their passport implemented with Facebook here: https://github.com/microsoft/TypeScript-Node-Starter/blob/master/src/config/passport.ts

It Looks like they are casting "Request" to any i suspect it's because of conflicting definitions of Request between express/passport/Typescript standard library but can't know for sure

Upvotes: 0

Related Questions