Using authentication
The tRPC-SvelteKit
package works with both cookie-based and JWT-based authentication.
Below is an example of how you could secure your tRPC routes with cookie-based authentication.
Let's assume you have a username/password-based login page that POSTs to the following action:
routes/login/+page.server.ts
import { JWT_SECRET } from '$env/static/private';
import prisma from '$lib/prisma';
import { fail } from '@sveltejs/kit';
import { md5 } from 'hash-wasm';
import jwt from 'jsonwebtoken';
import type { Actions } from './$types';
export const actions: Actions = {
default: async ({ request, cookies }) => {
try {
const data = await request.formData();
const email = data.get('email') as string;
const password = data.get('password') as string;
// 👇 replace this with a non-naiive hashing algorithm
const passwordHash = await md5(password);
const { id, name } = await prisma.user.findFirstOrThrow({
where: { email, passwordHash },
select: { id: true, name: true }
});
cookies.set('jwt', jwt.sign({ id, name }, JWT_SECRET), { path: '/', secure: false });
return { success: true };
// 👆 or, if we're using HTTP headers based auth, we could return the token,
// and let the client set the header on subsequent requests
} catch {
return fail(401, { error: 'Authentication failed' });
}
}
};
Your tRPC context builder could look like this:
lib/trpc/context.ts
import { JWT_SECRET } from '$env/static/private';
import type { RequestEvent } from '@sveltejs/kit';
import jwt from 'jsonwebtoken';
export async function createContext(event: RequestEvent) {
try {
const token = event.cookies.get('jwt');
// 👆 or, if we're using HTTP headers based authentication, we could do something like this:
// const token = event.request.headers.get('authorization')?.replace('Bearer ', '');
const { id: userId } = jwt.verify(token || '', JWT_SECRET) as { id: string };
return { userId };
} catch {
return { userId: '' };
}
}
export type Context = Awaited<ReturnType<typeof createContext>>;
You could define an authentication middleware like this:
lib/trpc/middleware/auth.ts
import { t } from '$lib/trpc/t';
import { TRPCError } from '@trpc/server';
export const auth = t.middleware(async ({ next, ctx }) => {
if (!ctx.userId) throw new TRPCError({ code: 'UNAUTHORIZED' });
return next();
});
And you could use the auth middleware in your tRPC procedured as needed:
lib/trpc/routes/authors.ts
import prisma from '$lib/prisma';
import { auth } from '$lib/trpc/middleware/auth';
import { logger } from '$lib/trpc/middleware/logger';
import { t } from '$lib/trpc/t';
import { z } from 'zod';
export const authors = t.router({
list: t.procedure
.use(logger)
.input(z.string().optional())
.query(({ input }) =>
prisma.author.findMany({
select: {
id: true,
firstName: true,
lastName: true,
updatedAt: true,
_count: { select: { books: true } }
},
orderBy: { updatedAt: 'desc' },
where: input
? { OR: [{ firstName: { contains: input } }, { lastName: { contains: input } }] }
: undefined
})
),
loadOptions: t.procedure.use(logger).query(() =>
prisma.author
.findMany({
select: { id: true, firstName: true, lastName: true },
orderBy: [{ firstName: 'asc' }, { lastName: 'asc' }]
})
.then((authors) =>
authors.map(({ id, firstName, lastName }) => ({
label: `${firstName} ${lastName}`,
value: id
}))
)
),
load: t.procedure
.use(logger)
.use(auth) // 👈 use auth middleware
.input(z.string())
.query(({ input }) =>
prisma.author.findUniqueOrThrow({
select: {
id: true,
firstName: true,
lastName: true,
bio: true,
updatedAt: true,
updatedBy: { select: { name: true } }
},
where: { id: input }
})
),
save: t.procedure
.use(logger)
.use(auth) // 👈 use auth middleware
.input(
z.object({
id: z.string().nullable(),
firstName: z.string().min(3).max(50),
lastName: z.string().min(3).max(50),
bio: z.string().nullable()
})
)
.mutation(async ({ input: { id, ...rest }, ctx: { userId } }) => {
if (id) {
await prisma.author.update({
data: { ...rest, updatedByUserId: userId },
where: { id }
});
} else {
await prisma.author.create({
data: { ...rest, updatedByUserId: userId }
});
}
}),
delete: t.procedure
.use(logger)
.use(auth) // 👈 use auth middleware
.input(z.string())
.mutation(async ({ input: id }) => {
await prisma.author.delete({ where: { id } });
})
});