Skip to content

Add support for fastify-passport package #1655

Open
@Niewdanka

Description

@Niewdanka

Is there an existing issue that is already proposing this?

  • I have searched the existing issues

Is your feature request related to a problem? Please describe it

Hi! Based on this thread fastify/help#382 it seems that fastify-passport package is stable and can help resolve the problem related to this issue nestjs/nest#5702 . While the workaround mentioned in issue works i believe this package could also help resolve the problem.

Describe the solution you'd like

Add support for fastify-passport package.

Teachability, documentation, adoption, migration strategy

none

What is the motivation / use case for changing the behavior?

none

Activity

transferred this issue fromnestjs/neston May 21, 2024
olawalejuwonm

olawalejuwonm commented on Jun 21, 2024

@olawalejuwonm

Any update on this please?

d6nn9

d6nn9 commented on Sep 27, 2024

@d6nn9
app.getHttpAdapter().getInstance().decorateReply('setHeader', function (key, value) { this.raw.setHeader(key, value); });
app.getHttpAdapter().getInstance().decorateReply('end', function () { this.raw.end(); });
dfenerski

dfenerski commented on Sep 28, 2024

@dfenerski
app.getHttpAdapter().getInstance().decorateReply('setHeader', function (key, value) { this.raw.setHeader(key, value); });
app.getHttpAdapter().getInstance().decorateReply('end', function () { this.raw.end(); });

The key issue is native support for passport's guards & strategies.

songkeys

songkeys commented on Sep 29, 2024

@songkeys

I'd like to help work on this.

But I found that:

  1. fastify-passport is a direct port from passport
  2. passport is a peer dependency of @nest/passport and the api was used in the code

How do you think we should bring fastify-passport in without interupting express's passport? Maybe we should make them optional and let user to pass in the passport instance?

SzymonGonet

SzymonGonet commented on Oct 29, 2024

@SzymonGonet
When do we know when this has been done?

Once `fastify-passport` becomes stable, we'll add support for it in the `@nestjs/passport` package.

Originally posted by @kamilmysliwiec in #5702 (comment)

jadejr

jadejr commented on Jan 24, 2025

@jadejr

stable by what metric? They are currently at 3.x.

I was able to get the tests to pass on initial release of nestjs 11 via this hacked up patch. The createPassportContext bit is particularly gnarly and could likely be greatly simplified and the e2e tests probably shouldn't be done this way, but rather be split out. I just don't know yet how to do that.

EDIT: I also realized i'm calling the request decorated method for authenticate but the global one could probably be used instead.

diff --git a/lib/auth.guard.ts b/lib/auth.guard.ts
index 4a7dcc1..054e7d9 100644
--- a/lib/auth.guard.ts
+++ b/lib/auth.guard.ts
@@ -9,7 +9,7 @@ import {
   Optional,
   UnauthorizedException
 } from '@nestjs/common';
