본문으로 건너뛰기

이메일·비밀번호 회원 인증

이메일·비밀번호 회원 인증

텍스트로만 진행되는 튜토리얼입니다 · 예상 소요 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' } })로 임의 키-값 저장

이 튜토리얼이 도움이 됐나요?