Episode ini mengamankan API tRPC: middleware otentikasi berbasis token, session, atau cookie, otorisasi per procedure dengan role-based access control, serta integrasi dengan NextAuth, Clerk, atau provider custom.

API yang bisa diakses siapa saja adalah api yang tidak aman. Episode 11 membahas dua konsep yang sering tertukar: otentikasi — membuktikan siapa user — dan authorization — menentukan apa yang boleh dilakukan user. Di tRPC, keduanya diimplementasikan lewat middleware dan context.
Kalian akan membangun middleware otentikasi berbasis token, session, dan cookie, menerapkan RBAC per procedure, serta menghubungkan tRPC dengan provider seperti NextAuth dan Clerk.
Pola paling dasar: client mengirim token di header Authorization, server memverifikasinya di createContext:
import { initTRPC, TRPCError } from "@trpc/server";
const verifyToken = async (token: string) => {
// decode JWT atau panggil provider auth
const payload = await jwt.verify(token, SECRET);
return { id: payload.sub, role: payload.role };
};
export const t = initTRPC.context<Context>().create({
createContext: async ({ req }) => {
const token = req.headers.get("authorization")?.replace("Bearer ", "");
if (!token) return { user: null };
const user = await verifyToken(token);
return { user };
},
});Di sini context membaca token sekali per request. createContext menjalankan verifikasi sebelum procedure mana pun berjalan, sehingga ctx.user tersedia di semua middleware dan resolver.
Procedure publik tetap bisa dipakai siapa saja, tetapi procedure terlindungi butuh user:
export const protectedProcedure = t.procedure.use(
t.middleware(({ ctx, next }) => {
if (!ctx.user) {
throw new TRPCError({ code: "UNAUTHORIZED" });
}
return next({ ctx: { ...ctx, userId: ctx.user.id } });
}),
);protectedProcedure.use(...) melempar UNAUTHORIZED (HTTP 401) jika user tidak ada, lalu meneruskan ctx.userId ke resolver. Pembagian ini membuat definisi procedure tetap jelas: publicProcedure untuk endpoint terbuka, protectedProcedure untuk yang tertutup.
Otentikasi memastikan user ada; otorisasi memastikan user berhak. Contoh: hanya admin yang boleh menghapus user:
export const adminProcedure = protectedProcedure.use(
t.middleware(({ ctx, next }) => {
if (ctx.user.role !== "admin") {
throw new TRPCError({ code: "FORBIDDEN" });
}
return next();
}),
);
user: t.router({
delete: adminProcedure
.input(z.object({ id: z.number() }))
.mutation(({ ctx, input }) => hapusUser(input.id, ctx.user.id)),
}),adminProcedure melempar FORBIDDEN (HTTP 403) untuk user non-admin — HTTP 403 yang membedakan "belum login" (401) dan "tidak berhak" (403) dengan benar.
Tidak semua aturan bisa dinyatakan dengan role saja. Untuk kepemilikan resource, lakukan check di dalam resolver atau middleware dengan input:
edit: protectedProcedure
.input(z.object({ postId: z.number(), judul: z.string() }))
.mutation(({ ctx, input }) => {
const post = cariPost(input.postId);
if (!post || post.authorId !== ctx.userId) {
throw new TRPCError({ code: "FORBIDDEN" });
}
post.judul = input.judul;
return post;
}),Pola ini penting: user hanya boleh mengedit posting yang dia miliki, bukan hanya yang "berhasil login". Kombinasi middleware role + check kepemilikan menghasilkan otorisasi yang lengkap.
Saat memakai NextAuth, session tersedia di createContext lewat getServerSession:
import { getServerSession } from "next-auth";
export const createContext = async (opts: { req: NextRequest }) => {
const session = await getServerSession(authOptions);
return {
...opts,
session,
user: session?.user ?? null,
};
};getServerSession(authOptions) mengambil session server-side secara aman. Middleware protectedProcedure kemudian membaca ctx.user dari session — pola yang sama persis dengan token, hanya sumber datanya berbeda.
Clerk menyediakan helper serupa untuk framework yang didukungnya, misalnya membaca user dari request. Prinsipnya selalu sama: konversi identitas dari provider menjadi ctx.user, lalu biarkan middleware tRPC menegakkan aturan. Untuk provider custom, cukup implementasikan verifyToken seperti contoh pertama dan pertukarkan secret provider dengan milik kalian.
Info
Pisahkan otentikasi dan otorisasi ke dua lapis middleware. Otentikasi menetapkan ctx.user; otorisasi memeriksa ctx.user. Dengan pemisahan ini, aturan otorisasi bisa berubah tanpa menyentuh proses login.
Jika ctx.session selalu null, periksa urutan provider: trpc.Provider harus berada di dalam SessionProvider (NextAuth) atau provider Clerk. Context membaca dari request saat procedure dipanggil, jadi provider auth harus membungkus aplikasi lebih dulu.
Mengirim token di query string adalah kebiasaan buruk yang sudah kita bahas di episode 10. Selalu gunakan header Authorization, dan pastikan httpBatchLink tidak memuntahkan token ke log proxy.
Episode 11 mengamankan API kalian: otentikasi berbasis token, session, atau cookie melalui createContext dan middleware, otorisasi per procedure dengan RBAC dan check kepemilikan, serta integrasi mulus dengan NextAuth, Clerk, dan provider custom.
Inti yang harus dibawa pulang:
createContext adalah titik ideal untuk memverifikasi identitas.protectedProcedure menegakkan kehadiran user dengan kode 401.adminProcedure dan check kepemilikan menegakkan otorisasi dengan 403.getServerSession; Clerk memakai helper framework-nya.ctx.user.Di episode 12 selanjutnya kita akan membahas keamanan API, rate limiting & best practice — proteksi terhadap injection, overfetch, dan payload abuse, rate limiting di level router atau adapter, serta best practice TLS, CSP, dan sanitasi input.