-import * as passport from 'passport';
+import passport from '@fastify/passport';
 import { Type } from './interfaces';
 import {
   AuthModuleOptions,
@@ -87,11 +87,7 @@ function createAuthGuard(type?: string | string[]): Type<IAuthGuard> {
       request: TRequest
     ): Promise<void> {
       const user = request[this.options.property || defaultOptions.property];
-      await new Promise<void>((resolve, reject) =>
-        request.logIn(user, this.options, (err) =>
-          err ? reject(err) : resolve()
-        )
-      );
+      await request.logIn(user, this.options);
     }
 
     handleRequest(err, user, info, context, status): TUser {
@@ -113,14 +109,22 @@ function createAuthGuard(type?: string | string[]): Type<IAuthGuard> {
 
 const createPassportContext =
   (request: any, response: any) =>
-  (type: string | string[], options: any, callback: Function) =>
-    new Promise<void>((resolve, reject) =>
-      passport.authenticate(type, options, (err, user, info, status) => {
-        try {
-          request.authInfo = info;
-          return resolve(callback(err, user, info, status));
-        } catch (err) {
-          reject(err);
-        }
-      })(request, response, (err: any) => (err ? reject(err) : resolve()))
-    );
+  async (type: string | string[], options: any, callback: Function) =>
+    new Promise((resolve, reject) => {
+      try {
+        return request.passport.authenticate(
+          type,
+          options,
+          (request, _response, err, user, info, status) => {
+            try {
+              request.authInfo = info;
+              return resolve(callback(err, user, info, status));
+            } catch (err) {
+              reject(err);
+            }
+          }
+        )(request, response);
+      } catch (error) {
+        reject(error);
+      }
+    });
diff --git a/lib/index.ts b/lib/index.ts
index 66b75aa..e56a630 100644
--- a/lib/index.ts
+++ b/lib/index.ts
@@ -3,4 +3,5 @@ export * from './auth.guard';
 export * from './interfaces';
 export * from './passport.module';
 export * from './passport/passport.serializer';
+export * from './passport/fastify-passport.serializer';
 export * from './passport/passport.strategy';
diff --git a/lib/passport/fastify-passport.serializer.ts b/lib/passport/fastify-passport.serializer.ts
new file mode 100644
index 0000000..443567f
--- /dev/null
+++ b/lib/passport/fastify-passport.serializer.ts
@@ -0,0 +1,30 @@
+import passport from '@fastify/passport';
+import type { FastifyRequest } from 'fastify';
+
+export abstract class FastifyPassportSerializer {
+  abstract userSerializer(user: unknown, request: FastifyRequest): Promise<any>;
+  abstract userDeserializer(
+    payload: unknown,
+    request: FastifyRequest
+  ): Promise<any>;
+
+  constructor() {
+    const passportInstance = this.getPassportInstance();
+
+    passportInstance.registerUserSerializer(
+      async (user: Record<string, unknown>, request: FastifyRequest) => {
+        return await this.userSerializer(user, request);
+      }
+    );
+    passportInstance.registerUserDeserializer(
+      async (payload: string, request: FastifyRequest) => {
+        const result = this.userDeserializer(payload, request);
+        return result;
+      }
+    );
+  }
+
+  getPassportInstance() {
+    return passport;
+  }
+}
diff --git a/lib/passport/passport.strategy.ts b/lib/passport/passport.strategy.ts
index 8541e51..8064761 100644
--- a/lib/passport/passport.strategy.ts
+++ b/lib/passport/passport.strategy.ts
@@ -1,4 +1,4 @@
-import * as passport from 'passport';
+import passport from '@fastify/passport';
 import { Type, WithoutCallback } from '../interfaces';
 
 export type AllConstructorParameters<T> = T extends {
diff --git a/package.json b/package.json
index 5311361..170aa9f 100644
--- a/package.json
+++ b/package.json
@@ -23,6 +23,7 @@
   ],
   "peerDependencies": {
     "@nestjs/common": "^10.0.0 || ^11.0.0",
+    "@fastify/passport": "^3.0.2",
     "passport": "^0.5.0 || ^0.6.0 || ^0.7.0"
   },
   "devDependencies": {
@@ -30,10 +31,13 @@
     "@commitlint/config-angular": "19.7.0",
     "@eslint/eslintrc": "3.2.0",
     "@eslint/js": "9.18.0",
+    "@fastify/cookie": "^11.0.1",
+    "@fastify/session": "^11.0.0",
     "@nestjs/common": "11.0.3",
     "@nestjs/core": "11.0.3",
     "@nestjs/jwt": "11.0.0",
     "@nestjs/platform-express": "11.0.3",
+    "@nestjs/platform-fastify": "11.0.3",
     "@nestjs/testing": "11.0.3",
     "@types/jest": "29.5.14",
     "@types/node": "22.10.7",
@@ -43,6 +47,7 @@
     "eslint": "9.18.0",
     "eslint-config-prettier": "10.0.1",
     "eslint-plugin-prettier": "5.2.3",
+    "fastify": "^5.0.0",
     "globals": "15.14.0",
     "husky": "9.1.7",
     "jest": "29.7.0",
@@ -68,4 +73,4 @@
     "type": "git",
     "url": "https://github.com/nestjs/passport"
   }
-}
+}
\ No newline at end of file
diff --git a/test/common/app.e2e-spec.ts b/test/common/app.e2e-spec.ts
index 7dd2e5f..0f29b4f 100644
--- a/test/common/app.e2e-spec.ts
+++ b/test/common/app.e2e-spec.ts
@@ -1,4 +1,11 @@
+import fastifyCookie from '@fastify/cookie';
+import fastifySession from '@fastify/session';
+import fastifyPassport from '@fastify/passport';
 import { INestApplication } from '@nestjs/common';
+import {
+  FastifyAdapter,
+  NestFastifyApplication
+} from '@nestjs/platform-fastify';
 import { Test } from '@nestjs/testing';
 import { spec, request } from 'pactum';
 import { AppModule as WithRegisterModule } from '../with-register/app.module';
@@ -9,13 +16,23 @@ describe.each`
   ${WithRegisterModule}    | ${'with'}
   ${WithoutRegisterModule} | ${'without'}
 `('Passport Module $RegisterUse register()', ({ AppModule }) => {
-  let app: INestApplication;
+  let app: NestFastifyApplication;
 
   beforeAll(async () => {
     const modRef = await Test.createTestingModule({
       imports: [AppModule]
     }).compile();
-    app = modRef.createNestApplication();
+    app = modRef.createNestApplication<NestFastifyApplication>(
+      new FastifyAdapter()
+    );
+    const fastifyInstance = app.getHttpAdapter().getInstance();
+    await fastifyInstance.register(fastifyCookie);
+    await fastifyInstance.register(fastifySession, {
+      secret: 'xxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxxs3cr3t'
+    });
+    await fastifyInstance.register(fastifyPassport.initialize());
+    await fastifyInstance.register(fastifyPassport.secureSession());
+
     await app.listen(0);
     const url = (await app.getUrl()).replace('[::1]', 'localhost');
     request.setBaseUrl(url);
jhrncar

jhrncar commented on Feb 3, 2025

@jhrncar

I also would like to use Fastify, Nest.js and Passport together in my application, so really looking forward to this!

mainfraame

mainfraame commented on Feb 6, 2025

@mainfraame

i’ve used passport with fastify in nestjs before with saml2 and oauth2 strategies. @d6nn9 is correct in that a temporary workaround is possible by decorating mostly) the reply object with express methods.

mrsimonemms

mrsimonemms commented on Feb 26, 2025

@mrsimonemms

app.getHttpAdapter().getInstance().decorateReply('setHeader', function (key, value) { this.raw.setHeader(key, value); });
app.getHttpAdapter().getInstance().decorateReply('end', function () { this.raw.end(); });

The original workaround doesn't work with fastify-secure-session if you need to persist session data to the callback URL as the onSend hook isn't triggered. Replacing this.raw.end() with this.send() seems to fix that.

Forceres

Forceres commented on May 23, 2025

@Forceres

Any updates?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Assignees

No one assigned

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

      Development

      No branches or pull requests

        Participants

        @mrsimonemms@mainfraame@songkeys@Niewdanka@dfenerski

        Issue actions

          Add support for fastify-passport package · Issue #1655 · nestjs/passport