Back
ESSAY비개발자를 위한 바이브코딩 안내서
DEC 25, 2025

Part 4.4 인증과 보안: 이것만은 알고가자

요새 보안 사고가 참 많이 들리죠? 빅테크 뿐 아니라, 국내 IT대기업에서도 보안 문제가 빈번하게 일어나고 있습니다. 하물여 저희가 만드는 바이브코딩의 결과물은 어떨까요? 정말 고도의 보안전략은 아니지만, 기본적으로 챙길 수 있는 것들은 챙겨야 합니다. 하지만, 많은 비개발자분들은 '그런 요소'가 있는 지 조차 모르기 때문에 챙기기가 힘든 게 사실입니다. 오늘 연재글로 어떤 것을 챙겨야 하는지 잘 파악해보시고, 꼭 적용해보시기 바랍니다.

AI는 "일단 작동하게" 만드는 데 집중합니다. 보안은 직접 챙겨야 합니다. 프롬프트로도 보안을 신경쓰라고 명령할 수 있지만, 반드시 한 번 더 확인을 해야합니다.

6가지 필수 보안 원칙

1. 사용자 인증: 누가 쓰고 있는지 확인하기

**인증(Authentication)**은 "이 사용자가 누구인지" 확인하는 것입니다.

왜 필요한가?

인증 없이 만들면:

  • 아무나 다른 사람 데이터를 볼 수 있음

  • 아무나 데이터를 수정/삭제할 수 있음

  • 누가 뭘 했는지 추적 불가

Supabase Auth 사용하기

Supabase에는 인증 기능이 내장되어 있습니다.

회원가입:

const { data, error } = await supabase.auth.signUp({
  email: 'user@email.com',
  password: 'securepassword123'
});

로그인:

const { data, error } = await supabase.auth.signInWithPassword({
  email: 'user@email.com',
  password: 'securepassword123'
});

로그아웃:

const { error } = await supabase.auth.signOut();

현재 로그인한 사용자 확인:

const { data: { user } } = await supabase.auth.getUser();

if (user) {
  console.log('로그인됨:', user.email);
} else {
  console.log('로그인 안 됨');
}

로그인 상태 유지하기

// App.tsx 또는 최상위 컴포넌트에서
useEffect(() => {
  // 현재 세션 확인
  supabase.auth.getSession().then(({ data: { session } }) => {
    setUser(session?.user ?? null);
  });

  // 인증 상태 변화 감지
  const { data: { subscription } } = supabase.auth.onAuthStateChange(
    (_event, session) => {
      setUser(session?.user ?? null);
    }
  );

  return () => subscription.unsubscribe();
}, []);

로그인한 사용자만 접근 허용

// 페이지나 컴포넌트에서
function Dashboard() {
  const [user, setUser] = useState(null);
  const [loading, setLoading] = useState(true);

  useEffect(() => {
    supabase.auth.getUser().then(({ data: { user } }) => {
      setUser(user);
      setLoading(false);
    });
  }, []);

  if (loading) return <div>로딩 중...</div>;
  if (!user) return <div>로그인이 필요합니다.</div>;

  return <div>환영합니다, {user.email}님!</div>;
}

2. API 키 숨기기: 절대 코드에 직접 쓰지 마세요

API 키는 외부 서비스(Supabase, OpenAI 등)에 접근하는 "비밀번호"입니다.

왜 위험한가?

// ❌ 절대 금지! 이렇게 하면 안 됩니다
const openaiKey = "sk-proj-abc123...";  // 머스크만큼 돈 많으면 올리셔도 됩니다

코드에 API 키를 직접 쓰면:

  • GitHub에 올라가면 전 세계에 공개

  • 악의적인 사용자가 내 API 키로 요금 폭탄 유발

  • 몇 분 만에 수백만 원 청구 사례도 있음

어떻게 숨기나?

환경 변수를 사용합니다 (다음 섹션에서 자세히).

// ✅ 올바른 방법
const openaiKey = process.env.OPENAI_API_KEY;

클라이언트 vs 서버 키

Supabase에는 두 종류의 키가 있습니다:

키 종류용도노출 여부
anon (public)클라이언트에서 사용노출 OK (제한된 권한)
service_role서버에서만 사용절대 노출 금지
// 클라이언트 코드에서는 anon 키만!
const supabase = createClient(
  process.env.NEXT_PUBLIC_SUPABASE_URL,
  process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY  // anon 키
);

service_role 키는 모든 보안을 우회할 수 있어서, 서버 코드에서만 사용해야 합니다.

이외에도 자신이 사용하는 모든 클라우드 관련한 api키 ( secret키 등 ) 를 전부, 반드시, 꼭 숨겨야 합니다.


3. 환경 변수: 비밀 정보 관리하기

환경 변수는 코드와 분리해서 비밀 정보를 저장하는 방법입니다.

.env 파일 만들기

프로젝트 루트에 .env 파일을 만듭니다:

# .env
NEXT_PUBLIC_SUPABASE_URL=https://xxx.supabase.co
NEXT_PUBLIC_SUPABASE_ANON_KEY=eyJhbGc...
OPENAI_API_KEY=sk-proj-...

이름 규칙 (Next.js/React):

  • NEXT_PUBLIC_ 접두사: 브라우저에서 사용 가능 (공개되어도 되는 것만)

  • 접두사 없음: 서버에서만 사용 (비밀 유지)

.gitignore에 추가하기

중요! .env 파일이 GitHub에 올라가지 않도록 .gitignore에 추가합니다.

# .gitignore
.env
.env.local
.env.production

코드에서 사용하기

// 환경 변수 읽기
const apiUrl = process.env.NEXT_PUBLIC_SUPABASE_URL;
const apiKey = process.env.NEXT_PUBLIC_SUPABASE_ANON_KEY;

