fastify / fastify-passport

Use passport strategies for authentication within a fastify application
MIT License
246 stars 44 forks source link

Infinite authentication loop on protected route #1142

Open HenriqueMentormate opened 2 months ago

HenriqueMentormate commented 2 months ago

Prerequisites

Fastify version

4.27.0

Plugin version

2.4.0

Node.js version

20.11.1

Operating system

macOS

Operating system version (i.e. 20.04, 11.3, 10)

14.4.1

Description

Hello. I am trying to use passport-microsoft and my issue is that after I have been authenticated and the callback URL has been reached and I am redirected to the protected route '/', I get prompted to log in again, this happens over and over again.

Here's an MRE:

"use strict";

const { readFileSync } = require("fs");
const { join } = require("path");

const fastify = require("fastify")({ logger: true });

const passport = require("@fastify/passport");
const secureSession = require("@fastify/secure-session");
const { Strategy: MicrosoftStrategy } = require("passport-microsoft");

const PORT = 3000;
const BASE_URL = "http://localhost";
const CLIENT_ID = "...";
const CLIENT_SECRET = "...";
const CALLBACK = `${BASE_URL}:${PORT}/microsoft/callback`;

// --- plugins (from the Fastify ecosystem) ---
fastify.register(secureSession, {
  key: readFileSync(join(__dirname, "secret-key")),
});
fastify.register(passport.initialize());
fastify.register(passport.secureSession());

passport.use(
  "microsoft",
  new MicrosoftStrategy(
    {
      clientID: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
      callbackURL: CALLBACK,
      scope: ["user.read"],
    },
    function (accessToken, refreshToken, profile, done) {
      done(null, profile);
    }
  )
);
passport.registerUserSerializer(async (user) => user.id);
passport.registerUserDeserializer(async (user) => user);

// --- routes                               ---
const defRoutes = [
  {
    method: "GET",
    url: `/auth/login`,
    preValidation: passport.authenticate("microsoft", {
      authInfo: false,
    }),
    handler: (req, res, err, user, status) => {},
  },
  {
    method: "GET",
    url: `/microsoft/callback`,
    preValidation: passport.authenticate("microsoft", {
      authInfo: false,
      successRedirect: "/",
    }),
    handler: (req, res) => {},
  },
  {
    method: "GET",
    url: `/`,
    preValidation: passport.authenticate("microsoft", { authInfo: false }),
    handler: (req, res) => {
      return res.send(req.user);
    },
  },
];

// Add all routes into Fastify route system
for (const route of defRoutes) {
  fastify.route(route);
}

async function start() {
  try {
    fastify.listen({ port: PORT });
  } catch (e) {
    throw e;
  }
}

start();

I am not sure if I am missing anything or what the issue is.

Link to code that reproduces the bug

No response

Expected Behavior

I am not prompted to log in again when redirected to the '/' route

mcollina commented 2 months ago

Thanks, I've never used passport-microsoft, so I can't really help here.

HenriqueMentormate commented 2 months ago

Something similar happens with passport-google-oauth20, so it does not seem to be a passport-microsoft only

MRE:

"use strict";

const { readFileSync } = require("fs");
const { join } = require("path");

const fastify = require("fastify")({ logger: true });

const passport = require("@fastify/passport");
const secureSession = require("@fastify/secure-session");
const { Strategy: GoogleStrategy } = require("passport-google-oauth20");

const PORT = 3000;
const BASE_URL = "http://localhost";
const CLIENT_ID = "...";
const CLIENT_SECRET = "...";
const CALLBACK = `${BASE_URL}:${PORT}/google/callback`;

// --- plugins (from the Fastify ecosystem) ---
fastify.register(secureSession, {
  key: readFileSync(join(__dirname, "secret-key")),
});
fastify.register(passport.initialize());
fastify.register(passport.secureSession());

passport.use(
  "google",
  new GoogleStrategy(
    {
      clientID: CLIENT_ID,
      clientSecret: CLIENT_SECRET,
      callbackURL: CALLBACK,
      scope: ['email', 'profile'],
    },
    function (accessToken, refreshToken, profile, done) {
      done(null, profile);
    }
  )
);
passport.registerUserSerializer(async (user) => user.id);
passport.registerUserDeserializer(async (user) => user);

// --- routes                               ---
const defRoutes = [
  {
    method: "GET",
    url: `/auth/login`,
    preValidation: passport.authenticate("google", {
      authInfo: false,
    }),
    handler: (req, res, err, user, status) => {},
  },
  {
    method: "GET",
    url: `/google/callback`,
    preValidation: passport.authenticate("google", {
      authInfo: false,
      successRedirect: "/",
    }),
    handler: (req, res) => {},
  },
  {
    method: "GET",
    url: `/`,
    preValidation: passport.authenticate("google", { authInfo: false }),
    handler: (req, res) => {
      return res.send(req.user);
    },
  },
];

// Add all routes into Fastify route system
for (const route of defRoutes) {
  fastify.route(route);
}

async function start() {
  try {
    fastify.listen({ port: PORT });
  } catch (e) {
    throw e;
  }
}

start();
linuswillner commented 2 months ago

I'm having this same issue with passport-discord-auth as well. If I decorate any route with { preValidation: passport.authenticate('discord') }, it just tosses me back to Discord to authenticate again even though I was just there. In my callback route, I can redirect to some route, and Fastify tells me the browser asks for that route, but then Passport just sends the browser back into authentication again.