Open Sn0wye opened 4 months ago
I'm implementing it myself, and if you want the code, I will share it soon :D
Yeah, we should probably start off by adding one in "get started" and "validate session cookies"
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'
}
);
//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;
}
}
// app.ts
export const app = fastify();
app.register(csrfPlugin, {
enabled: env.NODE_ENV === 'production'
});
app.register(authPlugin);
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!
Thank you @Sn0wye , I will try it soon.
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!
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
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 missingsend()
in case of Forbidden errorAdding the
done()
in the else blockif ( !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
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)
?
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.