배포할 때 환경 변수 설정

로컬의 .env 파일은 배포 환경에 자동으로 안 올라갑니다. 배포 플랫폼에서 따로 설정해야 합니다.

Vercel:

  1. 프로젝트 대시보드 → Settings → Environment Variables

  2. 각 변수 이름과 값 입력

  3. 저장 후 재배포

Netlify:

  1. Site settings → Environment variables

  2. Add a variable로 추가


4. 입력 검증: 사용자 입력을 믿지 마세요

사용자가 입력하는 모든 것은 잠재적으로 위험합니다.

검증 없이 받으면:

  • 빈 값이 DB에 저장됨

  • 예상과 다른 형식의 데이터가 들어옴

  • 악의적인 코드가 삽입될 수 있음

클라이언트 측 검증

사용자에게 즉각적인 피드백을 주기 위한 검증입니다.

const handleSubmit = (e) => {
  e.preventDefault();
  
  // 빈 값 체크
  if (!email || !password) {
    setError('이메일과 비밀번호를 입력하세요.');
    return;
  }
  
  // 이메일 형식 체크
  if (!email.includes('@')) {
    setError('올바른 이메일 형식이 아닙니다.');
    return;
  }
  
  // 비밀번호 길이 체크
  if (password.length < 8) {
    setError('비밀번호는 8자 이상이어야 합니다.');
    return;
  }
  
  // 검증 통과, 서버로 전송
  submitToServer(email, password);
};

서버 측 검증 (더 중요!)

클라이언트 검증은 우회 가능합니다. 브라우저 개발자 도구로 조작할 수 있어요.

그래서 서버에서도 반드시 검증해야 합니다.

// API 라우트 또는 서버 함수에서
export async function POST(request) {
  const { email, password } = await request.json();
  
  // 서버에서 다시 검증!
  if (!email || !email.includes('@')) {
    return Response.json({ error: '유효하지 않은 이메일' }, { status: 400 });
  }
  
  if (!password || password.length < 8) {
    return Response.json({ error: '비밀번호는 8자 이상' }, { status: 400 });
  }
  
  // 검증 통과 후 처리
}

숫자 입력 검증

// 가격, 수량 등 숫자 입력
const quantity = parseInt(inputValue, 10);

if (isNaN(quantity) || quantity < 1 || quantity > 100) {
  setError('1~100 사이의 숫자를 입력하세요.');
  return;
}

5. XSS와 SQL Injection: 코드 삽입 공격 막기

XSS (Cross-Site Scripting)

XSS는 악의적인 스크립트를 웹페이지에 삽입하는 공격입니다.

<!-- 사용자가 이름 입력란에 이걸 입력하면? -->
<script>alert('해킹!')</script>

검증 없이 이걸 화면에 그대로 표시하면, 스크립트가 실행됩니다.

React의 기본 보호:

다행히 React는 기본적으로 XSS를 방어합니다.

// React가 자동으로 이스케이프 처리
const userInput = "<script>alert('hack')</script>";
return <div>{userInput}</div>;  // 텍스트로 표시됨, 실행 안 됨

위험한 경우 - dangerouslySetInnerHTML:

// ❌ 위험! 사용자 입력을 이렇게 쓰면 안 됨
<div dangerouslySetInnerHTML={{ __html: userInput }} />

// ✅ 정말 필요하면 sanitize 라이브러리 사용
import DOMPurify from 'dompurify';
<div dangerouslySetInnerHTML={{ __html: DOMPurify.sanitize(userInput) }} />

SQL Injection

SQL Injection은 데이터베이스 쿼리에 악의적인 SQL을 삽입하는 공격입니다.

Supabase를 예시로 설명하고 있지만, 꼭 다른 Injection에 대해서도 LLM과 함께 대화하면서 더 알아가 보세요. 제일 좋은 거는 현재 여러분의 코드베이스를 기준으로 LLM과 함께 알아가는겁니다.

Supabase 클라이언트를 사용하면 자동으로 방어됩니다.

// ✅ 안전 - Supabase가 자동으로 처리
const { data } = await supabase
  .from('users')
  .select('*')
  .eq('email', userInput);  // userInput이 자동으로 이스케이프됨

위험한 경우 - 직접 SQL 작성:

// ❌ 위험! 절대 이렇게 하지 마세요
const { data } = await supabase.rpc('raw_query', {
  query: `SELECT * FROM users WHERE email = '${userInput}'`
});

// ✅ 파라미터 바인딩 사용
const { data } = await supabase.rpc('get_user', {
  user_email: userInput  // 안전하게 전달
});

6. HTTPS: 암호화된 통신

HTTPS는 브라우저와 서버 사이의 통신을 암호화합니다.

HTTP vs HTTPS

HTTPHTTPS
암호화 없음암호화됨
중간에서 데이터 엿볼 수 있음중간에서 못 봄
http://https:// 🔒

로그인 정보, 결제 정보 등이 암호화 없이 전송되면 누군가 가로챌 수 있습니다.

Vercel, Netlify 등 대부분의 배포 플랫폼은 HTTPS를 자동으로 제공합니다.

확인할 것:

  • 배포된 URL이 https://로 시작하는지

  • 브라우저 주소창에 🔒 자물쇠가 있는지

혼합 콘텐츠(Mixed Content) 주의:

HTTPS 사이트에서 HTTP 리소스를 불러오면 경고가 뜹니다.

// ❌ HTTPS 사이트에서 HTTP 이미지
<img src="http://example.com/image.jpg" />

// ✅ HTTPS 사용
<img src="https://example.com/image.jpg" />

Thank you for reading.

Based in Seoul
Since 2024