카테고리 없음

React Native + Supabase 카카오 로그인 과정 이해하기

순코딩 2025. 9. 9. 02:21

개요

React Native 앱에서 Supabase를 활용한 카카오 소셜 로그인 구현 방법을 설명합니다.

전체 로그인 프로세스

import { supabase } from '@/lib/supabaseClient'
import { mixinFlex } from '@/styles/mixins'
import { theme } from '@/styles/theme'
import styled from '@emotion/native'
import { Ionicons } from '@expo/vector-icons'
import { router } from 'expo-router'
import { useEffect, useState } from 'react'
import { Alert, BackHandler, Image, TouchableOpacity, View } from 'react-native'
import WebView from 'react-native-webview'

const LoginScreen = () => {
  const [showWebView, setShowWebView] = useState(false)
  const [authUrl, setAuthUrl] = useState('')

  ////////// 카카오 로그인 버튼 클릭 함수
  const signInWithKakao = async () => {
    try {
      const { data, error } = await supabase.auth.signInWithOAuth({
        provider: 'kakao',
        options: {
          skipBrowserRedirect: true, // 브라우저 리다이렉트 건너뛰고 로그인 URL만 받음(직접 브라우저를 열고 제어 가능)
        },
      })

      // 에러 발생 시 에러 처리
      if (error) throw error

      // URL이 있다면
      if (data?.url) {
        // URL 상태 업데이트
        setAuthUrl(data.url)
        // WebView 표시
        setShowWebView(true)
      }
    } catch(error) {
      // 에러 발생 시 로그인 페이지로 이동
      router.replace('/auth/login')
      throw error
    }
  }

  ////////// WebView URL 변경 감지 함수
  const handleNavigationStateChange = async (navState: any) => {
    // access_token이 포함된 URL인지 확인
    if (navState.url.includes('access_token=')) {
      try {
        // URL에서 access_token 추출
        const params = new URLSearchParams(navState.url.split('#')[1])
        const accessToken = params.get('access_token')
        const refreshToken = params.get('refresh_token')

        // 세션 설정
        const {
          data: { session },
          error,
        } = await supabase.auth.setSession({
          access_token: accessToken!,
          refresh_token: refreshToken!,
        })

        if (error) throw error

        if (session) {
          setShowWebView(false)
          // 홈 화면으로 이동
          router.replace('/auth/callback')
        }
      } catch {
        Alert.alert('로그인 실패')
        router.replace('/auth/login')
      }
    }
  }

  // 백핸들러(뒤로가기 버튼 눌렀을 때)
  useEffect(() => {
    const backHandler = BackHandler.addEventListener('hardwareBackPress', () => {
      router.replace('/') // 홈으로 이동
      return true // 기본 동작 방지
    })

    return () => backHandler.remove()
  }, [])

  return (
    <>
      {showWebView ? (
        <WebView source={{ uri: authUrl }} onNavigationStateChange={handleNavigationStateChange} />
      ) : (
        <Container>
          {/* 뒤로가기 버튼 */}
          <BackButton onPress={() => router.replace('/')}>
            <Ionicons
              name='chevron-back'
              size={theme.iconSizes.lg}
              color={theme.colors.core.white}
            />
          </BackButton>
          {/* 로고 */}
          <Logo source={require('@assets/images/icon.png')} style={{ width: 100, height: 100 }} />
          {/* 카카오 로그인 버튼 */}
          <TouchableOpacity onPress={signInWithKakao}>
            <KakaoLogin source={require('@assets/images/kakao-login.png')} />
          </TouchableOpacity>
        </Container>
      )}
    </>
  )
}

export default LoginScreen

const Container = styled(View)`
  ${mixinFlex('column', 'center', 'center')}
  flex: 1;
  background-color: ${theme.colors.background.default};
  row-gap: 24px;
`

const Logo = styled(Image)`
  border-radius: ${`${theme.border.radius.full}px`};
`

const BackButton = styled.TouchableOpacity`
  position: absolute;
  top: 16px;
  left: 16px;
`

const KakaoLogin = styled(Image)``

 

프로세스 설명

로그인 시작 - 사용자 액션
카카오 로그인 버튼 클릭 시 signInWithKakao 실행
<TouchableOpacity onPress={signInWithKakao}>
  <KakaoLogin source={require('@assets/images/kakao-login.png')} />
