S
Supabase2mo ago
Muezz

tRPC with Supabase Auth

I am unable to sign in as I am trying to set up my project with tRPC. Here is the procedure where I am making the call:
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(
z.object({
email: z.string().email(),
password: z.string().min(1).max(25),
})
)
.mutation(async ({ input }) => {
const sp = await createClient();
const { data, error } = await sp.auth.signInWithPassword({
email: input.email,
password: input.password,
});
if (error) handleUnknownError(error);
revalidatePath("/", "layout");
return { success: true, user: data.user };
}),
});
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(
z.object({
email: z.string().email(),
password: z.string().min(1).max(25),
})
)
.mutation(async ({ input }) => {
const sp = await createClient();
const { data, error } = await sp.auth.signInWithPassword({
email: input.email,
password: input.password,
});
if (error) handleUnknownError(error);
revalidatePath("/", "layout");
return { success: true, user: data.user };
}),
});
Even though this runs without any errors, there is no session/cookie in the browser. I think this may be because Nextjs does not allow usage and setting of cookies outside of server actions and route handlers. What can I do to get around this?
8 Replies
Muezz
MuezzOP2mo ago
This uses server actions. I am trying to set up auth with trpc.
Jiří Mika
Jiří Mika2mo ago
Oh, and you cant combinate like that? its more safety
// actions/auth.ts
"use server";

import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";

export async function login(formData: FormData) {
const supabase = createClient();
const { error } = await supabase.auth.signInWithPassword({
email: formData.get("email") as string,
password: formData.get("password") as string,
});

if (error) {
return { error: error.message };
}

redirect("/dashboard");
}
// actions/auth.ts
"use server";

import { createClient } from "@/lib/supabase/server";
import { redirect } from "next/navigation";

export async function login(formData: FormData) {
const supabase = createClient();
const { error } = await supabase.auth.signInWithPassword({
email: formData.get("email") as string,
password: formData.get("password") as string,
});

if (error) {
return { error: error.message };
}

redirect("/dashboard");
}
// components/LoginForm.tsx
"use client";

import { useFormState } from "react-dom";
import { login } from "@/actions/auth";

export function LoginForm() {
const [state, formAction] = useFormState(login, null);

return (
<form action={formAction}>
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Login</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
// components/LoginForm.tsx
"use client";

import { useFormState } from "react-dom";
import { login } from "@/actions/auth";

export function LoginForm() {
const [state, formAction] = useFormState(login, null);

return (
<form action={formAction}>
<input type="email" name="email" />
<input type="password" name="password" />
<button type="submit">Login</button>
{state?.error && <p>{state.error}</p>}
</form>
);
}
Muezz
MuezzOP2mo ago
I know it will work with server actions because it was already working just fine. I am deliberately moving to tRPC. There are some features of tRPC that I want to take advantage of.
Jiří Mika
Jiří Mika2mo ago
And this?
import { cookies } from 'next/headers';

export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input, ctx }) => {
const supabase = await createClient();
const { data, error } = await supabase.auth.signInWithPassword(input);

if (error) throw new Error(error.message);

// Set cookies manually
const cookieStore = cookies();
const { data: { session } } = await supabase.auth.getSession();

if (session) {
cookieStore.set('access-token', session.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});
// Set other cookies as needed
}

return { user: data.user };
}),
});
import { cookies } from 'next/headers';

export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input, ctx }) => {
const supabase = await createClient();
const { data, error } = await supabase.auth.signInWithPassword(input);

if (error) throw new Error(error.message);

// Set cookies manually
const cookieStore = cookies();
const { data: { session } } = await supabase.auth.getSession();

if (session) {
cookieStore.set('access-token', session.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === 'production',
sameSite: 'lax',
path: '/',
});
// Set other cookies as needed
}

return { user: data.user };
}),
});
Muezz
MuezzOP2mo ago
As far as I can tell, this wont work either because Nextjs does not allow usage of cookies outside route handlers and server actions.
Jiří Mika
Jiří Mika2mo ago
Hmm, you have two options i think, local, not safe:
// trpc/auth.ts
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input }) => {
if (typeof window === "undefined") {
throw new Error("This method only works on the client!");
}

const supabase = createBrowserClient();
const { data, error } = await supabase.auth.signInWithPassword(input);
if (error) throw new Error(error.message);
return { user: data.user };
}),
});
// trpc/auth.ts
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input }) => {
if (typeof window === "undefined") {
throw new Error("This method only works on the client!");
}

const supabase = createBrowserClient();
const { data, error } = await supabase.auth.signInWithPassword(input);
if (error) throw new Error(error.message);
return { user: data.user };
}),
});
Or next one is create API route
// app/api/login/route.ts
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
import { cookies } from "next/headers";

export async function POST(request: Request) {
const { email, password } = await request.json();
const supabase = createClient();

const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});

if (error) {
return NextResponse.json({ error: error.message }, { status: 400 });
}

const session = (await supabase.auth.getSession()).data.session;
if (session) {
cookies().set("sb-access-token", session.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
});
}

return NextResponse.json({ user: data.user });
}
// app/api/login/route.ts
import { createClient } from "@/lib/supabase/server";
import { NextResponse } from "next/server";
import { cookies } from "next/headers";

export async function POST(request: Request) {
const { email, password } = await request.json();
const supabase = createClient();

const { data, error } = await supabase.auth.signInWithPassword({
email,
password,
});

if (error) {
return NextResponse.json({ error: error.message }, { status: 400 });
}

const session = (await supabase.auth.getSession()).data.session;
if (session) {
cookies().set("sb-access-token", session.access_token, {
httpOnly: true,
secure: process.env.NODE_ENV === "production",
sameSite: "lax",
path: "/",
});
}

return NextResponse.json({ user: data.user });
}
// trpc/auth.ts
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input }) => {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});

if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}

return res.json();
}),
});
// trpc/auth.ts
export const authRouter = createTRPCRouter({
login: publicProcedure
.input(z.object({ email: z.string().email(), password: z.string() }))
.mutation(async ({ input }) => {
const res = await fetch("/api/login", {
method: "POST",
headers: { "Content-Type": "application/json" },
body: JSON.stringify(input),
});

if (!res.ok) {
const error = await res.json();
throw new Error(error.message);
}

return res.json();
}),
});
Muezz
MuezzOP2mo ago
I am also leaning towards the second but I am hoping someone who has used tRPC can chime in and mention some clever workaround.

Did you find this page helpful?