├── .eslintrc.json ├── .gitignore ├── .vscode └── settings.json ├── README.md ├── app ├── (auth) │ └── login │ │ ├── login-form.tsx │ │ └── page.tsx ├── api │ └── posts │ │ └── route.ts ├── favicon.ico ├── globals.css ├── layout.tsx └── page.tsx ├── components ├── navigation │ └── navbar.tsx ├── providers │ ├── supabase-auth-provider.tsx │ └── supabase-provider.tsx └── ui │ ├── avatar.tsx │ ├── button.tsx │ ├── dropdown-menu.tsx │ ├── input.tsx │ ├── label.tsx │ └── seperator.tsx ├── middleware.ts ├── next.config.js ├── package-lock.json ├── package.json ├── postcss.config.js ├── public ├── next.svg └── vercel.svg ├── supabase ├── .gitignore ├── config.toml └── seed.sql ├── tailwind.config.js ├── tsconfig.json ├── types ├── collections.ts └── supabase.ts ├── utils ├── cn.ts ├── supabase-browser.ts └── supabase-server.ts └── yarn.lock /.eslintrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "extends": "next/core-web-vitals" 3 | } 4 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # See https://help.github.com/articles/ignoring-files/ for more about ignoring files. 2 | 3 | # dependencies 4 | /node_modules 5 | /.pnp 6 | .pnp.js 7 | 8 | # testing 9 | /coverage 10 | 11 | # next.js 12 | /.next/ 13 | /out/ 14 | 15 | # production 16 | /build 17 | 18 | # misc 19 | .DS_Store 20 | *.pem 21 | 22 | # debug 23 | npm-debug.log* 24 | yarn-debug.log* 25 | yarn-error.log* 26 | .pnpm-debug.log* 27 | 28 | # local env files 29 | .env*.local 30 | 31 | # vercel 32 | .vercel 33 | 34 | # typescript 35 | *.tsbuildinfo 36 | next-env.d.ts 37 | -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "typescript.tsdk": "node_modules/typescript/lib", 3 | "typescript.enablePromptUseWorkspaceTsdk": true 4 | } -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | This is a [Next.js](https://nextjs.org/) project bootstrapped with [`create-next-app`](https://github.com/vercel/next.js/tree/canary/packages/create-next-app). 2 | 3 | ## Getting Started 4 | 5 | First, run the development server: 6 | 7 | ```bash 8 | npm run dev 9 | # or 10 | yarn dev 11 | # or 12 | pnpm dev 13 | ``` 14 | 15 | Open [http://localhost:3000](http://localhost:3000) with your browser to see the result. 16 | 17 | You can start editing the page by modifying `app/page.tsx`. The page auto-updates as you edit the file. 18 | 19 | [http://localhost:3000/api/hello](http://localhost:3000/api/hello) is an endpoint that uses [Route Handlers](https://beta.nextjs.org/docs/routing/route-handlers). This endpoint can be edited in `app/api/hello/route.ts`. 20 | 21 | This project uses [`next/font`](https://nextjs.org/docs/basic-features/font-optimization) to automatically optimize and load Inter, a custom Google Font. 22 | 23 | ## Learn More 24 | 25 | To learn more about Next.js, take a look at the following resources: 26 | 27 | - [Next.js Documentation](https://nextjs.org/docs) - learn about Next.js features and API. 28 | - [Learn Next.js](https://nextjs.org/learn) - an interactive Next.js tutorial. 29 | 30 | You can check out [the Next.js GitHub repository](https://github.com/vercel/next.js/) - your feedback and contributions are welcome! 31 | 32 | ## Deploy on Vercel 33 | 34 | The easiest way to deploy your Next.js app is to use the [Vercel Platform](https://vercel.com/new?utm_medium=default-template&filter=next.js&utm_source=create-next-app&utm_campaign=create-next-app-readme) from the creators of Next.js. 35 | 36 | Check out our [Next.js deployment documentation](https://nextjs.org/docs/deployment) for more details. 37 | -------------------------------------------------------------------------------- /app/(auth)/login/login-form.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { useAuth } from "@/components/providers/supabase-auth-provider"; 4 | import { Button } from "@/components/ui/button"; 5 | import { Input } from "@/components/ui/input"; 6 | import { Label } from "@/components/ui/label"; 7 | import { Separator } from "@/components/ui/seperator"; 8 | import { Github, Mail } from "lucide-react"; 9 | import { useRouter } from "next/navigation"; 10 | import { useEffect, useState } from "react"; 11 | 12 | const LoginForm = () => { 13 | const [email, setEmail] = useState(""); 14 | const [password, setPassword] = useState(""); 15 | const [error, setError] = useState(null); 16 | const { signInWithEmail, signInWithGithub, user } = useAuth(); 17 | const router = useRouter(); 18 | 19 | const handleSubmit = async (e: React.FormEvent) => { 20 | e.preventDefault(); 21 | setError(null); 22 | try { 23 | const error = await signInWithEmail(email, password); 24 | if (error) { 25 | setError(error); 26 | } 27 | } catch (error) { 28 | console.log("Something went wrong!"); 29 | } 30 | }; 31 | 32 | // Check if there is a user 33 | useEffect(() => { 34 | if (user) { 35 | router.push("/"); 36 | } 37 | }, [user]); 38 | 39 | return ( 40 |
41 | {/* Main Container */} 42 |
43 | {/* Text */} 44 |
45 |

