karawaci.kode

← Semua snippet

TypeScript Pemula Auth

Supabase magic link auth flow lengkap

Login passwordless dengan Supabase magic link — kirim email, verify token, set session. Cocok buat MVP yang gak mau ribet password reset.

Dipublikasikan 6 Juni 2026

Password lupa, lupa lagi, reset email gak dateng. Magic link bikin login flow yang lebih simpel: tinggal kasih email, cek inbox, klik link, masuk. Snippet ini integrasi Supabase Auth di Next.js — server-side aman, client-side smooth.

Kode

// lib/supabase.ts
import { createServerClient, type CookieOptions } from "@supabase/ssr";
import { cookies } from "next/headers";

export function createSupabaseServer() {
  const cookieStore = cookies();
  return createServerClient(
    process.env.NEXT_PUBLIC_SUPABASE_URL!,
    process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY!,
    {
      cookies: {
        get(name: string) {
          return cookieStore.get(name)?.value;
        },
        set(name: string, value: string, options: CookieOptions) {
          cookieStore.set({ name, value, ...options });
        },
        remove(name: string, options: CookieOptions) {
          cookieStore.set({ name, value: "", ...options, maxAge: 0 });
        },
      },
    }
  );
}
// app/login/actions.ts — Server Action untuk kirim magic link
"use server";

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

export async function sendMagicLink(formData: FormData) {
  const email = String(formData.get("email") ?? "").trim();

  if (!email || !email.includes("@")) {
    return { error: "Email tidak valid" };
  }

  const supabase = createSupabaseServer();
  const { error } = await supabase.auth.signInWithOtp({
    email,
    options: {
      shouldCreateUser: true,
      emailRedirectTo: `${process.env.NEXT_PUBLIC_SITE_URL}/auth/callback`,
    },
  });

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

  redirect(`/login/cek-email?email=${encodeURIComponent(email)}`);
}
// app/auth/callback/route.ts — handle redirect dari email
import { createSupabaseServer } from "@/lib/supabase";
import { NextResponse } from "next/server";

export async function GET(request: Request) {
  const { searchParams, origin } = new URL(request.url);
  const code = searchParams.get("code");
  const next = searchParams.get("next") ?? "/dashboard";

  if (!code) {
    return NextResponse.redirect(`${origin}/login?error=missing-code`);
  }

  const supabase = createSupabaseServer();
  const { error } = await supabase.auth.exchangeCodeForSession(code);

  if (error) {
    return NextResponse.redirect(`${origin}/login?error=invalid-link`);
  }

  return NextResponse.redirect(`${origin}${next}`);
}

Pemakaian

// app/login/page.tsx — form login
import { sendMagicLink } from "./actions";

export default function LoginPage() {
  return (
    <form action={sendMagicLink}>
      <input
        name="email"
        type="email"
        required
        placeholder="nama@email.com"
        autoComplete="email"
      />
      <button type="submit">Kirim link login</button>
    </form>
  );
}
// app/login/cek-email/page.tsx — instruksi setelah submit
export default function CekEmailPage({
  searchParams,
}: {
  searchParams: { email?: string };
}) {
  return (
    <div>
      <h1>Cek inbox kamu</h1>
      <p>Link login dikirim ke <strong>{searchParams.email}</strong>.</p>
      <p>Klik link di email untuk masuk. Link valid 1 jam.</p>
    </div>
  );
}

Kapan dipakai

  • MVP / produk awal yang fokus iterasi cepat, belum mau handle reset password.
  • B2B SaaS — user korporat biasanya bisa akses email kerja dengan cepat.
  • Aplikasi internal team — kombinasi dengan domain restriction (@perusahaan.id only).
  • Onboarding flow — kurangi friction signup, password bisa di-add belakangan.

Catatan

  • Email deliverability krusial — Supabase default SMTP rate-limited (3 email/jam). Setup custom SMTP (Resend, Postmark, SendGrid) sebelum production.
  • Same-device limit — magic link expect dibuka di browser yang sama dengan submit form. Kalau user submit di desktop tapi buka email di HP, butuh OTP flow alternatif.
  • PKCE flow@supabase/ssr pakai PKCE by default. Code di URL bukan token, jadi aman walaupun di-share. Token exchange terjadi server-side.
  • RLS check — pastikan Row Level Security policies aktif di table sensitif, jangan andalkan auth check di app saja.

Kalau user laporan “link expired” padahal baru terima — cek timezone server. Default Supabase TTL 1 jam dari email sent, kalau server clock drift bisa kacau.

# tags

supabasemagic-linkpasswordlessauthemail

Ditulis oleh Asti Larasati · 6 Juni 2026