프로젝트/할일목록 앱 (RN)

25. 소셜로그인하기 - 네이버 로그인 구현하기 (라이브러리 사용 안함)

syleemomo 2023. 11. 7. 14:25
728x90

https://reference-m1.tistory.com/368

 

[Front end] SNS 로그인 연동(네이버, 카카오) with React

SNS 로그인 SNS 연계에 필요한 개발자 및 앱 등록은 간단하므로 샘플 소스 위주로 정리하였다. 실제 로그인 연동 및 사용자 정보까지 연동이 되는 React 코드이다. developers.naver.com NAVER Developers 네이

reference-m1.tistory.com

 

import React, { useState, useEffect } from 'react'
import { SafeAreaView, View, Text, StyleSheet, StatusBar, ScrollView, Dimensions, ImageBackground  } from 'react-native'
import LandingPage from '../components/LandingPage'
import landingData from '../data/landingData'
import LoginButton from '../components/LoginButton';
import NaverLoginButton from '../components/NaverLoginButton';

import { GoogleSignin } from '@react-native-google-signin/google-signin'

function LandingScreen({navigation}){
    const { width, height } = Dimensions.get('window')
    const [currentPageIndex, setCurrentPageIndex] = useState(0)
    console.log('페이지 번호: ', currentPageIndex)

    const setCurrentPage = (e) => {
        const { x } = e.nativeEvent.contentOffset // x : 스크롤 위치
        console.log("스크롤 위치: ", x, "화면너비: ", width)
        const nextPageIndex = Math.ceil(x / width) // x / width : 스크롤 위치 / 화면너비 -> 페이지번호
        console.log(nextPageIndex)
        if(nextPageIndex !== currentPageIndex){
            setCurrentPageIndex(nextPageIndex)
        }
    }

    const getUserInfo = async () => await GoogleSignin.getCurrentUser()
    useEffect(() => {
        const result = getUserInfo()
        // if(user){
        //     navigation.navigate('App')
        // }
        result.then(user => {
            console.log('user : ',user)
            if(user){
              navigation.navigate('App', { userInfo: user.user })
            }
          })
    }, [])

    return (
        <>
            <StatusBar hidden></StatusBar>
            <SafeAreaView style={styles.block}>
                <ScrollView
                    style={{ flex: 1 }}
                    horizontal={true} // 수평 스크롤링
                    scrollEventThrottle={16} // 스크롤 이벤트 감지하는 시간간격 (ms)
                    pagingEnabled={true} // 스크롤시 페이지네이션
                    showsHorizontalScrollIndicator={false} // 스크롤바 숨기기
                    onScroll={setCurrentPage}
                >
                    {landingData.map((page, index) => (
                        <LandingPage
                            width={width}
                            height={height}
                            {...page}
                            key={index}
                        />
                    ))}
                </ScrollView>

                <View style={styles.scrollIndicatorWrapper}>
                    {Array(3).fill(0).map((_, index) => (
                        <View key={index} style={[styles.scrollIndicator, { opacity: currentPageIndex === index ? 1: 0.3}]}></View>
                    ))}
                </View>

                <LoginButton navigation={navigation}/>
                <NaverLoginButton/>
            </SafeAreaView>
        </>
    )
}

const styles = StyleSheet.create({
  block: {
    flex: 1
  },
  scrollIndicatorWrapper:{
    position: 'absolute',
    left: 0, right: 0,
    bottom: 50,
    flexDirection: 'row',
    justifyContent: 'center',
    alignItems: 'center'
  },
  scrollIndicator: {
    height: 10,
    width: 10,
    borderRadius: 10 / 2,
    backgroundColor: '#aaa',
    marginLeft: 10,
  },

})
export default LandingScreen

screens > LandingScreen.js 파일을 위와 같이 수정한다. 

import NaverLoginButton from '../components/NaverLoginButton';

네이버 로그인 기능이 구현된 컴포넌트를 임포트한다.

<SafeAreaView>
    // 중략
    <NaverLoginButton/>
</SafeAreaView>

화면에 로그인버튼을 보여주기 위하여 컴포넌트를 렌더링한다. 

 

 

npm install react-native-webview

네이버 로그인 화면을 보여주기 위하여 애플리케이션 내부에서 웹뷰를 사용할 예정이다. 이때 해당 라이브러리를 사용할 것이므로 설치해준다.

 

android.useAndroidX=true
android.enableJetifier=true

