Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[Feature Request]: Fastify guide? #1406

Open
Sn0wye opened this issue Feb 4, 2024 · 11 comments
Open

[Feature Request]: Fastify guide? #1406

Sn0wye opened this issue Feb 4, 2024 · 11 comments
Labels
feature request New feature requests

Comments

@Sn0wye
Copy link

Sn0wye commented Feb 4, 2024

Package

lucia

Description

Fastify guide?

Seen a lot of other frameworks, and Fastify, as it is one of the most popular alternatives of express should have a guide.

@Sn0wye Sn0wye added the feature request New feature requests label Feb 4, 2024
@Sn0wye
Copy link
Author

Sn0wye commented Feb 4, 2024

I'm implementing it myself, and if you want the code, I will share it soon :D

@pilcrowOnPaper
Copy link
Member

Yeah, we should probably start off by adding one in "get started" and "validate session cookies"

@Sn0wye
Copy link
Author

Sn0wye commented Feb 13, 2024

Hey, apologies for the delay, but here's what I've done!

// plugins/csrf.ts
import { fastifyPlugin } from 'fastify-plugin';
import { verifyRequestOrigin } from 'lucia';
import { type App } from '@/app';

type Options = {
  enabled: boolean;
};

export const csrfPlugin = fastifyPlugin(
  async (app: App, opts: Options) => {
    if (!opts.enabled) {
      return;
    }

    app.addHook('preHandler', (req, res, done) => {
      if (req.method === 'GET') {
        return done();
      }

      const originHeader = req.headers.origin ?? null;
      // NOTE: You may need to use `X-Forwarded-Host` instead
      const hostHeader = req.headers.host ?? null;
      if (
        !originHeader ||
        !hostHeader ||
        !verifyRequestOrigin(originHeader, [hostHeader])
      ) {
        // TODO: verify this
        console.error('Invalid origin', { originHeader, hostHeader });
        return res.status(403);
      }
    });
  },
  {
    name: 'csrf',
    fastify: '4.x'
  }
);

@Sn0wye
Copy link
Author

Sn0wye commented Feb 13, 2024

//plugins/auth.ts
import { fastifyPlugin } from 'fastify-plugin';
import { type Session, type User } from 'lucia';
import { type App } from '@/app';
import { auth } from '@/auth/lucia';

export const authPlugin = fastifyPlugin(
  async (app: App) => {
    app.addHook('preHandler', async (req, res) => {
      const sessionId = auth.readSessionCookie(req.headers.cookie ?? '');

      if (!sessionId) {
        req.user = null;
        req.session = null;
        return;
      }

      const { session, user } = await auth.validateSession(sessionId);
      if (session && session.fresh) {
        const cookie = auth.createSessionCookie(session.id);
        res.setCookie(cookie.name, cookie.value, cookie.attributes);
      }

      if (!session) {
        const cookie = auth.createBlankSessionCookie();
        res.setCookie(cookie.name, cookie.value, cookie.attributes);
      }

      req.user = user;
      req.session = session;
      return;
    });
  },
  {
    name: 'auth',
    fastify: '4.x'
  }
);

declare module 'fastify' {
  interface FastifyRequest {
    user: User | null;
    session: Session | null;
  }
}

@Sn0wye
Copy link
Author

Sn0wye commented Feb 13, 2024

// app.ts

export const app = fastify();

app.register(csrfPlugin, {
  enabled: env.NODE_ENV === 'production'
});
app.register(authPlugin);

@Sn0wye
Copy link
Author

Sn0wye commented Feb 13, 2024

Side note: the code might have some things that are not needed, and I took it from my own application, but the core remains the same, feel free to simplify it!

@StepanMynarik
Copy link

StepanMynarik commented Mar 27, 2024

Thank you @Sn0wye , I will try it soon.

@Sn0wye
Copy link
Author

Sn0wye commented Mar 27, 2024

Thank you @Sn0wye , I will try it soon.

Thanks, but the csrf is not working, if you manage to do so, please share what did I miss!

@heethjain21
Copy link

I implemented this a few days ago, and wasn't able to check until today. Had assumed the code was correct, but found issues today along with your comment of the error.

I have a slightly different version of auth.plugin, so that works but csrf has a very minor missing code:

There is a missing done() once the csrf validation if correct. Because of this, the API call doesn't moves forward and is also stuck without throwing any error too
Along with this, there is also a missing send() in case of Forbidden error

Adding the done() in the else block

if (
    !originHeader ||
    !hostHeader ||
    !verifyRequestOrigin(originHeader, [hostHeader])
) {
      console.error('Invalid origin', { originHeader, hostHeader })
      return res.status(403).send({ message: 'Forbidden' })
} else {
      done()
}

@Sn0wye this should fix the issue here

@Sn0wye
Copy link
Author

Sn0wye commented Mar 29, 2024

I implemented this a few days ago, and wasn't able to check until today. Had assumed the code was correct, but found issues today along with your comment of the error.

I have a slightly different version of auth.plugin, so that works but csrf has a very minor missing code:

There is a missing done() once the csrf validation if correct. Because of this, the API call doesn't moves forward and is also stuck without throwing any error too
Along with this, there is also a missing send() in case of Forbidden error

Adding the done() in the else block

if (
    !originHeader ||
    !hostHeader ||
    !verifyRequestOrigin(originHeader, [hostHeader])
) {
      console.error('Invalid origin', { originHeader, hostHeader })
      return res.status(403).send({ message: 'Forbidden' })
} else {
      done()
}

@Sn0wye this should fix the issue here

nice catch! but here the plugin was running fine, since it has two definitions, one using the done, and the other being async (no need for done, just return). My problem was in production with the host and origin headers that never matched

@heethjain21
Copy link

My problem was in production with the host and origin headers that never matched

Was it related to not calling the send() on returning res.status(403) ?

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
feature request New feature requests
Projects
None yet
Development

No branches or pull requests

4 participants