Login

46 |

47 | Welcome to the{" "} 48 | 49 | Supabase & Next.js 13 Auth Starter. 50 | {" "} 51 | Please login your account by email or the Github account. 52 |

53 |
54 | {/* Github Button */} 55 | 61 | {/* Seperator */} 62 |
63 | OR 64 |
65 | {/* Form Container */} 66 |
67 | {/* Inputs Container */} 68 |
69 |
70 | 71 | setEmail(e.target.value)} /> 72 |
73 |
74 | 75 | setPassword(e.target.value)} 78 | /> 79 |
80 |
81 | {/* Error */} 82 | {error &&
{error}
} 83 | 91 |
92 |
93 |
94 | ); 95 | }; 96 | 97 | export default LoginForm; 98 | -------------------------------------------------------------------------------- /app/(auth)/login/page.tsx: -------------------------------------------------------------------------------- 1 | import LoginForm from "./login-form"; 2 | 3 | const LoginPage = () => { 4 | return ( 5 |
6 | 7 |
8 |
9 | ); 10 | }; 11 | 12 | export default LoginPage; 13 | -------------------------------------------------------------------------------- /app/api/posts/route.ts: -------------------------------------------------------------------------------- 1 | import { createClient } from "@/utils/supabase-server"; 2 | import { NextResponse } from "next/server"; 3 | 4 | export async function GET(request: Request) { 5 | const supabase = createClient(); 6 | 7 | // Fetch the Current User 8 | const { 9 | data: { session }, 10 | } = await supabase.auth.getSession(); 11 | 12 | // If there is no user, return 401 Unauthorized 13 | if (!session) { 14 | return new Response("Unauthorized", { status: 401 }); 15 | } 16 | 17 | // Fetch Posts for the current user 18 | const { data: posts, error } = await supabase 19 | .from("posts") 20 | .select("*") 21 | .eq("author", session?.user.id); 22 | 23 | if (error) { 24 | return new Response(error.message, { status: 500 }); 25 | } 26 | 27 | return NextResponse.json(posts); 28 | } 29 | -------------------------------------------------------------------------------- /app/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/batuhanbilginn/supabase-nextjs-server-auth/343c4a95b07e2def86e77befae69907c309be74f/app/favicon.ico -------------------------------------------------------------------------------- /app/globals.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /app/layout.tsx: -------------------------------------------------------------------------------- 1 | import "server-only"; 2 | import "./globals.css"; 3 | 4 | export const metadata = { 5 | title: "Supabase Auth Starter", 6 | description: "Generated by create next app", 7 | }; 8 | 9 | import SupabaseAuthProvider from "@/components/providers/supabase-auth-provider"; 10 | import SupabaseProvider from "@/components/providers/supabase-provider"; 11 | import { createClient } from "@/utils/supabase-server"; 12 | import { Inter } from "next/font/google"; 13 | 14 | const inter = Inter({ subsets: ["latin"], variable: "--font-sans" }); 15 | 16 | export default async function RootLayout({ 17 | children, 18 | }: { 19 | children: React.ReactNode; 20 | }) { 21 | const supabase = createClient(); 22 | 23 | const { 24 | data: { session }, 25 | } = await supabase.auth.getSession(); 26 | return ( 27 | 28 | 29 | 30 | 31 | {children} 32 | 33 | 34 | 35 | 36 | ); 37 | } 38 | -------------------------------------------------------------------------------- /app/page.tsx: -------------------------------------------------------------------------------- 1 | import Navbar from "@/components/navigation/navbar"; 2 | 3 | export default function Home() { 4 | return ( 5 |
6 | 7 |
8 | Homepage 9 |
10 |
11 | {/* Logo */} 12 |
LOGO
13 |
All rights reserved.
14 |
15 |
16 | ); 17 | } 18 | -------------------------------------------------------------------------------- /components/navigation/navbar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { 4 | DropdownMenu, 5 | DropdownMenuContent, 6 | DropdownMenuGroup, 7 | DropdownMenuItem, 8 | DropdownMenuLabel, 9 | DropdownMenuSeparator, 10 | DropdownMenuShortcut, 11 | DropdownMenuTrigger, 12 | } from "@/components/ui/dropdown-menu"; 13 | import { 14 | Cloud, 15 | CreditCard, 16 | Github, 17 | LifeBuoy, 18 | LogOut, 19 | Settings, 20 | User, 21 | } from "lucide-react"; 22 | import { useAuth } from "../providers/supabase-auth-provider"; 23 | import { Avatar, AvatarFallback, AvatarImage } from "../ui/avatar"; 24 | 25 | const Navbar = () => { 26 | const { user, signOut } = useAuth(); 27 | return ( 28 | 84 | ); 85 | }; 86 | 87 | export default Navbar; 88 | -------------------------------------------------------------------------------- /components/providers/supabase-auth-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { Profile } from "@/types/collections"; 4 | import { Session } from "@supabase/supabase-js"; 5 | import { useRouter } from "next/navigation"; 6 | import { createContext, useContext, useEffect } from "react"; 7 | import useSWR from "swr"; 8 | import { useSupabase } from "./supabase-provider"; 9 | interface ContextI { 10 | user: Profile | null | undefined; 11 | error: any; 12 | isLoading: boolean; 13 | mutate: any; 14 | signOut: () => Promise; 15 | signInWithGithub: () => Promise; 16 | signInWithEmail: (email: string, password: string) => Promise; 17 | } 18 | const Context = createContext({ 19 | user: null, 20 | error: null, 21 | isLoading: true, 22 | mutate: null, 23 | signOut: async () => {}, 24 | signInWithGithub: async () => {}, 25 | signInWithEmail: async (email: string, password: string) => null, 26 | }); 27 | 28 | export default function SupabaseAuthProvider({ 29 | serverSession, 30 | children, 31 | }: { 32 | serverSession?: Session | null; 33 | children: React.ReactNode; 34 | }) { 35 | const { supabase } = useSupabase(); 36 | const router = useRouter(); 37 | 38 | // Get USER 39 | const getUser = async () => { 40 | const { data: user, error } = await supabase 41 | .from("profiles") 42 | .select("*") 43 | .eq("id", serverSession?.user?.id) 44 | .single(); 45 | if (error) { 46 | console.log(error); 47 | return null; 48 | } else { 49 | return user; 50 | } 51 | }; 52 | 53 | const { 54 | data: user, 55 | error, 56 | isLoading, 57 | mutate, 58 | } = useSWR(serverSession ? "profile-context" : null, getUser); 59 | 60 | // Sign Out 61 | const signOut = async () => { 62 | await supabase.auth.signOut(); 63 | router.push("/login"); 64 | }; 65 | 66 | // Sign-In with Github 67 | const signInWithGithub = async () => { 68 | await supabase.auth.signInWithOAuth({ provider: "github" }); 69 | }; 70 | 71 | // Sign-In with Email 72 | const signInWithEmail = async (email: string, password: string) => { 73 | const { error } = await supabase.auth.signInWithPassword({ 74 | email, 75 | password, 76 | }); 77 | 78 | if (error) { 79 | return error.message; 80 | } 81 | 82 | return null; 83 | }; 84 | 85 | // Refresh the Page to Sync Server and Client 86 | useEffect(() => { 87 | const { 88 | data: { subscription }, 89 | } = supabase.auth.onAuthStateChange((event, session) => { 90 | if (session?.access_token !== serverSession?.access_token) { 91 | router.refresh(); 92 | } 93 | }); 94 | 95 | return () => { 96 | subscription.unsubscribe(); 97 | }; 98 | }, [router, supabase, serverSession?.access_token]); 99 | 100 | const exposed: ContextI = { 101 | user, 102 | error, 103 | isLoading, 104 | mutate, 105 | signOut, 106 | signInWithGithub, 107 | signInWithEmail, 108 | }; 109 | 110 | return {children}; 111 | } 112 | 113 | export const useAuth = () => { 114 | let context = useContext(Context); 115 | if (context === undefined) { 116 | throw new Error("useAuth must be used inside SupabaseAuthProvider"); 117 | } else { 118 | return context; 119 | } 120 | }; 121 | -------------------------------------------------------------------------------- /components/providers/supabase-provider.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import { createClient } from "@/utils/supabase-browser"; 4 | import { createContext, useContext, useState } from "react"; 5 | 6 | import type { Database } from "@/types/supabase"; 7 | import type { SupabaseClient } from "@supabase/auth-helpers-nextjs"; 8 | 9 | type SupabaseContext = { 10 | supabase: SupabaseClient; 11 | }; 12 | 13 | const Context = createContext(undefined); 14 | 15 | export default function SupabaseProvider({ 16 | children, 17 | }: { 18 | children: React.ReactNode; 19 | }) { 20 | const [supabase] = useState(() => createClient()); 21 | 22 | return ( 23 | 24 | <>{children} 25 | 26 | ); 27 | } 28 | 29 | export const useSupabase = () => { 30 | let context = useContext(Context); 31 | if (context === undefined) { 32 | throw new Error("useSupabase must be used inside SupabaseProvider"); 33 | } else { 34 | return context; 35 | } 36 | }; 37 | -------------------------------------------------------------------------------- /components/ui/avatar.tsx: -------------------------------------------------------------------------------- 1 | "use client"; 2 | 3 | import * as AvatarPrimitive from "@radix-ui/react-avatar"; 4 | import * as React from "react"; 5 | 6 | import { cn } from "@/utils/cn"; 7 | 8 | const Avatar = React.forwardRef< 9 | React.ElementRef, 10 | React.ComponentPropsWithoutRef 11 | >(({ className, ...props }, ref) => ( 12 | 20 | )); 21 | Avatar.displayName = AvatarPrimitive.Root.displayName; 22 | 23 | const AvatarImage = React.forwardRef< 24 | React.ElementRef, 25 | React.ComponentPropsWithoutRef 26 | >(({ className, ...props }, ref) => ( 27 | 32 | )); 33 | AvatarImage.displayName = AvatarPrimitive.Image.displayName; 34 | 35 | const AvatarFallback = React.forwardRef< 36 | React.ElementRef, 37 | React.ComponentPropsWithoutRef 38 | >(({ className, ...props }, ref) => ( 39 | 47 | )); 48 | AvatarFallback.displayName = AvatarPrimitive.Fallback.displayName; 49 | 50 | export { Avatar, AvatarImage, AvatarFallback }; 51 | -------------------------------------------------------------------------------- /components/ui/button.tsx: -------------------------------------------------------------------------------- 1 | import { cva, VariantProps } from "class-variance-authority"; 2 | import * as React from "react"; 3 | 4 | import { cn } from "@/utils/cn"; 5 | 6 | const buttonVariants = cva( 7 | "active:scale-95 inline-flex items-center justify-center rounded-md text-sm font-medium transition-colors focus:outline-none focus:ring-2 focus:ring-slate-400 focus:ring-offset-2 dark:hover:bg-slate-800 dark:hover:text-slate-100 disabled:opacity-50 dark:focus:ring-slate-400 disabled:pointer-events-none dark:focus:ring-offset-slate-900 data-[state=open]:bg-slate-100 dark:data-[state=open]:bg-slate-800", 8 | { 9 | variants: { 10 | variant: { 11 | default: 12 | "bg-slate-900 text-white hover:bg-slate-700 dark:bg-slate-50 dark:text-slate-900", 13 | destructive: 14 | "bg-red-500 text-white hover:bg-red-600 dark:hover:bg-red-600", 15 | outline: 16 | "bg-transparent border border-slate-200 hover:bg-slate-100 dark:border-slate-700 dark:text-slate-100", 17 | subtle: 18 | "bg-slate-100 text-slate-900 hover:bg-slate-200 dark:bg-slate-700 dark:text-slate-100", 19 | ghost: 20 | "bg-transparent hover:bg-slate-100 dark:hover:bg-slate-800 dark:text-slate-100 dark:hover:text-slate-100 data-[state=open]:bg-transparent dark:data-[state=open]:bg-transparent", 21 | link: "bg-transparent dark:bg-transparent underline-offset-4 hover:underline text-slate-900 dark:text-slate-100 hover:bg-transparent dark:hover:bg-transparent", 22 | }, 23 | size: { 24 | default: "h-10 py-2 px-4", 25 | sm: "h-9 px-2 rounded-md", 26 | lg: "h-11 px-8 rounded-md", 27 | }, 28 | }, 29 | defaultVariants: { 30 | variant: "default", 31 | size: "default", 32 | }, 33 | } 34 | ); 35 | 36 | export interface ButtonProps 37 | extends React.ButtonHTMLAttributes, 38 | VariantProps {} 39 | 40 | const Button = React.forwardRef( 41 | ({ className, variant, size, ...props }, ref) => { 42 | return ( 43 |