fastify / help

Need help with Fastify? File an Issue here.
https://www.fastify.io/
65 stars 8 forks source link

fastify-passport calls local route instead of Google /o/oauth2/v2/auth #1040

Closed clubside closed 3 months ago

clubside commented 3 months ago

💬 Question here

Howdy! I had everything working great in my localhost setup. Unfortunately when I moved to testing using a public-facing URL attempting to login went to my site rather than Google's causing a 404. I am hosting this dev site as https://swift.saisquared.dev/ which uses IIS to reverse proxy to the original localhost environment http://localhost:3940. Because of this setup you can view the site on the server using either address, but only the latter actually lets you log in.

const app = fastify({
    logger: {
        level: 'debug',
        transport: {
            target: '@mgcrea/pino-pretty-compact',
            options: { translateTime: 'SYS:mm/dd/yy HH:MM:ss', ignore: 'pid,hostname' }
        }
    },
    disableRequestLogging: true
})

app.register(async function (app) {
    app.register(fastifyRequestLogger)
    app.register(fastifySecureSession, {
        key: fs.readFileSync(path.join(__dirname, 'secret-key')),
        cookie: {
            path: '/'
        }
    })
    app.register(fastifyPassport.initialize())
    app.register(fastifyPassport.secureSession())
    app.register(fastifyFormBody, {
        bodyLimit: 104857600000000
    })

    fastifyPassport.registerUserSerializer(
        async (user, request) => {
            console.log('registerUserSerializer', { user }, { json: user._json })
            const userJson = user._json
            const { id, displayName } = user
            /** @type {LoginWeb} */
            const loginData = {
                email: userJson.email,
                name: userJson.name,
                avatar: userJson.picture
            }
            /** @type {LoginWebResult} */
            const loginWebResult = await loginWeb(loginData)
            // User object sent it from Google.
            const userForSession = { id, displayName, email: userJson.email, role: loginWebResult.userRights.role }
            return userForSession
        }
    )

    fastifyPassport.registerUserDeserializer(async (userFromSession, request) => {
        console.log('registerUserDeserializer', { userFromSession })
        return userFromSession
    })

    fastifyPassport.use('google', new GoogleStrategy.OAuth2Strategy({
        clientID: process.env.AUTH0_CLIENT_ID,
        clientSecret: process.env.AUTH0_CLIENT_SECRET,
        callbackURL: 'https://lswift.saisquared.dev/api/auth/google/callback'
    }, function (accessToken, refreshToken, profile, cb) {
        // console.log({ accessToken, refreshToken, profile })
        return cb(null, profile)
    }
    ))

    app.setNotFoundHandler(async (request, reply) => {
        // console.log({ setNotFoundHandler: request })
        returnNotFound(request, reply)
    })

    app.get('/login',
        {
          preValidation: fastifyPassport.authenticate('google', { scope: ['profile', 'email'] })
        },
        async () => {
            console.log('GOOGLE API forward')
        }
    )

    app.get('/api/auth/google/callback',
        {
          preValidation: fastifyPassport.authenticate('google', { scope: ['profile', 'email'] })
        },
        function (request, reply) {
            reply.redirect('/profile')
        }
    )

    app.get('/logout', (request, reply) => {
        request.session.delete()
        reply.redirect('/')
    })

    await adminPages(app)
    await profilePages(app)
    await standardPages(app)
    await clientAPIs(app)
})

// static file server
app.register(fastifyStatic, {
    root: path.join(__dirname, 'public')
})

app.listen({ port: 3940 }, (err, address) => {
    if (err) throw err
})

If you were to go to the live site now https://swift.saisquared.dev/ and click the Login button you would get my 404 page. In the console you would see Fastify's logger display:

07/13/24 00:14:18  info: #req-s ←GET:/login request from ip ::1 (fastify-request-logger)
07/13/24 00:14:18  info: #req-s →GET:/login response with a 302-status took 4.166ms (fastify-request-logger)
07/13/24 00:14:18  warn: #req-t →GET:/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fswift.saisquared.dev%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com response with a 404-status took 16.287ms (fastify-request-logger)

With this debug information:

