nestjs / nest

A progressive Node.js framework for building efficient, scalable, and enterprise-grade server-side applications with TypeScript/JavaScript 🚀
https://nestjs.com
MIT License
67.04k stars 7.56k forks source link

Possible to access underlying express instance from within application context? #276

Closed viglucci closed 6 years ago

viglucci commented 6 years ago

I'm submitting a question / documentation / education request...


[ ] Regression 
[ ] Bug report
[ ] Feature request
[x] Documentation issue or request
[ ] Support request => Please do not submit support request here, instead post your question on Stack Overflow.

Current behavior

When integrating with authentication libraries such as passport there are setup middlewares that must first be initialized and registered with the underlying express instance, however, there doesn't seem to be a clean way to access the express instance once NestFactory.create(ApplicationModule, ...) is called. To overcome this, you are able to pass an instance of express to NestFactory.create(ApplicationModule, app), but from a code structure perspective, this is not ideal as your application configuration logic is now distributed between inside and outside of the nestjs application context.

import { NestFactory } from "@nestjs/core";
import { ApplicationModule } from "./modules/app.module";
import * as express from "express";
import * as passport from "passport";
import * as cookieParser from "cookie-parser";
import * as session from "express-session";

async function bootstrap() {
    const app = express();
    app.use(cookieParser());
    app.use(session({
        secret: "12345",
        saveUninitialized: false,
        resave: true
    }));
    app.use(passport.initialize());
    app.use(passport.session());
    const server = await NestFactory.create(ApplicationModule, app);
    await server.listen(3000);
}

bootstrap();

Expected behavior

I would expect to be able to register a module or configuration component that would be able to access the underlying express application so that I can apply additional middlewares, such as passport.initialize().

import { Module, NestModule, MiddlewaresConsumer, RequestMethod, INestApplication } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AuthModule } from "./auth/auth.module";
import { OAuthModule } from "./oauth/oauth.module";
import { RequestLoggerMiddleware } from "../middleware/request-logger.middleware";
import * as passport from "passport";

@Module({
    modules: [AuthModule, OAuthModule],
    controllers: [AppController],
    components: [],
})
export class ApplicationModule implements NestModule {

    configure(consumer: MiddlewaresConsumer, app: INestApplication): void {

       // where does this logic belong if not here? placing in bootstrap() feels wrong.
        app.use(cookieParser());
        app.use(session({
            secret: "12345",
            saveUninitialized: false,
            resave: true
        }));
        app.use(passport.initialize());
        app.use(passport.session());

        consumer.apply(RequestLoggerMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
    }
} 

Environment

Additional Information

If this is already possible I would love to know what the best way to accomplish this is. I would also be happy to submit a PR against the examples to include an application that uses passport to integrate with social platforms such as Facebook, Github, and or Blizzard Battlenet, as the current passport recipe and example only cover JWT, which is likely not a common passport use case, and does not cover some of the more complex implementation details such as how to interact with the underlying express object.

Thanks for your time!

wbhob commented 6 years ago

Many others have had this question. See here: https://github.com/nestjs/nest/issues?q=is%3Aissue+passport+is%3Aclosed

wbhob commented 6 years ago

There's also a passport example in this repository.

viglucci commented 6 years ago

Hi @wbhob thanks for getting back to me, I'll take a look closer look at the existing issues that you linked to see if I can find what I'm looking for.

In reference to the existing passport example, I called that out in my OP and found it helpful for some parts of the integration, but it didn't cover my use case of needing to initialize passport and session with express.

...the current passport recipe and example only cover JWT, which is likely not a common passport use case, and does not cover some of the more complex implementation details such as how to interact with the underlying express object.

wbhob commented 6 years ago

My advice would be to put your initialization in its own middleware, and pass that in before your main passport middleware.

viglucci commented 6 years ago

In this context, are you referencing a middleware as it is defined by nest?

Creating the below nest middleware that returns passport.initialize() that would normally be passed to app.use(passport.initialize()), we can see that in the below screenshot that the middleware is initialized after the AuthModule, and I believe that is why I receive a passport.initialize() middleware is not in use error.

How can I ensure that the passport.initialize() is middleware applied before the modules passed to the @Module decorator?

image

import { Middleware, NestMiddleware, ExpressMiddleware } from "@nestjs/common";
import * as passport from "passport";

@Middleware()
export class PassportInitializationMiddleware implements NestMiddleware {

    resolve(...args: any[]): ExpressMiddleware {
        console.log("PassportInitializationMiddleware");
        return passport.initialize();
    }
}
import { Module, NestModule, MiddlewaresConsumer, RequestMethod } from "@nestjs/common";
import { AppController } from "./app.controller";
import { AuthModule } from "./auth/auth.module";
import { OAuthModule } from "./oauth/oauth.module";
import { RequestLoggerMiddleware } from "../middleware/RequestLogger.middleware";
import { PassportInitializationMiddleware } from "../middleware/PassportInitialization.middleware";
import * as passport from "passport";

@Module({
    modules: [AuthModule, OAuthModule],
    controllers: [AppController],
    components: [],
})
export class ApplicationModule implements NestModule {

