Files
Obsidian/ZELLYY/zellyy core/sso-implementation.md
2025-03-26 18:16:46 +09:00

6.7 KiB

ZELLYY Core SSO 구현 계획

개요

이 문서는 ZELLYY Core의 Single Sign-On (SSO) 구현 방안과 소셜 로그인(구글, 애플) 통합에 대해 설명합니다.

SSO 구현 전략

지원하는 인증 방식

  1. 이메일/비밀번호 - 기본 인증 방식
  2. 구글 OAuth - 구글 계정을 통한 로그인
  3. 애플 로그인 - 애플 계정을 통한 로그인 (iOS 앱 필수)

Supabase Auth 활용

ZELLYY Core는 Supabase Auth를 활용하여 SSO를 구현합니다. Supabase는 다음 기능을 제공합니다:

  • 소셜 로그인 통합 (Google, Apple, Github 등)
  • JWT 토큰 관리
  • 사용자 세션 관리
  • 보안 설정 (2FA, 비밀번호 정책 등)

소셜 로그인 구현 단계

1. 제공자 등록 및 설정

구글 OAuth 설정

  1. Google Cloud Console에서 프로젝트 생성
  2. OAuth 2.0 클라이언트 ID 생성
  3. 승인된 리디렉션 URI 설정:
    https://[PROJECT_ID].supabase.co/auth/v1/callback
    
  4. 클라이언트 ID와 비밀번호를 Supabase 설정에 등록

애플 로그인 설정

  1. Apple Developer 계정에서 App ID 등록
  2. Sign in with Apple 서비스 활성화
  3. 서비스 ID 생성 및 도메인 검증
  4. 승인된 리디렉션 URI 설정
  5. 키 생성 및 Supabase 설정에 등록

2. 프론트엔드 구현

로그인 버튼

import { supabase } from './supabaseClient';

const SocialLoginButtons = () => {
  const handleGoogleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: 'google',
      options: {
        redirectTo: 'https://app.zellyy.com/auth/callback'
      }
    });
  };

  const handleAppleLogin = async () => {
    await supabase.auth.signInWithOAuth({
      provider: 'apple',
      options: {
        redirectTo: 'https://app.zellyy.com/auth/callback'
      }
    });
  };

  return (
    <div className="social-login">
      <button onClick={handleGoogleLogin} className="google-btn">
        Google로 계속하기
      </button>
      <button onClick={handleAppleLogin} className="apple-btn">
        Apple로 계속하기
      </button>
    </div>
  );
};

OAuth 콜백 처리

import { useEffect } from 'react';
import { useLocation, useNavigate } from 'react-router-dom';
import { supabase } from './supabaseClient';

const AuthCallback = () => {
  const location = useLocation();
  const navigate = useNavigate();

  useEffect(() => {
    // URL에서 인증 정보 추출
    const handleAuthCallback = async () => {
      const { error } = await supabase.auth.getSession();
      
      if (error) {
        console.error('인증 오류:', error);
        navigate('/login?error=auth_callback_error');
        return;
      }
      
      // 성공적으로 인증됨
      navigate('/dashboard');
    };

    handleAuthCallback();
  }, [navigate]);

  return <div>로그인 처리 ...</div>;
};

3. 백엔드 구현

사용자 프로필 동기화

새 사용자가 소셜 로그인으로 가입할 때 추가 정보를 저장하는 Supabase 함수:

-- Supabase 함수로 auth.users 테이블에서 core.users로 사용자 정보 동기화
CREATE OR REPLACE FUNCTION public.handle_new_user()
RETURNS TRIGGER AS $$
BEGIN
  INSERT INTO core.users (id, email, name, auth_provider, auth_provider_id, avatar_url)
  VALUES (
    NEW.id,
    NEW.email,
    COALESCE(NEW.raw_user_meta_data->>'full_name', NEW.email),
    NEW.provider,
    NEW.provider_id,
    NEW.raw_user_meta_data->>'avatar_url'
  );
  
  -- 기본 서비스 접근 권한 부여 (예: 처음 가입한 서비스)
  INSERT INTO core.service_access (user_id, service_id, access_level)
  SELECT 
    NEW.id, 
    id, 
    'user' 
  FROM core.services 
  WHERE name = 'budget'; -- 예시: 적자 탈출 가계부를 기본 서비스로 설정
    
  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();

서비스 간 인증 상태 공유

쿠키 또는 localStorage를 사용한 SSO 세션 관리:

// ZELLYY Core API 서비스
export const checkAndRefreshSession = async () => {
  const { data, error } = await supabase.auth.getSession();
  
  if (error || !data.session) {
    // 세션이 없거나 만료됨
    return { isAuthenticated: false };
  }
  
  // 세션 활성 연장
  if (data.session.expires_at - Date.now() / 1000 < 3600) {
    // 만료 1시간 전이면 갱신
    await supabase.auth.refreshSession();
  }
  
  return { 
    isAuthenticated: true,
    user: data.session.user,
    accessibleServices: await getUserServices(data.session.user.id)
  };
};

// 사용자의 접근 가능한 서비스 목록 조회
const getUserServices = async (userId) => {
  const { data, error } = await supabase
    .from('service_access')
    .select(`
      service:service_id (
        id,
        name,
        description,
        api_endpoint
      ),
      access_level
    `)
    .eq('user_id', userId);
    
  if (error) throw new Error('서비스 접근 권한 조회 실패');
  return data.map(item => ({
    ...item.service,
    accessLevel: item.access_level
  }));
};

통합 사용자 경험

1. 최초 로그인

  1. 사용자가 어느 ZELLYY 서비스에서든 "구글로 로그인" 버튼 클릭
  2. 구글 OAuth 인증 화면으로 리다이렉트
  3. 인증 후 ZELLYY Core로 리다이렉트
  4. 최초 로그인인 경우:
    • 사용자 프로필 정보 저장
    • 접근한 서비스에 대한 권한 부여
    • 환영 화면 표시
  5. 로그인 완료 후 원래 서비스로 리다이렉트

2. 서비스 간 전환

사용자가 이미 로그인한 상태에서 다른 ZELLYY 서비스 접근 시:

  1. 서비스는 로컬 스토리지 또는 쿠키에서 토큰 확인
  2. ZELLYY Core API에 토큰 검증 요청
  3. 유효한 토큰이면 자동 로그인 처리
  4. 접근 권한이 없는 서비스의 경우 권한 요청 화면 표시

3. 로그아웃

  1. 어느 서비스에서든 로그아웃 시 ZELLYY Core 로그아웃 API 호출
  2. 모든 서비스에서 인증 토큰 삭제
  3. SSO 세션 종료

보안 고려사항

  1. PKCE 인증 흐름: OAuth에 PKCE(Proof Key for Code Exchange) 사용
  2. 토큰 관리: 짧은 수명의 액세스 토큰과 리프레시 토큰 사용
  3. HTTPS: 모든 통신은 HTTPS로 암호화
  4. CORS 설정: API에 적절한 CORS 정책 적용
  5. CSP(Content Security Policy): 적절한 CSP 헤더 설정

다음 단계

  1. 구글 및 애플 개발자 계정 설정 및 OAuth 클라이언트 등록
  2. Supabase Auth 설정 및 테스트
  3. 로그인 UI 컴포넌트 개발
  4. 소셜 로그인 통합 테스트
  5. 서비스 간 SSO 흐름 테스트