android > gradle.properties 파일에 위 코드를 추가한다. 네이버 로그인을 위하여 필요하다.

 

https://developers.naver.com/docs/login/bi/bi.md

 

로그인 버튼 사용 가이드 - LOGIN

네이버 로그인은 애플리케이션에 사용할 수 있는 네이버 로그인 버튼 기본 이미지를 제공합니다. 애플리케이션의 상황에 맞게 버튼 이미지의 디자인을 변경할 수 있지만 네이버 고유의 아이덴

developers.naver.com

assets > imgs 폴더에 위 링크에서 다운로드 받은 로그인 버튼 이미지를 추가해준다.

 

 

* 네이버 로그인을 위한 네이버 디벨로퍼 설정하기 

 

https://developers.naver.com/main/

 

NAVER Developers

네이버 오픈 API들을 활용해 개발자들이 다양한 애플리케이션을 개발할 수 있도록 API 가이드와 SDK를 제공합니다. 제공중인 오픈 API에는 네이버 로그인, 검색, 단축URL, 캡차를 비롯 기계번역, 음

developers.naver.com

네이버 디벨로퍼 홈페이지에 가서 로그인을 진행한다. Application > 내 애플리케이션으로 이동한다. 

우측 상단의 Application 등록버튼을 클릭하여 아래와 같은 화면에서 네이버 로그인을 사용할 새로운 애플리케이션을 등록한다.

애플리케이션 등록화면

애플리케이션 이름과 사용API를 설정한다. 사용API는 네이버 로그인으로 선택한다. 또한, 로그인에 성공한 이후 애플리케이션에서 사용하고자 하는 사용자정보도 체크해준다. 

애플리케이션 이름 & 사용API

우선 로그인 환경추가에서 PC웹을 선택한다. 서비스 URL은 서버 도메인의 IP주소이다. 리액트 네이티브의 애뮬레이터는 현재 로컬서버에서 동작하고 있기 때문에 CMD창에서 ipconfig 를 입력하여 자신의 로컬IP주소를 확인하고 입력하면 된다. 또한, 포트 8081은 애뮬레이터가 동작중인 고유한 포트번호이다. Callback URL은 네이버 로그인 화면에서 로그인 버튼을 클릭하면 네이버 서버에서 로그인을 수행한 다음 로그인 결과를 전달받을 URL 주소이다. 현재는 서버 URL과 동일하게 설정한다. 

CMD창에서 애뮬레이터의 로컬서버 IP 확인

 

로그인 환경추가 - PC웹
로그인 환경추가 - Mobile 웹

다운로드 URL은 안드로이드 앱을 다운로드 받는 URL이지만 현재는 서버URL과 동일하게 설정하도록 한다. 추후 변경하면 된다. 안드로이드 앱 패키지 이름은 android > app > build.gradle 파일의 applicationId 값을 등록하면 된다. 

defaultConfig {
        applicationId "com.learnreactnative"
        minSdkVersion rootProject.ext.minSdkVersion
        targetSdkVersion rootProject.ext.targetSdkVersion
        versionCode 1
        versionName "1.0"
    }

로그인 환경추가 - 안드로이드

애플리케이션 등록을 마치면 내 애플리케이션에서 등록정보를 확인할 수 있다. ClientID, Client Secret 은 추후 네이버 로그인에 사용될 예정이다. 

 

* 네이버 로그인 구현하기 

 

https://developers.naver.com/docs/login/devguide/devguide.md

 

네이버 로그인 개발가이드 - LOGIN

네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디

developers.naver.com

https://developers.naver.com/docs/login/devguide/devguide.md#3-4-%EB%84%A4%EC%9D%B4%EB%B2%84-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%97%B0%EB%8F%99-%EA%B0%9C%EB%B0%9C%ED%95%98%EA%B8%B0

 

네이버 로그인 개발가이드 - LOGIN

네이버 로그인 개발가이드 1. 개요 4,200만 네이버 회원을 여러분의 사용자로! 네이버 회원이라면, 여러분의 사이트를 간편하게 이용할 수 있습니다. 전 국민 모두가 가지고 있는 네이버 아이디

developers.naver.com

네이버 로그인 개발가이드를 참고하여 아래 코드를 구현하였다. 

 

import React, {useState, useEffect } from 'react';
import {SafeAreaView, Button, View, Text, ScrollView, StyleSheet, Dimensions, TouchableWithoutFeedback, Image } from 'react-native';
import { WebView } from 'react-native-webview';
import { useRoute } from '@react-navigation/native'

