Auth | Supabase Docs
Use Supabase to Authenticate and Authorize your users.
supabase.com
https://lopunko.notion.site/Part-3-Supabase-Auth-1ba2621a1b5345f3b02d2059d47ac7a3
Part 3. Supabase Auth ์๊ฐ ๋ฐ ์ธ์ฆ๋ฐฉ์ ๊ธฐํ | Notion
Supabase Auth
lopunko.notion.site
Supabase Auth
- Supabase Auth๋ ๋ค์ํ ์ธ์ฆ ๋ฐฉ์์ ์ง์ํ๋ ์ธ์ฆ ์์คํ
์
๋๋ค.
- ์ฌ์ฉ์ ์ธ์ฆ ๋ฐ ๊ถํ ๊ด๋ฆฌ๋ฅผ ์ ๊ณตํฉ๋๋ค.
- ์ด๋ฉ์ผ ์ธ์ฆ, Magic Link๋ฅผ ํตํ ๋น๋ฐ๋ฒํธ ์๋ ๋ก๊ทธ์ธ, ์ ํ๋ฒํธ๋ฅผ ํตํ ๋ก๊ทธ์ธ, ์์
๋ก๊ทธ์ธ, ๊ทธ๋ฆฌ๊ณ ๊ธฐ์
์ฉ SSO ๋ฑ ๋ค์ํ ๋ฐฉ๋ฒ์ผ๋ก ์ฌ์ฉ์ ์ธ์ฆ์ ์ง์ํฉ๋๋ค.
- 19๊ฐ์ง ์ด์์ ์์
๋ก๊ทธ์ธ ๋ฐฉ์๋ ์ง์ํ๋ฉฐ, Facebook, Google, GitHub, ์นด์นด์ค(Kakao), Slack ๋ฑ์ ์๋น์ค๋ฅผ ์ด์ฉํ์ฌ ๋ก๊ทธ์ธํ ์ ์์ต๋๋ค.
- ์ ํ๋ฒํธ ์ธ์ฆ์ Twilio ๋ฑ์ ์ธ๋ถ Provider๋ฅผ ์ฌ์ฉํ์ฌ ์งํ ๊ฐ๋ฅํฉ๋๋ค.
- ์ธ์ฆ ๋ฐฉ์์ JWT๋ Session์ ์ฌ์ฉํ์ฌ ์ค์ ํ ์ ์์ต๋๋ค.
- next.js๋ฅผ ์ฌ์ฉํ๋ ๊ฒฝ์ฐ, ๋ณ๋์ ์ค์ ๋ฌธ์๋ฅผ ํตํด Supabase Auth ์ค์ ์ ์งํํ ์ ์์ต๋๋ค.
- Supabase Auth๋ฅผ ํตํด ์น์ฌ์ดํธ์ ๊ฐํธํ๊ฒ ์ฌ์ฉ์ ์ธ์ฆ ๊ธฐ๋ฅ์ ์ถ๊ฐํ ์ ์์ต๋๋ค.
https://supabase.com/dashboard/project/eceptjznfdhgchrlzyyt/auth/users
Auth | Supabase
supabase.com
1. ์ธ์ฆ ๋งํฌ๋ก ํ์๊ฐ์
https://supabase.com/docs/guides/auth/passwords
Password-based Auth | Supabase Docs
Allow users to sign in with a password connected to their email or phone number.
supabase.com
https://lopunko.notion.site/Part-4-28ca1fc056bf4b3691b4e06caeac9384
Part 4. ์ธ์ฆ๊ตฌํ - ์ธ์ฆ๋งํฌ ๋ฐฉ์ ํ์๊ฐ์
| Notion
TODO
lopunko.notion.site
supabase -> auth
site url -> ํ์ฌ ์น์ฌ์ดํธ๋ฅผ ์
๋ ฅํ๋ฉด๋๋ค.
redirect URL -> ํ์๊ฐ์
์ด๋ ์ธ์ฆ๊ณผ์ ์ ์๋ฃ๋์๋ ์ด๋๊ณณ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ ์ํฌ์ง
Email template
( ๋ฌด๋ฃ ์๋น์ค๋ 1์๊ฐ์ 3๋ฒ๋ง ๊ฐ๋ฅ )
.
confirmationURL ์ ์ฌ์ฉํด์ ์ด๋ฉ์ผ ์ธ์ฆ์ ํ ์ ์์
์ด๋ ๊ฒ ํ๊ณ SAVE ๋๋ฅด๋ฉด ์ธํ
์ ์๋ฃ๋๊ฑฐ์
์ด์ ํ์๊ฐ์
ํ์ด์ง์์ ๊ฐ์
๊ตฌํ์ ํ๋ฉด๋จ
export default function SignUp({ setView }) {
const [email, setEmail] = useState("");
const [password, setPassword] = useState("");
const [confirmationRequired, setConfirmationRequired] = useState(false); //์ด๋ฉ์ผ ์ธ์ฆ ์๋ฃ ์ฌ๋ถ
//supabase auth
const supabase = createBrowserSupabaseClient();
//signup mutation
const signupMutation = useMutation({
mutationFn: async () => {
const { data, error } = await supabase.auth.signUp({
email,
password,
options: {
emailRedirectTo: "http://localhost:3000/signup/confirm", //์ด๋ฉ์ผ ๋งํฌ ํด๋ฆญํ์ฌ , SUPABASE ํ์๊ฐ์
์ฒ๋ฆฌ ๋ ํ ๋ฆฌ๋ค์ด๋ ํธ๋๋ ์ฃผ์
},
});
if (data) {
setConfirmationRequired(true);
}
if (error) {
alert(error.message);
}
},
});
๊ฐ์
ํ๊ธฐ ๋ฒํผ๋ ์ด๋ฏธ ๊ฐ์
ํ๊ธฐ ์คํ์ค ์ผ ๋๋ ํ๋ฒ ๋ ํด๋ฆญํ์ง ๋ชปํ๋๋ก ์ฝ๋๋ฅผ ์์ ํ๋ค.
<Button
onClick={() => signupMutation.mutate()}
loading={signupMutation.isPending}
disabled={confirmationRequired}
className="w-full text-md py-1 "
color="light-blue"
>
{confirmationRequired ? "๋ฉ์ผํจ์ ํ์ธํด์ฃผ์ธ์" : "๊ฐ์
ํ๊ธฐ"}
</Button>
</div>
์ด๋ฉ์ผ ์ธ์ฆ ํ ๋ฆฌ๋ค์ด๋ ํธ ์ฃผ์๋
http://localhost:3000/signup/confirm?code=....
์ผ๋ก ๋ ๊ฑฐ์
import { NextResponse } from "next/server";
import { createServerSupabaseClient } from "utils/supabase/server";
//localhost:3000/signup/confirm?code....
export async function GET(request: Request) {
const requestUrl = new URL(request.url);
const code = requestUrl.searchParams.get("code");
if (code) {
const supabase = await createServerSupabaseClient();
await supabase.auth.exchangeCodeForSession(code); //์ฝ๋๊ฐ ์ ์์ ์ด๋ฉด ์ธ์
์ ํ๋ํจ
}
//localhost:3000/ ์ผ๋ก ๋ฆฌ๋ค์ด๋ ํธ
return NextResponse.redirect(requestUrl.origin);
}
๋น๋ฐ๋ฒํธ 6์๋ฆฌ ์ด์ ์
๋ ฅํด์ผํจ
๊ฐ์
ํ๊ธฐ๋ฅผ ํด๋ฆญํ๋ฉด ์
๋ ฅํ ๋ฉ์ผ๋ก ์ธ์ฆ ๋ฉ์ผ์ด ๊ฐ๋ค.
.
์ด๋ ๊ฒ ๋ฉ์ผ์ด ์จ๋ค ..
์ธ์ฆํ๊ธฐ๋ฅผ ํด๋ฆญํ๋ฉด ์ ์ ์ถ๊ฐ๊ฐ ๋๋ค.
ํ์ง๋ง ์ด ์ํ์์๋ ํ์ฌ ๋ก๊ทธ์ธ์ ์ฌ๋ถ๋ ํ์ธํ ์ ์์ผ๋ฏ๋ก
RootLayout์์ ์ฝ๋๋ฅผ ์์ ํ์ฌ ํ์ธํด๋ณธ๋ค.
export default async function RootLayout({
children,
}: Readonly<{
children: React.ReactNode;
}>) {
const supabase = await createServerSupabaseClient();
const {
data: { session },
} = await supabase.auth.getSession();
console.log(session);
<ThemeProvider>
{session?.user ? <MainLayout>{children}</MainLayout> : <Auth />}
</ThemeProvider>
page.tsx ์์ ์ ์ ์ ๋ณด๋ฅผ ๊ฐ์ ธ์์ ํ์ํ ๊ฑฐ์
์ด๋ฉ์ผ ์ ์ฒด๋ฅผ ๊ฐ์ ธ์ค๋ฉด ๋๋ฌด ๊ธธ์ด์ง๋๊น split์ผ๋ก ๋ถ๋ฆฌํ์ฌ 0๋ฒ์งธ๋ง ๊ฐ์ ธ์ด
import LogoutButton from "components/auth/logout-button";
import { createServerSupabaseClient } from "utils/supabase/server";
export const metadata = {
title: "Inflearngram",
descripton: "Instargram clone project",
};
export default async function Home() {
const supabase = await createServerSupabaseClient();
const {
data: { session },
} = await supabase.auth.getSession();
return (
<main className="w-full h-screen flex gap-2 flex-col items-center justify-center">
<h1 className="font-bold text-xl">
{/* ์ ์ ์ ๋ณด๊ฐ์ ธ์ค๊ธฐ */}
Welcome {session?.user?.email?.split("@"?.[0])}
</h1>
<LogoutButton />
</main>
);
}
๋ก๊ทธ์์ ๋ฒํผ ์ปดํฌ๋ํธ์์ ๋ก๊ทธ์์ ์ฒ๋ฆฌ๋ฅผ ์ถ๊ฐํ๋ค.
"use client";
import { Button } from "@material-tailwind/react";
import { createBrowserSupabaseClient } from "utils/supabase/client";
export default function LogoutButton() {
const supabase = createBrowserSupabaseClient();
return (
<Button
color="red"
onClick={async () => {
supabase.auth.signOut();
}}
>
๋ก๊ทธ์์
</Button>
);
}
sidebar์ ๋ก๊ทธ์์ ๋ฒํผ์๋ ์์ ์ฝ๋๋ฅผ ๊ทธ๋๋ก ๋ฃ์ด์ค
{/* logout button */}
<div>
<button
onClick={async () => {
supabase.auth.signOut();
}}
>
<Logout className="text-2xl text-deep-purple-900" />
</button>
</div>
</aside>
auth-provider๋ฅผ ๋ง๋ ํ ์ ์ฉ
export default function AuthProvider({ accessToken, children }) {
const supabase = createBrowserSupabaseClient();
const router = useRouter();
useEffect(() => {
const {
data: { subscription: authListner },
} = supabase.auth.onAuthStateChange((event, session) => {
if (session?.access_token !== accessToken) {
router.refresh(); //๋ฆฌํ๋ ์ฌ
}
});
return () => {
authListner.unsubscribe(); //ํ์์๋ ๊ตฌ๋
์ ํ๋ฉด์ ์๋ก๊ณ ์นจ์ ์์ผ์คฌ๋ค๊ฐ ํ๋ฉด์ด ๋ซํ๋ฉด ๊ตฌ๋
์ ์ทจ์ํ๋ค.
};
}, [accessToken, supabase, router]); //์์ธ์ค ํ ํฐ์ด ๋ง๋ฃ๋์๊ฑฐ๋, ์ ์ ๋ก๊ทธ์์์ ํ๋ค๋๊ฐํ๋ฉด ๋ฆฌํ๋ ์ฌํ๋ค.
return children;
}
<body className={inter.className}>
<RecoilProvider>
<ReactQueryClientProvider>
<ThemeProvider>
<AuthProvider accessToken={session?.access_token}>
{session?.user ? <MainLayout>{children}</MainLayout> : <Auth />}
</AuthProvider>
</ThemeProvider>
</ReactQueryClientProvider>
</RecoilProvider>
</body>