# [인벤션덱 시리즈] EP.4 | 회원가입은요? 보안은요? Supabase 웹앱에 연동하기

![Image](https://upload.cafenono.com/image/slashpagePost/20251023/110506_jAmVo9yRJwpOcuyqCN?q=80&s=1280x180&t=outside&f=webp)

MVP를 만들고나니 금세 또 다른 고민이 생겼습니다. 🤔

**'누가 이 서비스를 사용하고 있는지, 어떤 카드를 조합하는지, 어떤 아이디어를 만들고 있는지'**를 알 수 있어야 진짜 서비스다운 서비스 운영이 가능하다고 생각했기 때문이죠. 그래서 저희는 회원가입 기능을 추가하기로 마음 먹었습니다. 

# 🔒 회원가입 서비스도 너무 많아서..

![Image](https://upload.cafenono.com/image/slashpagePost/20251027/140118_2hXWYPZfxxVgvjgeRa?q=80&s=1280x180&t=outside&f=webp)

다양한 서비스를 찾다가 **Firebase**에서 제공하는 회원가입 기능을 발견했습니다. 저희 서비스의 기반이 되는 Google에서 제공하는 서비스이고 글로벌하게 사용되는 만큼 안전성이 보장되기 때문에 우리가 찾던 서비스라고 생각했습니다. 하지만 실제로 적용하려고 하니 생각보다 초기 세팅이 복잡했고 프론트엔드와 백엔드를 이해해야 하는 부분이 부담되었습니다. (진짜 개발자가 아니어서…) 무엇보다 빠르게 기능을 고도화 해야 하는 상황에서 다소 과한 기능이 많다고 느껴졌습니다. 

[Firebase Authentication | Simple, multi-platform sign-in](https://firebase.google.com/products/auth?hl=ko)

다음으로는 한 스타트업 대표님께 추천받은 인증 솔루션인 **Clerk**을 확인해보았습니다. UI가 직관적이고 설정도 간편해 사용하기에 좋아보였지만 MVP 수준에서 사용하기엔 다소 무겁게 느껴졌습니다. 기능이 워낙 잘 갖춰져 있다 보니 오히려 저희처럼 단순한 인증만 필요한 초기 단계 팀에는 과도한 설정과 리소스 소모로 이어질 수 있겠다는 판단이 들었습니다.

[Clerk | Authentication and User Management](https://clerk.com/)

그 다음 발견한 **Supabase**는 Firebase에 비해 훨씬 간단하고 직관적이었습니다. 데이터베이스, 인증, 스토리지를 한 번에 제공해주었고 SQL 기반이라 테이블을 손쉽게 다룰 수 있었습니다. 조금 사용하다보니 저희같은 비개발자도 쉽게 사할 수 있어 최종적으로 회원가입은 Supabase를 활용하기로 결정했습니다 👏🏻

[Supabase | The Postgres Development Platform.](https://supabase.com/)

# 😞 무한 시행착오

그런데 기능을 하나씩 추가하면서 예상치 못한 어려움이 생기기 시작했습니다. 회원 정보를 담은 테이블을 만들고 '찜한 카드 목록', '저장한 아이디어'와 같은 다른 테이블들과 연결하는 과정에서 오류가 연달아 발생했습니다. Supabase에서 제공하는 관계형 데이터 구조가 처음에는 굉장히 직관적이고 간편하게 느껴졌지만 실제로 관계를 설정하는 과정이 결코 간단하지 않았습니다. 

![Image](https://upload.cafenono.com/image/slashpagePost/20251027/140339_jZ7o2E2CaE5iASI0zI?q=80&s=1280x180&t=outside&f=webp)

복잡한 관계형 DB (출처 : [https://taesang-jo.blogspot.com/2017/09/11.html](https://taesang-jo.blogspot.com/2017/09/11.html)) 

특히 외래 키를 통해 연결된 테이블 간의 권한을 설정하다 보니 자연스럽게 RLS(Row Level Security)라는 개념을 마주하게 되었는데요, RLS는 간단히 말해 '**누가 어떤 데이터를 다룰 수 있는지**'를 조절하는 보안 정책입니다. 처음 이 개념을 접했을 때 굉장히 혼란스러웠는데요, 조건을 하나라도 잘못 걸면 데이터가 전부 노출되거나 아예 나오지 않기도 했습니다. 

> **RLS(Row Level Security) 
> **: 오픈 소스 PostgreSQL DB에서 제공하는 보안 기능으로 데이터베이스 관리자는 특정 데이터 행에 대해 하나 이상의 역할이 표시되고 작동하는 방식을 제어하는 정책을 정의하는 것으로 select, insert, update, delete와 같은 특정 명령을 포함하여 테이블의 행 수준에서 세분화된 액세스 제어를 구현할 수 있는 기능.

결국 AI Studio의 Code Assistant와 대화하면서 하나하나 고쳐나갔고 그 덕분에 '백엔드란 이런거구나, 서비스 운영에는 데이터 설계와 권한 관리가 진짜 중요하구나'하는 인사이트를 얻을 수 있었습니다. 

# 😱 Gemini API 키가 노출되다 

어느 날, 예상치 못한 문제가 또 하나 발견되었습니다. 배포한 인벤션덱을 확인하던 중 Gemini API 키가 그대로 노출되어있었던 것입니다. 웹앱 코드 내부에 API키가 그대로 포함되어 있었기 때문에 누구든지 개발자 도구를 열면 해당 키를 볼 수 있는 상태였던 것이죠. 이건 사실상 보안 이슈를 넘어서는 아주 중대한 문제였습니다. Gemini API 키는 사용량에 따라 과금되는 구조이기 때문에 만약 다른 사람이 무단으로 이 키를 사용해 AI를 호출한다면…? 말 그대로 과금 폭탄을 맞을 수 있는 상황이었습니다. 

서비스를 외부에 공개해야 하는 이상, 무조건 키를 숨기는 방법을 찾아야 했습니다. 여러 방법을 찾은 끝에 역시나 답은 또다시 Supabase에 있었습니다. Supabase에서 제공하는 **Edge Function**을 활용하면 키를 안전하게 서버 측으로 옮기고 공개적으로 노출하지 않을 수 있다는 것을 알게 되었습니다. 

이후에는 모든 Gemini API 요청을 Supabase의 Edge Function을 통해 처리하도록 변경했습니다. 덕분에 코드가 훨씬 안전해졌고 구조도 보다 명확하게 정리되었습니다. 

# 🤖 이후로도 AI와 함께 문제를 풀어나가다

물론 그 뒤로도 여러 번의 시행착오가 있었습니다. 하지만 이번에는 외주에 의존하지 않고 저희 스스로 하나씩 직접 해결해나갔습니다. 다행히 Google AI Studio의 Code Assistant가 든든한 파트너가 되어주었는데요! 에러 메시지를 보여주며 "이건 왜 안되지?", "지금 이 부분이 문제인 것 같은데 해결 방법을 알려줘" 라고 물으면 적절한 코드나 해결 방안을 제안해주었습니다. 

덕분에 처음에는 생소하게 느껴졌던 코드도 점차 익숙해졌습니다. 작은 수정이라도 하나하나 직접 해보면서 기능이 실제로 구현되는 과정을 확인할 수 있었습니다. 그러다보니 코드 자체를 점점 더 자주 들여다보게 되었고 특정 에러가 발생했을 때 어디서 문제가 생긴 건지 추적하고 고쳐보는 일도 자연스럽게 하게 되었습니다. 물론 전문 개발자는 아니지만 로직의 흐름, 테이블 간의 연결 방식 등을 이해할 수 있을 만큼은 보이기 시작했습니다. 

# 🪙 더 나아가 플랜별 기능 제한 구현까지!

서비스의 기능들이 안정적으로 작동하자 슬슬 서비스의 운영 방식을 고민하기 시작했습니다. 가장 먼저 든 생각은 '모든 사용자가 똑같은 기능을 쓰는게 맞을까?' 였습니다. 기획 차원에서 의문도 있었지만 현실적인 이유도 있었습니다. Gemini API 는 사용량에 따라 실제 비용이 발생하고 템플릿 기능의 경우 자사에서 직접 기획하고 제작한 콘텐츠라서 누구나 자유롭게 쓸 수 있게 열어두는 것도 조심스러웠기 때문입니다. 

그래서 결국 사용자의 이용 목적과 서비스 운영의 지속 가능성을 모두 고려해 플랜을 설계하고 플랜별로 기능을 제한하기로 했습니다. 무료 사용자는 카드를 조합해 아이디어를 생성하는 기능까지, 유료 사용자는 모든 기능을 사용할 수 있도록 기능 범위를 구분한 것이죠! 이 역시 Supabase를 활용해 구현했습니다.

![Image](https://upload.cafenono.com/image/slashpagePost/20251027/140553_EvsMFAcc4VCvspj8pc?q=80&s=1280x180&t=outside&f=webp)

 실제 세팅한 플랜 

사용자 플랜과 사용량 데이터를 담은 테이블을 별도로 만들고 각 사용자의 요청 흐름과 기능 접근 조건에 따라 해당 정보를 참조하게 만들었습니다. 

> 로그인 시 사용자의 user_id에 연결된 플랜을 불러오고

> 기능 실행 시마다 사용량을 체크한 뒤

> 무료 플랜이면 사용량 제한 초과 여부를 판단하고,

> 유료 플랜이면 모든 기능을 자유롭게 이용 가능하도록 하는 식입니다!

이 과정을 모두 Supabase의 RLS와 인증 로직을 연결해 깔끔하게 구성할 수 있었습니다.

![Image](https://upload.cafenono.com/image/slashpagePost/20251023/111902_iZejb9aY1naZ4bUPDI?q=80&s=1280x180&t=outside&f=webp)

인벤션덱의 전체 DB

## ✨ 마무리하며..

EP.4는 회원가입, 보안, 데이터 관리라는 현실적인 과제들을 마주한 과정이었습니다. 기능 하나를 만들 때마다 새로운 문제들이 튀어나왔고 처음 보는 에러 메시지에 당황하기도 했었습니다. 하지만 하나씩 해결해 나가다 보니 서비스는 어느새 더 단단하고 정교한 모습으로 다듬어지고 있었습니다. 

요즘은 바이브 코딩으로 비개발자분들도 자신만의 서비스를 직접 기획하고 만들어가는 경우가 많은데요, 독자 여러분께서도 비슷한 고민을 하고 계시다면 이 에피소드가 작게나마 도움이 되시길 바랍니다!

---

👉 다음편인 Ep.5에서는 완벽하지 않아도 괜찮아! 인벤션덱AI 첫 공개를 연재할 예정입니다 👏🏻

실제 인벤션덱AI의 URL이 궁금하시다면 꼭 기대해주세요!

For the site tree, see the [root Markdown](https://slashpage.com/theilab-blog.md).