const NAVER_CLIENT_ID = 'KFv6qukJ1q8DqHRUGOs9'
const NAVER_CLIENT_SECRET = 'vMHgRoSTC3'
const STATE = encodeURIComponent("sunrise")
const REDIRECT_URI = encodeURIComponent('http://192.168.200.9:8081')
const SERVER_URL = 'http://192.168.200.9:8081'
const NAVER_AUTH_URL = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${NAVER_CLIENT_ID}&state=${STATE}&redirect_uri=${REDIRECT_URI}`
console.log(NAVER_AUTH_URL)

const NaverLoginButton = () => {
  const route = useRoute();
  const { width, height } = Dimensions.get('window')
  const [loginStatus, setLoginStatus] = useState(false)
  const [userInfo, setUserInfo] = useState(null)

  const login = async () => {
    console.log("네이버 로그인시작")
    setLoginStatus(true)
  };

  const handleResponseFromNaverLogin = async (state) => {
      let url = state.url
      console.log("로그인 응답 - ", url)
      let queryString = url.split('?')[1]
      console.log(queryString)
      let queryParameters = queryString.split('&')
      console.log(queryParameters)
      queryParameters = queryParameters.map(param => {
        const paramName = param.split('=')[0]
        const paramValue = param.split('=')[1]
        return { [paramName]: paramValue }
      })
      console.log("쿼리스트링: ", queryParameters)

      const code = queryParameters[0].code 
      // const state = queryParameters[1].state
      const TOKEN_URL = `https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&client_id=${NAVER_CLIENT_ID}&client_secret=${NAVER_CLIENT_SECRET}&code=${code}&state=${STATE}`

      let result = await fetch(TOKEN_URL, {
        headers: { 'Content-Type': 'application/json' }
      })
      result = await result.json()
      console.log("최종 응답: ", result)
      console.log("액세스 토큰 - ", result.access_token)
      console.log("리프레쉬 토큰 - ", result.refresh_token)
      console.log("토큰만료(초) - ", result.expires_in)

      const PROFILE_URL = "https://openapi.naver.com/v1/nid/me"
      result = await fetch(PROFILE_URL, {
        headers: {
          'Authorization': `Bearer ${result.access_token}`, 
        }
      })
      result = await result.json()
      console.log("최종 응답: ", result)
      const { response } = result
      // const { age, birthday, birthyear, email, gender, id, mobile, mobile_e164, name, nickname, profile_image } = response 
      console.log('-------------------- 네이버 사용자 정보 -------------------------')
      console.log('사용자 이름: ', response?.name)
      console.log('사용자 별명: ', response?.nickname )
      console.log('사용자 나이: ', response?.age)
      console.log('사용자 성별: ', response?.gender)
      console.log('사용자 생일: ', `${response?.birthyear}-${response?.birthday}`)
      console.log('사용자 이메일: ', response?.email)
      console.log('사용자 연락처: ', response?.mobile)

      if(response){
        setUserInfo(response)
        setLoginStatus(false)
      }
  }

  return (
    <View
      style={styles.btnWrapper}>
        {loginStatus && <WebView
        source={{ uri: NAVER_AUTH_URL }}
        style={[styles.webView, {width, height}]}
        // injectedJavaScript={INJECTEDJAVASCRIPT}
        scrollEnabled
        onNavigationStateChange={(state) => handleResponseFromNaverLogin(state)}
      />}

      {!loginStatus && userInfo && 
        <View>
          <Text style={styles.result}>네이버 로그인 성공</Text>
          {/* <Text style={styles.result}>{userInfo.name && userInfo.name}</Text> */}
          <Text style={styles.result}>{userInfo.nickname && userInfo.nickname}</Text>
          {/* <Text style={styles.result}>{userInfo.age && userInfo.age}</Text> */}
          {/* <Text style={styles.result}>{userInfo.birthyear && userInfo.birthday && `${userInfo?.birthyear}-${userInfo?.birthday}`}</Text> */}
          <Text style={styles.result}>{userInfo.email && userInfo.email}</Text>
          {/* <Text style={styles.result}>{userInfo.mobile && userInfo.mobile}</Text> */}
        </View>
      }
      
      <TouchableWithoutFeedback onPress={login}>
        <Image source={require('../assets/imgs/naver-login-btn.png')} style={styles.loginBtn}></Image>
      </TouchableWithoutFeedback>
    </View>
  );
}

export default NaverLoginButton

const styles = StyleSheet.create({
  btnWrapper: {
    position: 'absolute',
    left: 0, right: 0,
    bottom: 150,
    // backgroundColor: 'red',
    alignItems: 'center'
  },
  webView: {
    flex: 1,
  },
  loginBtn: {
    width: 303, 
    height: 45, 
    borderRadius: 3
  },
  result: {
    fontSize: 30,
    fontWeight: 'bold',
    color: '#fff'
  }
})

components > NaverLoginButton.js 파일을 생성하고 위와 같이 작성하여 네이버 로그인 기능을 구현한다. 

import React, {useState, useEffect } from 'react';
import {SafeAreaView, Button, View, Text, ScrollView, StyleSheet, Dimensions, TouchableWithoutFeedback, Image } from 'react-native';
import { WebView } from 'react-native-webview';
import { useRoute } from '@react-navigation/native'

필요한 리액트훅과 컴포넌트, 그리고 라이브러리를 임포트한다. WebView 는 웹뷰를 보여줄수 있는 라이브러리이다. useRoute 는 현재 사용하지 않는다. 

const NAVER_CLIENT_ID = 'KFv6qukJ1q8DqHRUGOs9'
const NAVER_CLIENT_SECRET = 'vMHgRoSTC3'
const STATE = encodeURIComponent("sunrise")
const REDIRECT_URI = encodeURIComponent('http://192.168.200.9:8081')
const SERVER_URL = 'http://192.168.200.9:8081'
const NAVER_AUTH_URL = `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${NAVER_CLIENT_ID}&state=${STATE}&redirect_uri=${REDIRECT_URI}`
console.log(NAVER_AUTH_URL)

네이버 디벨로퍼에서 애플리케이션 등록을 완료하면 ClientID, ClientSecret, Callback URL을 확인할 수 있다. NAVER_AUTH_URL은 네이버 로그인 화면을 보여주는 URL주소이다. 브라우저에서 해당 URL주소로 접속하면 네이버 로그인 화면이 보일 것이다. 현재 코드에서는 웹뷰에서 네이버 로그인 화면을 보여줄 예정이다. 

const { width, height } = Dimensions.get('window')

웹뷰를 애뮬레이터 전체화면에 걸쳐서 보여주기 위함이다. 

const [loginStatus, setLoginStatus] = useState(false)

웹뷰를 화면에서 보여주거나 숨기기 위한 토글 상태이다. Boolean 값이다. 

const [userInfo, setUserInfo] = useState(null)

로그인에 성공했을때 사용자정보를 화면에 보여주기 위한 state 이다. 

const login = async () => {
    console.log("네이버 로그인시작")
    setLoginStatus(true)
  };

랜딩페이지에서 네이버 로그인버튼을 클릭할때 실행되는 함수이다. loginState 를 true 로 변경하여 네이버 로그인화면을 웹뷰로 보여준다.

const handleResponseFromNaverLogin = async (state) => {
      let url = state.url
      console.log("로그인 응답 - ", url)
      let queryString = url.split('?')[1]
      console.log(queryString)
      let queryParameters = queryString.split('&')
      console.log(queryParameters)
      queryParameters = queryParameters.map(param => {
        const paramName = param.split('=')[0]
        const paramValue = param.split('=')[1]
        return { [paramName]: paramValue }
      })
      console.log("쿼리스트링: ", queryParameters)

      const code = queryParameters[0].code 
      // const state = queryParameters[1].state
      const TOKEN_URL = `https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&client_id=${NAVER_CLIENT_ID}&client_secret=${NAVER_CLIENT_SECRET}&code=${code}&state=${STATE}`

      let result = await fetch(TOKEN_URL, {
        headers: { 'Content-Type': 'application/json' }
      })
      result = await result.json()
      console.log("최종 응답: ", result)
      console.log("액세스 토큰 - ", result.access_token)
      console.log("리프레쉬 토큰 - ", result.refresh_token)
      console.log("토큰만료(초) - ", result.expires_in)

      const PROFILE_URL = "https://openapi.naver.com/v1/nid/me"
      result = await fetch(PROFILE_URL, {
        headers: {
          'Authorization': `Bearer ${result.access_token}`, 
        }
      })
      result = await result.json()
      console.log("최종 응답: ", result)
      const { response } = result
      // const { age, birthday, birthyear, email, gender, id, mobile, mobile_e164, name, nickname, profile_image } = response 
      console.log('-------------------- 네이버 사용자 정보 -------------------------')
      console.log('사용자 이름: ', response?.name)
      console.log('사용자 별명: ', response?.nickname )
      console.log('사용자 나이: ', response?.age)
      console.log('사용자 성별: ', response?.gender)
      console.log('사용자 생일: ', `${response?.birthyear}-${response?.birthday}`)
      console.log('사용자 이메일: ', response?.email)
      console.log('사용자 연락처: ', response?.mobile)

      if(response){
        setUserInfo(response)
        setLoginStatus(false)
      }
  }

네이버 로그인화면(웹뷰)에서 로그인을 진행한 후 로그인버튼을 클릭하면 실행되는 함수이다. 로그인버튼을 클릭하면 네이버 서버에서 사용자 인증을 진행하고, 인증결과를 Callback URL로 다시 보내준다. 현재는 내 애플리케이션으로 인증결과를 보내준다. 이때 handleResponseFromNaverLogin 함수에서 인증결과를 전달받아서 이후 인증과정을 수행할 수 있다.

맨 처음 사용자가 로그인을 수행하면 Callback URL 주소로 리다이렉션하면서  네이버로부터 인증코드(code, state)를 전달받는다. Callback URL의 쿼리스트링에 포함되어 있다. Callback URL에서 인증코드(code)를 파싱(해석)해서 추출한다. 인증코드를  ClientID, ClientSecret, State 와 함께 다시 네이버 서버에 요청하여 액세스 토큰을 발급받는다. 액세스 토큰을 요청헤더(request header)의 Authorization 속성에 삽입하여 네이버에 등록된 프로필 정보를 내 애플리케이션으로 가져온다.

성공적으로 로그인하고 프로필정보를 가져오면 웹뷰를 숨기고 프로필 정보를 UI로 넘겨서 화면에 보여준다. 

<View
      style={styles.btnWrapper}>
        {loginStatus && <WebView
        source={{ uri: NAVER_AUTH_URL }}
        style={[styles.webView, {width, height}]}
        // injectedJavaScript={INJECTEDJAVASCRIPT}
        scrollEnabled
        onNavigationStateChange={(state) => handleResponseFromNaverLogin(state)}
      />}

      {!loginStatus && userInfo && 
        <View>
          <Text style={styles.result}>네이버 로그인 성공</Text>
          {/* <Text style={styles.result}>{userInfo.name && userInfo.name}</Text> */}
          <Text style={styles.result}>{userInfo.nickname && userInfo.nickname}</Text>
          {/* <Text style={styles.result}>{userInfo.age && userInfo.age}</Text> */}
          {/* <Text style={styles.result}>{userInfo.birthyear && userInfo.birthday && `${userInfo?.birthyear}-${userInfo?.birthday}`}</Text> */}
          <Text style={styles.result}>{userInfo.email && userInfo.email}</Text>
          {/* <Text style={styles.result}>{userInfo.mobile && userInfo.mobile}</Text> */}
        </View>
      }
      
      <TouchableWithoutFeedback onPress={login}>
        <Image source={require('../assets/imgs/naver-login-btn.png')} style={styles.loginBtn}></Image>
      </TouchableWithoutFeedback>
    </View>

loginStatus 가 true 이면 웹뷰(네이버 로그인 화면)를 보여준다. 네이버 로그인화면의 URL주소는 NAVER_AUTH_URL에 담겨있다. onNavigationStateChange 는 네이버 로그인화면에서 사용자가 로그인버튼을 클릭하면 실행되는 함수이다. 또한, loginStatus 가 false 이고 프로필 정보(userInfo)가 존재하면 프로필 정보를 화면에 보여준다. 네이버 로그인버튼의 이미지는 네이버 개발자 가이드에서 다운로드받아서 사용하였다. 

const styles = StyleSheet.create({
  btnWrapper: {
    position: 'absolute',
    left: 0, right: 0,
    bottom: 150,
    // backgroundColor: 'red',
    alignItems: 'center'
  },
  webView: {
    flex: 1,
  },
  loginBtn: {
    width: 303, 
    height: 45, 
    borderRadius: 3
  },
  result: {
    fontSize: 30,
    fontWeight: 'bold',
    color: '#fff'
  }
})

네이버 로그인버튼의 스타일은 위와 같이 작성한다. 이는 추후 변경 예정이므로 대충 작성해도 되며 마음대로 변경해봐도 된다. 

 

네이버 로그인 버튼이 추가된 랜딩 페이지 화면
네이버 로그인 성공후 프로필 정보 조회

 

728x90