{
  setNotFoundHandler: Request {
    id: 'req-t',
    params: { '*': 'o/oauth2/v2/auth' },
    raw: IncomingMessage {
      _events: [Object],
      _readableState: [ReadableState],
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      joinDuplicateHeaders: null,
      aborted: false,
      upgrade: false,
      url: '/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fswift.saisquared.dev%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com',
      method: 'GET',
      statusCode: null,
      statusMessage: null,
      client: [Socket],
      _consuming: false,
      _dumped: false,
      [Symbol(shapeMode)]: true,
      [Symbol(kCapture)]: false,
      [Symbol(kHeaders)]: [Object],
      [Symbol(kHeadersCount)]: 42,
      [Symbol(kTrailers)]: null,
      [Symbol(kTrailersCount)]: 0
    },
    query: Empty <[Object: null prototype] {}> {
      response_type: 'code',
      redirect_uri: 'https://swift.saisquared.dev/api/auth/google/callback',
      scope: 'profile email',
      client_id: '294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com'
    },
    log: EventEmitter {
      trace: [Function: noop],
      debug: [Function: LOG],
      info: [Function: LOG],
      warn: [Function: LOG],
      error: [Function: LOG],
      fatal: [Function (anonymous)],
      [Symbol(pino.serializers)]: [Object],
      [Symbol(pino.formatters)]: [Object],
      [Symbol(pino.chindings)]: ',"pid":12824,"hostname":"CLUBDEVSRV","reqId":"req-t"',
      [Symbol(pino.levelVal)]: 20,
      [Symbol(fastify.disableRequestLogging)]: true
    },
    body: undefined,
    [Symbol(fastify.context)]: {
      schema: undefined,
      handler: [Function: bound ] AsyncFunction,
      Reply: [Function],
      Request: [Function],
      contentTypeParser: [ContentTypeParser],
      onRequest: [Array],
      onSend: null,
      onError: null,
      onTimeout: null,
      preHandler: [Array],
      onResponse: [Array],
      preSerialization: null,
      onRequestAbort: null,
      config: {},
      errorHandler: [Object],
      requestIdLogLabel: 'reqId',
      childLoggerFactory: [Function: defaultChildLoggerFactory],
      _middie: null,
      _parserOptions: [Object],
      exposeHeadRoute: undefined,
      prefixTrailingSlash: undefined,
      logLevel: '',
      logSerializers: undefined,
      attachValidation: undefined,
      schemaErrorFormatter: [Function: defaultSchemaErrorFormatter],
      validatorCompiler: null,
      serializerCompiler: null,
      server: [Object],
      preParsing: null,
      preValidation: [Array],
      [Symbol(fastify.404ContextKey)]: null,
      [Symbol(fastify.replySerializerDefault)]: undefined,
      [Symbol(fastify.routeByFastify)]: undefined,
      [Symbol(fastify.request.cache.validateFns)]: null,
      [Symbol(fastify.reply.cache.serializeFns)]: null,
      [Symbol(fastify.routeOptions)]: [Object: null prototype]
    },
    [Symbol(fastify.RequestPayloadStream)]: IncomingMessage {
      _events: [Object],
      _readableState: [ReadableState],
      _maxListeners: undefined,
      socket: [Socket],
      httpVersionMajor: 1,
      httpVersionMinor: 1,
      httpVersion: '1.1',
      complete: true,
      rawHeaders: [Array],
      rawTrailers: [],
      joinDuplicateHeaders: null,
      aborted: false,
      upgrade: false,
      url: '/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fswift.saisquared.dev%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com',
      method: 'GET',
      statusCode: null,
      statusMessage: null,
      client: [Socket],
      _consuming: false,
      _dumped: false,
      [Symbol(shapeMode)]: true,
      [Symbol(kCapture)]: false,
      [Symbol(kHeaders)]: [Object],
      [Symbol(kHeadersCount)]: 42,
      [Symbol(kTrailers)]: null,
      [Symbol(kTrailersCount)]: 0
    }
  }
}

However if I'm on that server and go to http://localhost:3940 (which serves up the identical site) and click the Login button it goes ahead to Google to choose an account and the Fastify log shows:

07/13/24 00:14:46  info: #req-13 ←GET:/login request from ip ::1 (fastify-request-logger)
07/13/24 00:14:46  info: #req-13 →GET:/login response with a 302-status took 1.635ms (fastify-request-logger)

So you can see when someone is at https://swift.saisquared.dev/ and Login is clicked the URL changes to

https://swift.saisquared.dev/o/oauth2/v2/auth?response_type=code&redirect_uri=https%3A%2F%2Fswift.saisquared.dev%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com

rather than the URL http://localhost:3940 changes to

https://accounts.google.com/o/oauth2/v2/auth/oauthchooseaccount?response_type=code&redirect_uri=https%3A%2F%2Fswift.saisquared.dev%2Fapi%2Fauth%2Fgoogle%2Fcallback&scope=profile%20email&client_id=294398145538-hm97gsnslbpdi47or8csqlaoic51dcrt.apps.googleusercontent.com&service=lso&o2v=2&ddm=0&flowName=GeneralOAuthFlow

Somehow, be it the reverse proxy or some other thing happening in the middle Passport is choosing to call my server for the Google login page rather than Google as it should.

I did a lot of searching and could not find a similar problem but given all the variables it seems like there would be a simple solution. Would really appreciate some help as the next step is to deploy to the real website.

P.S. I went so far as trying to do this with @fastify/oauth2 before asking for help and it produces the same result, everything works access the site using localhost but not with swift.saisquared.dev.

Your Environment

clubside commented 3 months ago

Sorry for the trouble, it was my fault related to outbound redirects from IIS, just needed to change one option on the server and everything is working.