이메일·비밀번호 회원 인증
이메일·비밀번호 회원 인증
텍스트로만 진행되는 튜토리얼입니다 · 예상 소요 8분
입문인증8분읽기 약 3분업데이트 2026-04-14
회원가입·로그인·세션 유지·로그아웃·보호 영역까지 직접 만드는 인증 입문 가이드입니다.
먼저 완료하면 좋은 튜토리얼이 있어요
이 튜토리얼에서 배울 내용
이메일·비밀번호 회원가입 / 로그인
세션 유지 및 복원 (getMe)
로그아웃 및 보호 영역 구현
게스트 세션과 소셜 로그인 연계
이메일·비밀번호 회원 인증
소셜 로그인이 Google·Naver 계정으로 로그인하는 방법이라면, 이 튜토리얼은 앱이 직접 아이디(이메일)와 비밀번호로 회원을 관리하는 방법입니다. 회원가입 → 로그인 → 세션 유지 → 로그아웃 → 로그인한 사용자만 접근하는 보호 영역까지 한 번에 만듭니다.
💡 ConnectBase에는 두 종류의 "사용자"가 있습니다.
- 콘솔 계정: 당신(개발자)이 ConnectBase에 로그인하는 계정
- 앱 멤버(App Member): 당신이 만든 앱의 최종 사용자 — 이 튜토리얼에서 다루는 대상입니다
완성된 기능
- 이메일·비밀번호 회원가입
- 로그인 / 로그아웃
- 새로고침해도 유지되는 세션 (SDK가 토큰을 자동 저장)
- 로그인한 사용자만 보이는 보호 영역
사전 준비
- ConnectBase 콘솔에서 앱을 하나 만들고 Public Key(
cb_pk_*) 를 발급받습니다 (Public Key 발급과 관리 참고) - 콘솔 → 인증 설정에서 아이디/비밀번호 로그인이 활성화되어 있는지 확인합니다
1. 프로젝트 설정
bash
npm create vite@latest auth-app -- --template react-ts
cd auth-app
npm install connectbase-client.env 파일에 Public Key를 입력합니다:
VITE_CONNECT_BASE_PUBLIC_KEY=cb_pk_여기에_퍼블릭키_입력
2. SDK 설정
src/lib/connectbase.ts:
typescript
import { ConnectBase } from 'connectbase-client'
export const cb = new ConnectBase({
publicKey: import.meta.env.VITE_CONNECT_BASE_PUBLIC_KEY
})3. 인증 훅 만들기
회원가입·로그인·로그아웃과 세션 복원을 한 곳에서 처리하는 훅을 만듭니다.
signUpMember({ login_id, password, nickname? })— 회원가입.login_id는 이메일을 그대로 써도 됩니다.signInMember({ login_id, password })— 로그인getMe()— 현재 로그인된 멤버 정보 조회 (세션 복원용)signOut()— 로그아웃
📌
signUpMember/signInMember는 성공 시 access/refresh 토큰을 SDK가 자동으로 저장합니다. 그래서 새로고침 후getMe()만 호출하면 세션이 복원됩니다.
src/hooks/useAuth.ts:
typescript
import { useState, useEffect } from 'react'
import type { MemberInfoResponse } from 'connectbase-client'
import { cb } from '../lib/connectbase'
export function useAuth() {
const [user, setUser] = useState<MemberInfoResponse | null>(null)
const [loading, setLoading] = useState(true)
// 앱이 처음 로드될 때 저장된 토큰으로 세션을 복원합니다.
// 토큰이 없거나 만료되면 getMe()가 에러를 던지므로 catch에서 null 처리합니다.
useEffect(() => {
cb.auth
.getMe()
.then(setUser)
.catch(() => setUser(null))
.finally(() => setLoading(false))
}, [])
const signUp = async (email: string, password: string, nickname: string) => {
// login_id로 이메일을 그대로 사용합니다
await cb.auth.signUpMember({ login_id: email, password, nickname })
setUser(await cb.auth.getMe())
}
const signIn = async (email: string, password: string) => {
await cb.auth.signInMember({ login_id: email, password })
setUser(await cb.auth.getMe())
}
const signOut = async () => {
await cb.auth.signOut()
setUser(null)
}
return { user, loading, signUp, signIn, signOut }
}4. 로그인·회원가입 폼
src/pages/AuthPage.tsx:
tsx
import { useState } from 'react'
import { ApiError } from 'connectbase-client'
import { useAuth } from '../hooks/useAuth'
export function AuthPage() {
const { user, loading, signUp, signIn, signOut } = useAuth()
const [mode, setMode] = useState<'signin' | 'signup'>('signin')
const [email, setEmail] = useState('')
const [password, setPassword] = useState('')
const [nickname, setNickname] = useState('')
const [error, setError] = useState('')
if (loading) return <p>세션 확인 중...</p>
// 로그인된 상태 — 보호 영역
if (user) {
return (
<div>
<h1>{user.nickname}님, 환영합니다 🎉</h1>
<button onClick={signOut}>로그아웃</button>
</div>
)
}
const handleSubmit = async (e: React.FormEvent) => {
e.preventDefault()
setError('')
try {
if (mode === 'signup') await signUp(email, password, nickname)
else await signIn(email, password)
} catch (err) {
// ApiError에는 statusCode가 들어있어 원인별로 분기할 수 있습니다
if (err instanceof ApiError && err.statusCode === 401) {
setError('이메일 또는 비밀번호가 올바르지 않습니다.')
} else if (err instanceof ApiError && err.statusCode === 409) {
setError('이미 가입된 이메일입니다.')
} else {
setError('문제가 발생했습니다. 잠시 후 다시 시도해주세요.')
}
}
}
return (
<form onSubmit={handleSubmit}>
<h1>{mode === 'signup' ? '회원가입' : '로그인'}</h1>
<input value={email} onChange={(e) => setEmail(e.target.value)} placeholder="이메일" />
<input value={password} onChange={(e) => setPassword(e.target.value)} type="password" placeholder="비밀번호" />
{mode === 'signup' && (
<input value={nickname} onChange={(e) => setNickname(e.target.value)} placeholder="닉네임" />
)}
<button type="submit">{mode === 'signup' ? '가입하기' : '로그인'}</button>
{error && <p style={{ color: 'red' }}>{error}</p>}
<button type="button" onClick={() => setMode(mode === 'signup' ? 'signin' : 'signup')}>
{mode === 'signup' ? '이미 계정이 있어요' : '계정 만들기'}
</button>
</form>
)
}5. 실행하기
bash
npm run dev회원가입 후 페이지를 새로고침해도 로그인 상태가 유지되는지 확인해보세요 — SDK가 토큰을 저장해 getMe()로 세션을 복원합니다.
다음 단계
- 소셜 로그인 함께 제공 — 소셜 로그인 튜토리얼의
cb.oauth.signInWithPopup()을 같은 화면에 추가 - 사용자별 데이터 — 로그인한 멤버의
member_id로 데이터베이스에 본인 데이터만 저장/조회 - 추가 정보 저장 —
cb.auth.updateCustomData({ custom_data: { theme: 'dark' } })로 임의 키-값 저장
이 튜토리얼이 도움이 됐나요?