Supabase Auth + 추가 정보 저장 문제 해결기

김태민·2026년 2월 3일

문제 상황

Supabase Authentication을 사용해서 회원가입을 구현하는데, auth.users 테이블 외에 추가 사용자 정보(이름, 대학교, 구독 정보 등)를 별도 테이블에 저장하고 싶었습니다.

필요사항

  • Supabase Auth로 인증 처리 (자체 구현보다 안전하다고 판단)
  • 추가 정보는 profiles 테이블에 저장
  • 대학교 이메일 인증 기능 (특정 대학교의 학생들만 사용해야 하기 때문에, 학교 이메일을 사용)

1차 시도: Update 방식

구현 방법

// 1. Supabase Auth로 회원가입
const { data, error } = await supabase.auth.signUp({
  email: fullEmail,
  password: password,
});

// 2. profiles 테이블에 추가 정보 저장
const { error: profileError } = await supabase
  .from("profiles")
  .update({ name, university })
  .eq("id", data.user.id);

Database 설정

트리거: auth.users에 유저 생성 시 자동으로 profiles 레코드 생성

create function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, email)
  values (new.id, new.email);
  return new;
end;
$$ language plpgsql security definer;

create trigger on_auth_user_created
  after insert on auth.users
  for each row execute procedure public.handle_new_user();

문제 발생: 빈 배열 반환

// Console 출력
=== profiles 테이블 업데이트 결과 ===
Profile Data: []  // 빈 배열!
Profile Error: null

업데이트 할 레코드가 없다는 의미였습니다.

에러도 없는데 profile 테이블의 칼럼에 name, university의 값이 들어가질 않습니다.

2차 시도: 타이밍 이슈 의심

가설

새로운 레코드가 생기기도 전에, 클라이언트에서 너무 빨리 update를 시도하는 것 아닐까? 라는 생각이 들었습니다.

해결 시도: Delay 추가

// 트리거가 레코드 생성할 때까지 대기
await new Promise(resolve => setTimeout(resolve, 1000));

const { data: profileData, error: profileError } = await supabase
  .from("profiles")
  .update(updateData)
  .eq("id", data.user.id);

1초의 대기 시간을 줘서 레코드 생성 시간을 기다려보기로 했습니다.

결과: 여전히 실패

Profile Data: []  // 여전히 빈 배열

3차 분석: 세션 문제 발견

세션 확인

const { data: { session } } = await supabase.auth.getSession();
console.log("Session:", session);  // null!

원인 파악

세션(Session)이란?

  • 사용자가 로그인한 상태를 나타내는 정보
  • JWT 토큰 + 사용자 정보 포함
  • 이 세션이 있어야 auth.uid()가 사용자 ID를 반환

왜 세션이 null인가?

Supabase에서 이메일 인증이 활성화되어 있으면:
1. 회원가입 → 이메일 발송
2. 사용자가 이메일 링크 클릭 → 계정 인증
3. 인증 완료 후에야 세션 생성

따라서:

  • 회원가입 성공
  • 하지만 이메일 미인증 → 세션 없음
  • 세션 없음 → auth.uid() = null
  • RLS 정책 using (auth.uid() = id) 통과 못함
  • Update 권한 없음 → 0개 row 업데이트

트리거는 정상 작동했지만, update가 RLS 정책 때문에 실패한 것!

최종 해결: 트리거 함수에서 메타데이터 활용

핵심 아이디어

이메일 인증은 꼭 필요한 기능이였기 때문에, 이메일 인증을 유지하면서 세션 없이도 정보를 저장하려면, 트리거에서 모든 정보를 한 번에 저장해야 한다.

구현

1. 회원가입 시 메타데이터에 정보 포함

const { data, error } = await supabase.auth.signUp({
  email: fullEmail,
  password: formData.password.trim(),
  options: {
    data: {
      name: formData.name.trim(),
      university: formData.university,
    },
  },
});

if (data.user) {
  // 트리거가 자동으로 처리하므로 별도 update 불필요!
  setSignupSuccess(true);
}

2. 트리거 함수 수정 (메타데이터에서 정보 추출)

create or replace function public.handle_new_user()
returns trigger as $$
begin
  insert into public.profiles (id, email, name, university)
  values (
    new.id,
    new.email,
    new.raw_user_meta_data->>'name',
    new.raw_user_meta_data->>'university'
  );
  return new;
end;
$$ language plpgsql security definer;

동작 방식

  1. 회원가입 시 options.data에 name, university 저장 → auth.usersraw_user_meta_data에 저장됨
  2. 트리거가 자동으로 메타데이터를 읽어서 profiles 테이블에 저장
  3. 클라이언트에서 별도 update 불필요 ✅
  4. 이메일 인증 유지 가능 ✅
  5. 세션 없어도 문제 없음 ✅

정리

기존 방식

  1. 회원가입 시 authentication의 users 테이블에 유저 정보 (id, email) 저장
  2. 이 때 트리거로 profiles 테이블에 새로운 레코드로 id, email을 저장함 3. 만들어진 레코드에 university, name 저장 시도
  3. 이메일 인증이 되지 않아 세션이 없기 때문에 RLS 정책에 의해 값 업데이트 불가 -> 실패!

변경한 방식

  1. 회원가입 시 authentication의 users 테이블에 유저 정보 (id, email, metadata) 저장
  2. 이 때 트리거로 profiles 테이블에 id, email과 metadata에서 읽어온 university, name까지 한꺼번에 저장
  3. 세션 상관없이 값 저장 -> 성공!
profile
빠르게 성장하는 개발자

0개의 댓글