    configure(consumer: MiddlewaresConsumer): void {
        consumer.apply(PassportInitializationMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
        consumer.apply(RequestLoggerMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
    }
}
Error: passport.initialize() middleware not in use
    at SessionStrategy.authenticate (E:\dev\nestjs-poc\node_modules\passport\lib\strategies\session.js:45:43)
    at attempt (E:\dev\nestjs-poc\node_modules\passport\lib\middleware\authenticate.js:361:16)
    at authenticate (E:\dev\nestjs-poc\node_modules\passport\lib\middleware\authenticate.js:362:7)
    at Layer.handle [as handle_request] (E:\dev\nestjs-poc\node_modules\express\lib\router\layer.js:95:5)
    at trim_prefix (E:\dev\nestjs-poc\node_modules\express\lib\router\index.js:317:13)
    at E:\dev\nestjs-poc\node_modules\express\lib\router\index.js:284:7
    at Function.process_params (E:\dev\nestjs-poc\node_modules\express\lib\router\index.js:335:12)
    at next (E:\dev\nestjs-poc\node_modules\express\lib\router\index.js:275:10)
    at session (E:\dev\nestjs-poc\node_modules\express-session\index.js:454:7)
    at Layer.handle [as handle_request] (E:\dev\nestjs-poc\node_modules\express\lib\router\layer.js:95:5)

Again, thanks for your time.

viglucci commented 6 years ago

Thanks for your help @wbhob. Based on your last suggestions about using middleware, I was able to get this working by moving all of the required middlewares to auth.module.ts and registering newly created middlewares for each express middleware that is required.

src/modules/auth/auth.module.ts

import * as passport from "passport";
import {
    Module,
    NestModule,
    MiddlewaresConsumer,
    RequestMethod,
} from "@nestjs/common";
import { BattlenetStrategy } from "./passport/strategy/battlenet.strategy";
import { AuthController } from "./auth.controller";
import { PassportInitializationMiddleware } from "../../middleware/PassportInitialization.middleware";
import { SessionInitializationMiddleware } from "../../middleware/SessionInitialization.middleware";
import { PassportSessionInitializationMiddleware } from "../../middleware/PassportSessionInitialization.middleware";

@Module({
    components: [BattlenetStrategy],
    controllers: [AuthController],
})
export class AuthModule implements NestModule {

    public configure(consumer: MiddlewaresConsumer) {
        consumer.apply(SessionInitializationMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
        consumer.apply(PassportInitializationMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
        consumer.apply(PassportSessionInitializationMiddleware).forRoutes({ path: "*", method: RequestMethod.ALL });
    }
}

src/middleware/PassportInitialization.middleware.ts

import { Middleware, NestMiddleware, ExpressMiddleware } from "@nestjs/common";
import * as passport from "passport";

@Middleware()
export class PassportInitializationMiddleware implements NestMiddleware {

    resolve(...args: any[]): ExpressMiddleware {
        console.log("PassportInitializationMiddleware initialized");
        return passport.initialize();
    }
}

src/middleware/PassportSessionInitialization.middleware.ts

import { Middleware, NestMiddleware, ExpressMiddleware } from "@nestjs/common";
import * as passport from "passport";
import * as session from "express-session";

@Middleware()
export class PassportSessionInitializationMiddleware implements NestMiddleware {

    resolve(...args: any[]): ExpressMiddleware {
        console.log("PassportSessionInitializationMiddleware initialized");
        return passport.session();
    }
}

src/middleware/SessionInitialization.middleware.ts

import { Middleware, NestMiddleware, ExpressMiddleware } from "@nestjs/common";
import * as passport from "passport";
import * as session from "express-session";

@Middleware()
export class SessionInitializationMiddleware implements NestMiddleware {

    resolve(...args: any[]): ExpressMiddleware {
        console.log("SessionInitializationMiddleware initialized");
        return session({
            secret: "12345",
            saveUninitialized: false,
            resave: true
        });
    }
}

src/server.ts

import { NestFactory } from "@nestjs/core";
import { ApplicationModule } from "./modules/app.module";
import * as express from "express";
import * as cookieParser from "cookie-parser";

function expressFactory () {
    const app = express();
    // TODO: move cookieParser() to middleware
    app.use(cookieParser());
    return app;
}

async function bootstrap() {
    const server = await NestFactory.create(ApplicationModule, expressFactory());
    await server.listen(3000);
}

bootstrap();
wbhob commented 6 years ago

Glad it worked out!

lock[bot] commented 5 years ago

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.