G: ๋ฐฑ์๋ ๊ฐ๋ฐ์ ๋ณต์กํจ์ ์ง์น์ จ๋์? ๐คฏ ์ฌ์ฉ์ ์ธ์ฆ ์์คํ ์ ๊ตฌ์ถํ๋ ๊ฒ์ ์น ๋ฐ ๋ชจ๋ฐ์ผ ์ ํ๋ฆฌ์ผ์ด์ ๊ฐ๋ฐ์์ ๊ฐ์ฅ ๊ธฐ๋ณธ์ ์ธ ๋ถ๋ถ์ด๋ฉด์๋ ๊ฐ์ฅ ๋ฒ๊ฑฐ๋ก์ด ์์ ์ค ํ๋์ ๋๋ค. ๋ฐ์ดํฐ๋ฒ ์ด์ค ์ค์ , ์ํธํ, ์ธ์ ๊ด๋ฆฌ, ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฑ ๊ณ ๋ คํด์ผ ํ ๊ฒ์ด ํ๋ ๊ฐ์ง๊ฐ ์๋๋๋ค.
ํ์ง๋ง ๊ฑฑ์ ๋ง์ธ์! ์ค๋๋ ์ฐ๋ฆฌ๋ Supabase๋ผ๋ ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ์ฌ์ฉํ์ฌ ๋จ 5๋ถ ๋ง์ ์๋ฒฝํ๊ฒ ์๋ํ๋ ๋ก๊ทธ์ธ ์์คํ ์ ๊ตฌ์ถํ๋ ๋ฐฉ๋ฒ์ ์์๋ณผ ๊ฒ์ ๋๋ค. ๋ฏฟ๊ธฐ์ง ์๋๋ค๊ตฌ์? ๊ทธ๋ผ ์ ์ ํจ๊ป ์ง๊ธ ๋ฐ๋ก ์์ํด ๋ณผ๊น์? ๐
๐ก ์ Supabase์ธ๊ฐ์? Firebase์ ์คํ์์ค ๋์!
Supabase๋ ์คํ์์ค ๊ธฐ๋ฐ์ ๋ฐฑ์๋ ์๋น์ค๋ก, ํํ ‘Firebase์ ์คํ์์ค ๋์’์ผ๋ก ๋ถ๋ฆฝ๋๋ค. ํ์ง๋ง ๋จ์ํ ๋์์ ๋์ด์ ๊ฐ๋ ฅํ ํน์ง๋ค์ ๊ฐ์ง๊ณ ์์ต๋๋ค:
- PostgreSQL ๊ธฐ๋ฐ: ์น์ํ ๊ด๊ณํ ๋ฐ์ดํฐ๋ฒ ์ด์ค์ธ PostgreSQL์ ์ฌ์ฉํฉ๋๋ค. SQL์ ์ต์ํ๋ค๋ฉด ๋์ฑ ์ฝ๊ฒ ์ ๊ทผํ ์ ์์ต๋๋ค. ๐
- ํตํฉ ์ธ์ฆ(Authentication): ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ, ์์ ๋ก๊ทธ์ธ(Google, GitHub, Facebook ๋ฑ), OTP ๋ก๊ทธ์ธ ๋ฑ ๋ค์ํ ์ธ์ฆ ๋ฐฉ์์ ์์ฝ๊ฒ ํตํฉํ ์ ์์ต๋๋ค. ๐
- ์ค์๊ฐ(Realtime) ๊ตฌ๋ : ๋ฐ์ดํฐ๋ฒ ์ด์ค ๋ณ๊ฒฝ ์ฌํญ์ ์ค์๊ฐ์ผ๋ก ํด๋ผ์ด์ธํธ์ ํธ์ํ ์ ์์ต๋๋ค. โก๏ธ
- ์คํ ๋ฆฌ์ง(Storage): ํ์ผ ์ ๋ก๋ ๋ฐ ๊ด๋ฆฌ๋ฅผ ์ํ ๊ฐ์ฒด ์คํ ๋ฆฌ์ง ๊ธฐ๋ฅ์ ์ ๊ณตํฉ๋๋ค. ๐ฆ
- ์ฃ์ง ํจ์(Edge Functions): Deno ๊ธฐ๋ฐ์ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ํตํด ๋ฐฑ์๋ ๋ก์ง์ ๊ตฌํํ ์ ์์ต๋๋ค. โ๏ธ
- ์ฌ์ด ์ฌ์ฉ์ฑ: ์ง๊ด์ ์ธ ๋์๋ณด๋์ ์ ์ ๋ฆฌ๋ ๋ฌธ์, ๊ทธ๋ฆฌ๊ณ ๊ฐ๋ ฅํ ํด๋ผ์ด์ธํธ ๋ผ์ด๋ธ๋ฌ๋ฆฌ(JavaScript, Dart/Flutter ๋ฑ)๋ฅผ ์ ๊ณตํฉ๋๋ค. ๐
์ด ๋ชจ๋ ๊ธฐ๋ฅ๋ค์ด ์ ๊ธฐ์ ์ผ๋ก ์ฐ๊ฒฐ๋์ด ์์ด, ๊ฐ๋ฐ์๋ ํต์ฌ ๋น์ฆ๋์ค ๋ก์ง์๋ง ์ง์คํ ์ ์๊ฒ ํด์ค๋๋ค. ํนํ ์ธ์ฆ ๊ธฐ๋ฅ์ Supabase์ ํต์ฌ ๊ฐ์ ์ค ํ๋์ ๋๋ค.
๐ ๏ธ 1๋จ๊ณ: Supabase ํ๋ก์ ํธ ์์ํ๊ธฐ
๊ฐ์ฅ ๋จผ์ Supabase ํ๋ก์ ํธ๋ฅผ ์์ฑํด์ผ ํฉ๋๋ค.
-
Supabase ๊ฐ์ ๋ฐ ๋ก๊ทธ์ธ:
- Supabase ์น์ฌ์ดํธ (supabase.com)์ ์ ์ํ์ฌ GitHub ๊ณ์ ์ด๋ ์ด๋ฉ์ผ๋ก ๊ฐ์ /๋ก๊ทธ์ธํฉ๋๋ค.
- Supabase ๋์๋ณด๋๋ก ์ด๋ํฉ๋๋ค.
-
์ ํ๋ก์ ํธ ์์ฑ:
- ๋์๋ณด๋์์
New project
๋ฒํผ์ ํด๋ฆญํฉ๋๋ค. - Name: ํ๋ก์ ํธ ์ด๋ฆ์ ์
๋ ฅํฉ๋๋ค. (์:
MyLoginApp
) - Database Password: ๊ฐ๋ ฅํ ๋น๋ฐ๋ฒํธ๋ฅผ ์ค์ ํ๊ณ ๊ธฐ์ตํด๋ก๋๋ค. ๐
- Region: ์๋ฒ ์์น๋ฅผ ์ ํํฉ๋๋ค. ์ฌ์ฉ์๋ค๊ณผ ๊ฐ๊น์ด ๊ณณ์ ์ ํํ๋ ๊ฒ์ด ์ข์ต๋๋ค.
- Organization: ์กฐ์ง์ ์ ํํ๊ฑฐ๋ ์๋ก ์์ฑํฉ๋๋ค.
Create new project
๋ฅผ ํด๋ฆญํฉ๋๋ค. ํ๋ก์ ํธ ์์ฑ์ด ์๋ฃ๋ ๋๊น์ง ์ ์ ๊ธฐ๋ค๋ฆฝ๋๋ค. (์ฝ 1~2๋ถ ์์)
- ๋์๋ณด๋์์
-
API ํค ํ์ธ:
- ํ๋ก์ ํธ ๋์๋ณด๋๋ก ์ด๋ํฉ๋๋ค.
- ์ผ์ชฝ ๋ฉ๋ด์์
Project Settings
(ํฑ๋๋ฐํด ์์ด์ฝ โ๏ธ)๋ฅผ ํด๋ฆญํ ๋ค์API
๋ฅผ ์ ํํฉ๋๋ค. - ์ฌ๊ธฐ์
Project URL
๊ณผanon public
ํค๋ฅผ ํ์ธํ ์ ์์ต๋๋ค. ์ด ๋ ๊ฐ์ ํด๋ผ์ด์ธํธ ์ ํ๋ฆฌ์ผ์ด์ ์์ Supabase์ ํต์ ํ ๋ ํ์ํ๋ ์ ๊ธฐ๋กํด ๋์ธ์! ๐Project URL
:https://[your-project-id].supabase.co
ํํanon public
(orpublic anon
) key:eyJ...
ํํ๋ก ์์ํ๋ ๊ธด ๋ฌธ์์ด
โ๏ธ 2๋จ๊ณ: ์ธ์ฆ(Authentication) ์ค์ ํ๊ธฐ
์ด์ ํ๋ก์ ํธ๊ฐ ์ค๋น๋์์ผ๋, ๋ก๊ทธ์ธ ์์คํ ์ ํต์ฌ์ธ ์ธ์ฆ ๋ฐฉ์์ ์ค์ ํด ๋ด ์๋ค.
-
์ธ์ฆ ์ค์ ํ์ด์ง ์ ์:
- Supabase ๋์๋ณด๋์์ ์ผ์ชฝ ๋ฉ๋ด์
Authentication
(์๋ฌผ์ ์์ด์ฝ ๐)์ ํด๋ฆญํฉ๋๋ค. Settings
ํญ์ผ๋ก ์ด๋ํฉ๋๋ค.
- Supabase ๋์๋ณด๋์์ ์ผ์ชฝ ๋ฉ๋ด์
-
๋ก๊ทธ์ธ ๊ณต๊ธ์ ํ์ฑํ:
- ๊ธฐ๋ณธ์ ์ผ๋ก
Email
๋ก๊ทธ์ธ์ด ํ์ฑํ๋์ด ์์ต๋๋ค. Enable email signup
์ด ํ์ฑํ๋์ด ์๋์ง ํ์ธํฉ๋๋ค.Confirm email
์ต์ ๋ ํ์ฑํํ๋ ๊ฒ์ ๊ถ์ฅํฉ๋๋ค. (์ฌ์ฉ์๊ฐ ๊ฐ์ ์ ์ด๋ฉ์ผ ์ธ์ฆ์ ํ๋๋ก ํ์ฌ ์คํธ ๊ณ์ ์ ๋ฐฉ์งํฉ๋๋ค.) ๐ง- ์ํ๋ค๋ฉด
Google
,GitHub
,Facebook
๋ฑ ๋ค๋ฅธ ์์ ๋ก๊ทธ์ธ ๊ณต๊ธ์๋ค๋ ํ์ฑํํ ์ ์์ต๋๋ค. ๊ฐ ๊ณต๊ธ์๋ฅผ ํด๋ฆญํ์ฌ ์๋ด์ ๋ฐ๋ผ API ํค๋ฅผ ์ค์ ํ๋ฉด ๋ฉ๋๋ค. (์ด ์์์์๋ ์ด๋ฉ์ผ/๋น๋ฐ๋ฒํธ๋ง ์ฌ์ฉํ๊ฒ ์ต๋๋ค.)
- ๊ธฐ๋ณธ์ ์ผ๋ก
-
SMTP ์ค์ (์ ํ ์ฌํญ์ด์ง๋ง ์ค์!):
- ์ค์ ์๋น์ค์์๋ ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ด๋ฉ์ผ, ์ด๋ฉ์ผ ์ธ์ฆ ์ด๋ฉ์ผ ๋ฑ์ ๋ฐ์กํด์ผ ํฉ๋๋ค. ์ด๋ฅผ ์ํด SMTP ์ค์ ์ ํด์ผ ํฉ๋๋ค.
Email Templates
์น์ ์๋์SMTP Settings
์์ SendGrid, Mailgun ๋ฑ ์ด๋ฉ์ผ ์๋น์ค ์ ๊ณต์์ ์ ๋ณด๋ฅผ ์ ๋ ฅํ๋ฉด ๋ฉ๋๋ค. (์ด ๋จ๊ณ๋ 5๋ถ ์์ ์๋ฃํ๊ธฐ๋ ์ด๋ ต์ง๋ง, ์ค์ ์๋น์ค์์๋ ํ์์ ์ ๋๋ค!) ๐ฉ
๐ป 3๋จ๊ณ: ํด๋ผ์ด์ธํธ ์ฝ๋ ์์ฑํ๊ธฐ (5๋ถ ์์ ๋ก๊ทธ์ธ ์์คํ ์์ฑ!)
๊ฐ์ฅ ํต์ฌ์ ์ธ ๋จ๊ณ์
๋๋ค. ์ฐ๋ฆฌ๋ Supabase์์ ์ ๊ณตํ๋ React์ฉ Auth
UI ์ปดํฌ๋ํธ๋ฅผ ์ฌ์ฉํ์ฌ ๋๋๋๋ก ๋น ๋ฅด๊ฒ ๋ก๊ทธ์ธ ์์คํ
์ ๊ตฌํํ ๊ฒ์
๋๋ค. (React ๋์ ์ผ๋ฐ HTML/JS๋ก๋ ๊ฐ๋ฅํ๋ฉฐ, Vue, Next.js ๋ฑ ๋ค์ํ ํ๋ ์์ํฌ์์๋ ์ ์ฌํ๊ฒ ์ ์ฉ๋ฉ๋๋ค.)
์ด ์์์์๋ React ์ฑ์์ Supabase Auth UI๋ฅผ ์ฌ์ฉํ๋ ๋ฐฉ๋ฒ์ ๋ณด์ฌ๋๋ฆฌ๊ฒ ์ต๋๋ค.
-
์ React ํ๋ก์ ํธ ์์ฑ (์ด๋ฏธ ์๋ค๋ฉด ์คํต):
npx create-react-app supabase-login-app cd supabase-login-app
-
Supabase ํด๋ผ์ด์ธํธ ๋ฐ Auth UI ์ค์น:
npm install @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared # ๋๋ yarn add @supabase/supabase-js @supabase/auth-ui-react @supabase/auth-ui-shared
-
Supabase ํด๋ผ์ด์ธํธ ์ด๊ธฐํ:
src
ํด๋ ์์supabaseClient.js
ํ์ผ์ ๋ง๋ค๊ณ ์๋ ์ฝ๋๋ฅผ ์ถ๊ฐํฉ๋๋ค.YOUR_SUPABASE_URL
๊ณผYOUR_SUPABASE_ANON_KEY
๋ฅผ 1๋จ๊ณ์์ ์ป์ ๊ฐ์ผ๋ก ๋ฐ๊ฟ์ฃผ์ธ์.// src/supabaseClient.js import { createClient } from '@supabase/supabase-js' const supabaseUrl = process.env.REACT_APP_SUPABASE_URL const supabaseAnonKey = process.env.REACT_APP_SUPABASE_ANON_KEY export const supabase = createClient(supabaseUrl, supabaseAnonKey)
์ฃผ์: ์ค์ ๊ฐ์ ์ฝ๋์ ์ง์ ๋ฃ๋ ๋์
.env
ํ์ผ์ ์ฌ์ฉํ์ฌ ํ๊ฒฝ ๋ณ์๋ก ๊ด๋ฆฌํ๋ ๊ฒ์ด ์ข์ต๋๋ค.supabase-login-app
๋ฃจํธ ํด๋์.env
ํ์ผ์ ๋ง๋ค๊ณ ์๋ ๋ด์ฉ์ ์ถ๊ฐํ์ธ์:REACT_APP_SUPABASE_URL=YOUR_SUPABASE_URL REACT_APP_SUPABASE_ANON_KEY=YOUR_SUPABASE_ANON_KEY
์ดํ ์ฑ์ ๋ค์ ์์ํด์ผ ํ๊ฒฝ ๋ณ์๊ฐ ๋ก๋๋ฉ๋๋ค.
-
Auth
์ปดํฌ๋ํธ ์ฌ์ฉ:src/App.js
ํ์ผ์ ์ด๊ณ ๋ชจ๋ ๋ด์ฉ์ ์ง์ด ํ ์๋ ์ฝ๋๋ฅผ ๋ถ์ฌ๋ฃ์ผ์ธ์.// src/App.js import React, { useState, useEffect } from 'react'; import { Auth } from '@supabase/auth-ui-react'; import { ThemeSupa } from '@supabase/auth-ui-shared'; // ํ ๋ง ์ํฌํธ import { supabase } from './supabaseClient'; // 3๋จ๊ณ์์ ๋ง๋ supabaseClient ์ํฌํธ function App() { const [session, setSession] = useState(null); useEffect(() => { // ์ธ์ ์ํ ๋ณ๊ฒฝ์ ๊ฐ์งํฉ๋๋ค. supabase.auth.getSession().then(({ data: { session } }) => { setSession(session); }); const { data: { subscription }, } = supabase.auth.onAuthStateChange((_event, session) => { setSession(session); }); return () => subscription.unsubscribe(); }, []); if (!session) { // ์ธ์ ์ด ์์ผ๋ฉด ๋ก๊ทธ์ธ/ํ์๊ฐ์ UI๋ฅผ ๋ณด์ฌ์ค๋๋ค. return ( <div style={{ width: '100%', maxWidth: '400px', margin: '50px auto' }}>
๐ Supabase ๋ก๊ทธ์ธ/ํ์๊ฐ์
<Auth
supabaseClient={supabase}
appearance={{ theme: ThemeSupa }} // ๊ธฐ๋ณธ ํ
๋ง ์ฌ์ฉ
providers={['google', 'github', 'discord']} // ํ์ฑํํ ์์
๋ก๊ทธ์ธ ์ ํ (์ ํ ์ฌํญ)
localization={{
variables: {
sign_in: {
email_label: '์ด๋ฉ์ผ ์ฃผ์',
password_label: '๋น๋ฐ๋ฒํธ',
email_input_placeholder: '๋น์ ์ ์ด๋ฉ์ผ',
password_input_placeholder: '๋น๋ฐ๋ฒํธ',
button_label: '๋ก๊ทธ์ธ',
social_provider_text: '๋ค์์ผ๋ก ๋ก๊ทธ์ธ',
link_text: '์ด๋ฏธ ๊ณ์ ์ด ์์ผ์ ๊ฐ์? ๋ก๊ทธ์ธ',
},
sign_up: {
email_label: '์ด๋ฉ์ผ ์ฃผ์',
password_label: '๋น๋ฐ๋ฒํธ',
email_input_placeholder: '๋น์ ์ ์ด๋ฉ์ผ',
password_input_placeholder: '๋น๋ฐ๋ฒํธ',
button_label: 'ํ์๊ฐ์
',
social_provider_text: '๋ค์์ผ๋ก ํ์๊ฐ์
',
link_text: '๊ณ์ ์ด ์์ผ์ ๊ฐ์? ํ์๊ฐ์
',
},
// ๋น๋ฐ๋ฒํธ ์ฌ์ค์ , ์
๋ฐ์ดํธ ๋ฑ์ ํ
์คํธ๋ ์ปค์คํฐ๋ง์ด์ง ๊ฐ๋ฅ
},
}}
/>
</div>
);
} else {
// ์ธ์
์ด ์์ผ๋ฉด ์ฌ์ฉ์ ์ ๋ณด๋ฅผ ๋ณด์ฌ์ค๋๋ค.
return (
<div style={{ width: '100%', maxWidth: '400px', margin: '50px auto', textAlign: 'center' }}>
ํ์ํฉ๋๋ค! ๐
๋ก๊ทธ์ธ๋์์ต๋๋ค. ์ฌ์ฉ์ ์ด๋ฉ์ผ: {session.user.email}
<button
onClick={() => supabase.auth.signOut()}
style={{
padding: '10px 20px',
backgroundColor: '#ef4444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
marginTop: '20px'
}}
>
๋ก๊ทธ์์ ๐ช
</button>
</div>
);
}
}
export default App;
```
-
์ฑ ์คํ:
npm start
๋ธ๋ผ์ฐ์ ๊ฐ ์ด๋ฆฌ๋ฉด์
http://localhost:3000
์ Supabase ๋ก๊ทธ์ธ/ํ์๊ฐ์ UI๊ฐ ๋ํ๋๋ ๊ฒ์ ๋ณผ ์ ์์ต๋๋ค! โจ์ด๋ฉ์ผ ์ฃผ์์ ๋น๋ฐ๋ฒํธ๋ฅผ ์ ๋ ฅํ๊ณ
ํ์๊ฐ์
๋ฒํผ์ ๋๋ฅด๋ฉด, Supabase ๋์๋ณด๋์Authentication
ํญ์ ์๋ก์ด ์ฌ์ฉ์๊ฐ ์ถ๊ฐ๋ ๊ฒ์ ํ์ธํ ์ ์์ต๋๋ค. ๋ง์ฝ ์ด๋ฉ์ผ ํ์ธ์ ํ์ฑํํ๋ค๋ฉด, ํด๋น ์ด๋ฉ์ผ๋ก ํ์ธ ๋ฉ์ผ์ด ๋ฐ์ก๋ฉ๋๋ค.์์ ์๊ฐ: 5๋ถ! โฑ๏ธ ์ด๋ค๊ฐ์? ์ด ๊ฐ๋จํ ์ฝ๋ ๋ช ์ค๋ง์ผ๋ก ์ฌ์ฉ์ ๊ฐ์ , ๋ก๊ทธ์ธ, ์ธ์ ๊ด๋ฆฌ, ๋ก๊ทธ์์ ๊ธฐ๋ฅ๊น์ง ๋ชจ๋ ๊ตฌํํ์ต๋๋ค! ๋ฌผ๋ก ์ด๋ฉ์ผ ํ์ธ, ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฑ์ ๊ธฐ๋ฅ๋
Auth
์ปดํฌ๋ํธ๊ฐ ์๋์ผ๋ก ์ฒ๋ฆฌํด ์ค๋๋ค.
๐จ (์ต์ ) ์๋์ผ๋ก ๋ก๊ทธ์ธ/ํ์๊ฐ์ UI ๊ตฌํํ๊ธฐ (๋ ์ธ๋ฐํ ์ ์ด)
์์ Auth
์ปดํฌ๋ํธ๋ ๋งค์ฐ ํธ๋ฆฌํ์ง๋ง, ๋์์ธ ์ปค์คํฐ๋ง์ด์ง์ ์ ํ์ด ์์ ์ ์์ต๋๋ค. ์ข ๋ ์ปค์คํฐ๋ง์ด์ง๋ UI๋ฅผ ์ํ๋ค๋ฉด, Supabase JS ํด๋ผ์ด์ธํธ๋ฅผ ์ง์ ์ฌ์ฉํ์ฌ ๋ก๊ทธ์ธ ๊ธฐ๋ฅ์ ๊ตฌํํ ์ ์์ต๋๋ค.
// src/components/AuthForm.js (์ ํ์ผ ์์ฑ)
import React, { useState } from 'react';
import { supabase } from '../supabaseClient'; // 3๋จ๊ณ์์ ๋ง๋ supabaseClient ์ํฌํธ
function AuthForm() {
const [loading, setLoading] = useState(false);
const [email, setEmail] = useState('');
const [password, setPassword] = useState('');
const handleLogin = async (event) => {
event.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signInWithPassword({ email, password });
if (error) alert(error.message);
setLoading(false);
};
const handleSignup = async (event) => {
event.preventDefault();
setLoading(true);
const { error } = await supabase.auth.signUp({ email, password });
if (error) alert(error.message);
else alert('ํ์๊ฐ์
์ฑ๊ณต! ์ด๋ฉ์ผ์ ํ์ธํ์ฌ ์ธ์ฆ์ ์๋ฃํด์ฃผ์ธ์.');
setLoading(false);
};
return (
<div style={{ maxWidth: '400px', margin: '50px auto', padding: '20px', border: '1px solid #ddd', borderRadius: '8px', boxShadow: '0 2px 4px rgba(0,0,0,0.1)' }}>
<h2 style={{ textAlign: 'center', marginBottom: '30px', color: '#333' }}>๋ก๊ทธ์ธ ๋๋ ํ์๊ฐ์
</h2>
<form onSubmit={handleLogin}>
<div style={{ marginBottom: '15px' }}>
<label htmlFor="email" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>์ด๋ฉ์ผ:</label>
<input
id="email"
type="email"
placeholder="๋น์ ์ ์ด๋ฉ์ผ ์ฃผ์"
value={email}
onChange={(e) => setEmail(e.target.value)}
style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
</div>
<div style={{ marginBottom: '20px' }}>
<label htmlFor="password" style={{ display: 'block', marginBottom: '5px', fontWeight: 'bold' }}>๋น๋ฐ๋ฒํธ:</label>
<input
id="password"
type="password"
placeholder="๋น๋ฐ๋ฒํธ"
value={password}
onChange={(e) => setPassword(e.target.value)}
style={{ width: '100%', padding: '10px', border: '1px solid #ccc', borderRadius: '4px' }}
/>
</div>
<button
onClick={handleLogin}
disabled={loading}
style={{
width: '100%',
padding: '12px',
backgroundColor: '#28a745',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer',
marginBottom: '10px'
}}
>
{loading ? '๋ก๊ทธ์ธ ์ค...' : '๋ก๊ทธ์ธ'}
</button>
<button
onClick={handleSignup}
disabled={loading}
style={{
width: '100%',
padding: '12px',
backgroundColor: '#007bff',
color: 'white',
border: 'none',
borderRadius: '4px',
fontSize: '16px',
cursor: 'pointer'
}}
>
{loading ? 'ํ์๊ฐ์
์ค...' : 'ํ์๊ฐ์
'}
</button>
</form>
</div>
);
}
export default AuthForm;
์ด์ App.js
์์ Auth
์ปดํฌ๋ํธ ๋์ AuthForm
์ ์ฌ์ฉํฉ๋๋ค.
// src/App.js
import React, { useState, useEffect } from 'react';
import { supabase } from './supabaseClient';
import AuthForm from './components/AuthForm'; // ์๋ก ๋ง๋ AuthForm ์ํฌํธ
function App() {
const [session, setSession] = useState(null);
useEffect(() => {
supabase.auth.getSession().then(({ data: { session } }) => {
setSession(session);
});
const {
data: { subscription },
} = supabase.auth.onAuthStateChange((_event, session) => {
setSession(session);
});
return () => subscription.unsubscribe();
}, []);
if (!session) {
return <AuthForm />; // AuthForm ์ปดํฌ๋ํธ ๋ ๋๋ง
} else {
return (
<div style={{ width: '100%', maxWidth: '400px', margin: '50px auto', textAlign: 'center' }}>
<h2>ํ์ํฉ๋๋ค! ๐</h2>
<p>๋ก๊ทธ์ธ๋์์ต๋๋ค. ์ฌ์ฉ์ ์ด๋ฉ์ผ: <strong>{session.user.email}</strong></p>
<button
onClick={() => supabase.auth.signOut()}
style={{
padding: '10px 20px',
backgroundColor: '#ef4444',
color: 'white',
border: 'none',
borderRadius: '5px',
cursor: 'pointer',
marginTop: '20px'
}}
>
๋ก๊ทธ์์ ๐ช
</button>
</div>
);
}
}
export default App;
์ด๋ ๊ฒ ํ๋ฉด ์ํ๋ ๋๋ก ๋์์ธ์ ์์ ๋กญ๊ฒ ๋ณ๊ฒฝํ๋ฉด์ Supabase์ ๊ฐ๋ ฅํ ์ธ์ฆ ๊ธฐ๋ฅ์ ํ์ฉํ ์ ์์ต๋๋ค.
๐ ๋ค์ ๋จ๊ณ: ๋ก๊ทธ์ธ ์์คํ ์ ๋์ด!
5๋ถ ๋ง์ ๋ก๊ทธ์ธ ์์คํ ์ ๊ตฌ์ถํ์ง๋ง, Supabase์ ์ง์ ํ ํ์ ์ฌ๊ธฐ์๋ถํฐ ์์๋ฉ๋๋ค!
-
Row Level Security (RLS) ์ค์ :
- ๊ฐ์ฅ ์ค์ํฉ๋๋ค! Supabase ํ ์ด๋ธ์ ๋ฐ์ดํฐ๋ฅผ ์์ ํ๊ฒ ๋ณดํธํ๋ ค๋ฉด ๋ฐ๋์ RLS๋ฅผ ์ค์ ํด์ผ ํฉ๋๋ค.
Database
->Table Editor
์์ ํน์ ํ ์ด๋ธ์ ์ ํํ๊ณPolicies
ํญ์ผ๋ก ์ด๋ํ์ฌ RLS ์ ์ฑ ์ ์ถ๊ฐํฉ๋๋ค.- ์๋ฅผ ๋ค์ด, “์ฌ์ฉ์ ๋ณธ์ธ์ ํ๋กํ๋ง ์์ ๊ฐ๋ฅ”๊ณผ ๊ฐ์ ์ ์ฑ ์ ์ฝ๊ฒ ๊ตฌํํ ์ ์์ต๋๋ค. ๐
-
์ฌ์ฉ์ ํ๋กํ ํ ์ด๋ธ ์ฐ๋:
auth.users
ํ ์ด๋ธ์ Supabase๊ฐ ๊ด๋ฆฌํ๋ ์ธ์ฆ ์ ๋ณด์ ๋๋ค.- ์ฌ์ฉ์ ์ด๋ฆ, ํ๋กํ ์ฌ์ง, ๊ธฐํ ์์ธ ์ ๋ณด๋ฅผ ์ ์ฅํ๋ ค๋ฉด ๋ณ๋์
public.profiles
ํ ์ด๋ธ์ ๋ง๋ค๊ณauth.users
ํ ์ด๋ธ์id
์ ์ฐ๊ฒฐํ๋ ๊ฒ์ด ์ผ๋ฐ์ ์ ๋๋ค. (One-to-one ๊ด๊ณ) ๐งโ๐ป
-
๋น๋ฐ๋ฒํธ ์ฌ์ค์ ๋ฐ ๋ณ๊ฒฝ:
- Supabase ์ธ์ฆ์ ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ด๋ฉ์ผ ์ ์ก ๊ธฐ๋ฅ๋ ์ ๊ณตํฉ๋๋ค.
supabase.auth.resetPasswordForEmail('user@example.com')
๊ฐ์ ํจ์๋ฅผ ์ฌ์ฉํฉ๋๋ค. ๐
- Supabase ์ธ์ฆ์ ๋น๋ฐ๋ฒํธ ์ฌ์ค์ ์ด๋ฉ์ผ ์ ์ก ๊ธฐ๋ฅ๋ ์ ๊ณตํฉ๋๋ค.
-
์์ ๋ก๊ทธ์ธ ์ฐ๋:
Auth
์ปดํฌ๋ํธ์providers
์์ฑ์ ํ์ฉํ๊ฑฐ๋,supabase.auth.signInWithOAuth({ provider: 'google' })
์ ๊ฐ์ ํจ์๋ฅผ ์ง์ ํธ์ถํ์ฌ Google, GitHub ๋ฑ ๋ค์ํ ์์ ๋ก๊ทธ์ธ์ ์ถ๊ฐํ ์ ์์ต๋๋ค. ๐
-
์๋ก์ด ๊ธฐ๋ฅ ํ์ฅ:
- ์ค์๊ฐ ๊ตฌ๋ (Realtime): ์ฑํ ์ฑ์ด๋ ์๋ฆผ ์์คํ ์ ๊ตฌ์ถํ ๋ ์ ์ฉํฉ๋๋ค. ๐ฌ
- ์คํ ๋ฆฌ์ง(Storage): ์ฌ์ฉ์ ํ๋กํ ์ฌ์ง, ๊ฒ์๋ฌผ ์ด๋ฏธ์ง ๋ฑ์ ์ ์ฅํ๊ณ ๊ด๋ฆฌํ ์ ์์ต๋๋ค. ๐ผ๏ธ
- ์ฃ์ง ํจ์(Edge Functions): ๋ณต์กํ ๋ฐฑ์๋ ๋ก์ง์ด๋ ์๋ํํฐ API ์ฐ๋์ ์ํ ์๋ฒ๋ฆฌ์ค ํจ์๋ฅผ ๋ฐฐํฌํฉ๋๋ค. ๐ก
โจ ๋ง๋ฌด๋ฆฌํ๋ฉฐ
์ค๋ ์ฐ๋ฆฌ๋ Supabase๋ฅผ ์ฌ์ฉํ์ฌ ๋จ 5๋ถ ๋ง์ ๊ธฐ๋ณธ์ ์ธ ๋ก๊ทธ์ธ ์์คํ ์ ๊ตฌ์ถํ๋ ๋๋ผ์ด ๊ฒฝํ์ ํ์ต๋๋ค. ๋ณต์กํ ๋ฐฑ์๋ ์ค์ ์์ด๋, Supabase์ ๊ฐ๋ ฅํ ๊ธฐ๋ฅ์ ํตํด ๊ฐ๋ฐ ์์ฐ์ฑ์ ๊ทน๋ํํ ์ ์๋ค๋ ๊ฒ์ ํ์ธํ์ฃ .
Supabase๋ ๋จ์ํ Firebase ๋์์ด ์๋๋ผ, ์คํ์์ค์ ์ด์ ์ ์ด๋ฆฌ๋ฉด์๋ ํ๋์ ์ธ ์น/์ฑ ๊ฐ๋ฐ์ ํ์ํ ๋ชจ๋ ๋ฐฑ์๋ ๊ธฐ๋ฅ์ ์ ๊ณตํ๋ ์๋ฒฝํ ํ๋ซํผ์ ๋๋ค. ์ด์ ์ด ๊ฐ๋ ฅํ ๋๊ตฌ๋ฅผ ๋ฐํ์ผ๋ก ์ฌ๋ฌ๋ถ๋ง์ ๋ฉ์ง ์์ด๋์ด๋ฅผ ํ์ค๋ก ๋ง๋ค์ด ๋ณด์ธ์! ๐
๊ถ๊ธํ ์ ์ด ์๋ค๋ฉด ์ธ์ ๋ ์ง Supabase ๋ฌธ์๋ฅผ ์ฐธ์กฐํ๊ฑฐ๋ ์ปค๋ฎค๋ํฐ์ ์ง๋ฌธํด๋ณด์ธ์. ์ฆ๊ฑฐ์ด ์ฝ๋ฉ ๋์ธ์! Happy Hacking! ๐