</TouchableOpacity>
동작:
사용자가 카카오 로그인 버튼 터치
signInWithKakao() 함수 호출

 

OAuth URL 획득 - 웹뷰 초기화
signInWithKakao 함수 내부에서 로그인 URL을 받아 URL 상태를 업데이트하고 웹뷰 상태를 true로 변경
const signInWithKakao = async () => {
  // Supabase OAuth URL 요청
  const { data, error } = await supabase.auth.signInWithOAuth({
    provider: 'kakao',
    options: { skipBrowserRedirect: true },
  })

  if (data?.url) {
    setAuthUrl(data.url)      // 카카오 로그인 URL 저장
    setShowWebView(true)      // 웹뷰 표시 활성화
  }
}
동작:
- Supabase에 카카오 OAuth URL 요청
- 받은 URL로 `authUrl` 상태 업데이트
- `showWebView`를 `true`로 변경
- 결과: 화면이 로그인 버튼 → 웹뷰로 전환



사용자 인증 - 카카오 로그인 수행
사용자가 로그인 정보를 입력 후 로그인을 시도하면 인증 URL로 리다이렉트됨

{showWebView ? (
  <WebView 
    source={{ uri: authUrl }}  // 카카오 로그인 페이지 표시
    onNavigationStateChange={handleNavigationStateChange} 
  />
) : (
  // 로그인 버튼 화면
)}

 

동작:
- 웹뷰에 카카오 로그인 페이지 표시
- 사용자가 카카오 계정/비밀번호 입력
- 카카오 서버에서 인증 처리
- 인증 성공 시 Supabase 콜백 URL로 리다이렉트
- **URL 변경: `
https://kauth.kakao.com/...
` → `stockexam://auth/callback#access_token=...`**

 

토큰 추출 - 세션 설정
리다이렉트 과정에서 onNavigationStateChange이 웹뷰의 URL 변화를 감지하고 URL에서 액세스 토큰을 추출 및 세션을 설정
const handleNavigationStateChange = async (navState) => {
  // access_token이 포함된 URL 감지
  if (navState.url.includes('access_token=')) {
    
    // URL에서 토큰 추출
    const params = new URLSearchParams(navState.url.split('#')[1])
    const accessToken = params.get('access_token')
    const refreshToken = params.get('refresh_token')

    // Supabase 세션 설정
    const { data: { session }, error } = await supabase.auth.setSession({
      access_token: accessToken!,
      refresh_token: refreshToken!,
    })

    if (session) {
      // 다음 단계로...
    }
  }
}
동작:
- `onNavigationStateChange`가 URL 변경 감지
- URL에 `access_token` 포함 여부 확인
- URLSearchParams로 토큰 파싱
- `supabase.auth.setSession()`으로 세션 설정
- 결과: 사용자 로그인 상태 활성화

 

로그인 완료 - 화면 전환
세션 설정을 마쳤다면 웹뷰를 닫고 /auth/callback으로 리다이렉트
if (session) {
  setShowWebView(false)           // 웹뷰 숨기기
  router.replace('/auth/callback') // callback 페이지로 이동
}
동작:
- `setShowWebView(false)` - 웹뷰 화면 닫기
- `router.replace('/auth/callback')` - callback 화면으로 이동
- 결과: 로그인 후속 처리 화면으로 전환


주요 설정사항

1.앱 설정
// app.config.js
   export default {
     expo: {
       scheme: 'stockexam'  // 커스텀 URL 스킴
     }
   }

 

2. 필요한 패키지
npm install @supabase/supabase-js @react-native-async-storage/async-storage react-native-webview


주의사항

1. 웹과 앱의 차이
   - 웹: 쿠키로 자동 세션 관리
   - 앱: WebView와 앱이 분리되어 수동 세션 설정 필요

2. 테스트
   - Expo Go가 아닌 개발자 빌드로 테스트 필요
   - USB 디버깅으로 실제 기기에서 테스트

3. 보안
   - Supabase URL Configuration에 정확한 리다이렉트 URL 등록
   - 토큰 관리 주의

 

용어 설명

- 커스텀 URL 스킴: 앱을 식별하는 고유한 URL 형식 (예: stockexam://)
- 딥링크: 앱의 특정 화면으로 직접 이동할 수 있는 링크
- OAuth: 사용자 인증을 위한 개방형 표준 프로토콜