본문 바로가기
경일/React

[React-native/Expo]Expo에서 Kakao(카카오) 로그인 구현(Webview)

by dev_kong 2022. 6. 8.
728x90
728x90

0. 목차

1. 개요

2. Front-end

3. Back-end

1. 개요

Dodol 프로젝트를 진행하면서 가장 많이 고생했던 부분인
카카오 로그인에 대해 포스팅 해보려 한다.

 

기존의 Web에서 구현 할 때는 Rest API를 이용해서,
어렵지 않게 구현 할 수 있었지만,

Native 환경에서는 sdk를 이용하여 구현한다는 점을 깨달았다.

 

문제는 expo 환경에서는 위의 방식을 사용할 수가 없었다.

 

구글링을 통해 블로그 하나를 발견 할 수 있었고,
해당 블로그 내용과 직접 구현을 해보면서 알게된 조금 더 상세한 내용을 적어보려한다.

참고한 블로그

2. Front-end

위에서 expo환경에서는 sdk 사용이 불가능 하다고 적었다.


그리고 우리가 결국 사용한 방식은 기존에 사용했던 방식, 즉 rest API 였다.

하지만 위에 말했듯 rest API는 web에서만 사용이 가능하다.


답은 간단했다.
react-native(이하 rn)에서 Webview를 이용해 web을 띄우면 된다!

우선 라이브러리 하나를 설치해야한다.

 

npm install react-native-webview

 

설치를 했으면, import를 하고 컴포넌트를 작성하면 된다.

 

.
.
.
import {Webview} from 'react-native-webview';

const REST_API_KEY = '카카오 디벨로퍼에서 발급받은 REST_API_KEY'
const REDIRECT_URI = '카카오 디벨로퍼에서 설정한 redirect uri'

const INJECTED_JAVASCRIPT = `window.ReactNativeWebView.postMessage('message from webView')`;

function LoginScreen() {
  return (
    <View style={{ flex: 1 }}>
      <WebView
        style={{ flex: 1 }}
        source={{
          uri: `https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${REST_API_KEY}&redirect_uri=${REDIRECT_URI}`,
        }}
        injectedJavaScript={INJECTED_JAVASCRIPT}
        javaScriptEnabled
        onMessage={event => {
          const data = event.nativeEvent.url;
          getCode(data);
        }}
      />
    </View>
  );
}

 

이게 뭔가 싶겠지만 하나하나 뜯어보면 그 원리를 알 수 있다.

 

컴포넌트를 보면 View 안에 Webview가 들어있는 형태로 구성 되어있다.


그리고 Webview 컴포넌트로 여러가지 props가 전달 되는 걸 볼 수 있는데,

가장 중요한 것은 source 와 onMessage 그리고 injectedJavaScript이다.

 

source 는 Webview 보여줄 페이지의 주소다.

 

그리고 onMessage 는 Webview 에서 window.ReactNativeWebView.postMessage 함수가 호출될때 실행되는 이벤트 함수이다.

 

import React, { Component } from 'react';
import {Webview} from 'react-native-webview';
import { Alert } from 'react-native';

const html = `
    <script>
      function send(){
        window.ReactNativeWebView.postMessage('hello!!');
      }
    </script>
    <button onclick="send()">Send</button>
`;

export default class App extends Component {
  render() {
    return (
      <View>
        <WebView style={{top:50}}
          source={{html}}
          onMessage={(event)=> Alert.alert(event.nativeEvent.data) }
        />
      </View>
    );
  }
}

 

테스트를 위해 새로운 컴포넌트를 하나 만들었다.

 

위의 페이지에서 Webview에 뜬 버튼을 누르면,
핸드폰에 hello!! 가 적힌 alert 창이 생성된다.

 

여기까지 이해했다면 얘기가 쉬워진다.


카카오는 당연하게도
window.ReactNativeWebView.postMessage(code)
이런 코드를 자신들의 페이지에 작성하지 않았다.

 

우린 강제로 Webview에서 저 메서드를 실행시키게끔 만들어야 한다.

 

그리고 그 역할을 바로 injectedJavaScript 가 수행한다.

 

injectedJavascript 속성에는 웹뷰로 열리는 모든 페이지에서 실행될 자바스크립트 코드를 작성할 수 있다.

위의 테스트 페이지를 조금 변경해보겠다.

 

import React, { Component } from 'react';
import { WebView, Alert } from 'react-native';

const INJECTED_JAVASCRIPT = `window.ReactNativeWebView.postMessage('CHECK!')`;

const html = `
  <div>TEST</div>
`;

export default class App extends Component {
  render() {
    return (
      <View>
        <WebView 
          source={{html}}
          injectedJavascript={INJECTED_JAVASCRIPT}
          onMessage={(event)=> Alert.alert(event.nativeEvent.data) }
        />
      </View>
    );
  }
}

 

이 페이지에서는 그냥 Webview가 켜지기만 해도
alert창이 뜨고 CHECK가 찍혀있는 걸 확인 할 수가 있다.

 

다시 원래의 LoginScreen 컴포넌트로 돌아가서 보자.

 

우리는 Webview에서 보내주는 데이터를 처리하는게 아니라,
데이터를 보내주는 url이 필요하다.


그 url 안에 우리가 필요한 code가 queryString형식으로 들어있다.

 

이게 무슨 말인지 모르겠다면,

https://kong-dev.tistory.com/153

위의 포스팅을 읽어보는게 우선이다.

 

조금 덧붙여 설명을 해보자.

 

카카오 로그인 페이지에서 알맞은 회원 정보를 입력하고 로그인 버튼을 누르고, 로그인이 성공하게 되면
어떠한 주소로 이동하게 되는데,
이 주소에 우리가 필요한 code 가 queryString으로 들어있다.


그리고 이 주소로 성공적으로 이동되었을때,
injectJavascript 속성에 적은 코드
즉, window.ReactNativeWebView.postMessage('message from webView') 이 코드가 실행되고,

onMessage는 위의 함수가 실행됨을 캐치해서 onMessage 속성에 작성한 함수가 실행된다.

 

 onMessage={event => {
          const data = event.nativeEvent.url;
          getCode(data);}

 

이 부분이다.

 

postMessage로 보낸 데이터를 처리할 때는
event.nativeEvent.data 를 이용하지만


postMessage를 보낸 페이지의 url을 가져올때는
event.nativeEvent.url 를 이용하면 된다

 

그리고 가져온 url 에서 code를 가져오는 함수인 getCode 에 url을 넣어주면 된다.
getCode 함수는 이렇게 생겼다.

 

const getCode = (target: string) => {
    const exp = 'code=';
    const condition = target.indexOf(exp);
    if (condition !== -1) {
      const requestCode = target.substring(condition + exp.length);
      requestToken(requestCode);
    }
  };

 

injectJavascript는 특정페이지에서만 실행되는 코드가 아니라,
모든 페이지에서 실행되는 코드이다.


그렇기 때문에 url에서 code= 이라는 string 이 없다면 아무런 처리도 하지않는다.

하지만 url에 'code=' 이라는 string이 포함 되어있다면,
해당 url에서 code를 가져와서 requestToken을 가져온다.

 

여기서 부터는 restAPI를 이용한 카카오 로그인과 매우 흡사하게 진행된다.

 

const requestToken = async (code: string,) => {
  const requestTokenUrl = 'https://kauth.kakao.com/oauth/token';

  const options = qs.stringify({
    grant_type: 'authorization_code',
    client_id: REST_API_KEY,
    redirect_uri: REDIRECT_URI,
    code,
  });

  try {
    const tokenResponse = await axios.post(requestTokenUrl, options);
    const ACCESS_TOKEN = tokenResponse.data.access_token;

    const body = {
      ACCESS_TOKEN,
    };
    const response = await axios.post(REDIRECT_URI, body);
    const value = response.data;
    const result = await storeUser(value);
    if (result === 'stored') {
      const user = await getData('user');
      dispatch(read_S(user));
      await navigation.navigate('Main');
    }
  } catch (e) {
    console.log(e);
  }
};

 

객체를 queryString 형식으로 변경하기 위해서 qs 라이브러리를 사용했다.


카카오와 통신을 한 뒤, ACCESS_TOKEN을 받아와서,
Back-end로 전달을 해주면 Back-end 에서 ACCESS_TOKEN을 이용해 유저정보를 카카오 서버로 부터 가져온다.

 

굳이 Front 에서 유저정보를 받아오지 않고 Back-end에서 처리하는 이유는
ACCESS_TOKEN을 이용해 유저정보를 받아오는 요청은
우리가 카카오 디벨로퍼에서 Redirect URI에 적어놓은 주소에서만 가능하기 때문이다.

 

Back-end와 axios통신 이후의 코드는
우리 프로젝트에서 로그인 이후의 처리를 해주는 과정이므로 각자 프로젝트의 플로우차트에 맞게 설계하면 될듯하다.

3. Back-end

여기까지 왔으면 Back-end는 매우매우매우 수월하다.

 

export const login = async (req: Request, res: Response) => {
  console.log(req.body);
  const { ACCESS_TOKEN } = req.body;
  let tmp: AxiosResponse;
  try {
    const url = 'https://kapi.kakao.com/v2/user/me';
    const Header = {
      headers: {
        Authorization: `Bearer ${ACCESS_TOKEN}`,
      },
    };
    tmp = await axios.get(url, Header);
  } catch (e) {
    console.log('액시오스 에러');
    console.log(e);

    const response: Failure<string> = {
      result: 'fail',
      error: '토큰 에러',
    };

    res.send(response);
    return;
  }

  try {
    const { data } = tmp as AxiosResponse;
    const { id, properties } = data;
    const { nickname } = properties;

    const result = await Users.findOne({ where: { u_id: id } });

    if (result) {
      const response: Success<Users> = {
        result: 'success',
        data: result,
      };
      res.send(response);
    } else {
      const payload = {
        u_id: id,
        u_alias: nickname,
      };
      await Users.create(payload as any);
      const data = (await Users.findOne({ where: { u_id: id } })) as Users;
      const response: Success<Users> = {
        result: 'success',
        data,
      };

      res.send(response);
    }
  } catch (e) {
    console.log(e);
    let msg = '';
    if (typeof e === 'string') {
      msg = e;
    } else if (e instanceof Error) {
      msg = e.message;
    }
    const response: Failure<string> = {
      result: 'fail',
      error: msg,
    };

    res.send(response);
  }
};

 

Front에서 전달받은 Token을 가지고,
카카오와 통신하여 유저정보를 받아낸 뒤,

해당 유저정보가 우리 DB에 들어있으면, 우리 DB에 있는 유저정보를 프론트에 전달해주고,


유저정보가 없을 때는 insert 해준 뒤에,
insert한 data를 Front에 전달해주면 된다.

 

Back-end 처리는 기존의 소셜로그인 방식과 동일하기 때문에 굳이 부가적인 설명은 필요 없을 듯 하다.


이번 프로젝트에서는 sequelize를 이용했기 때문에 메서드를 이용해서 DB와 통신 하였지만,
당연히 sequelize 없이 그냥 SQL문으로도 가능하다.

 

이렇게 하면 길고도 험난했던 expo에서 Webview를 이용한 카카오 로그인 구현이 끝이났다.


진짜 어렵고 난해했고, 심지어 블로그를 쓰고있는 지금에서야 새롭게 이해한 부분도 있을 만큼
챌린징했던 기능이었는데,
이렇게 블로그로 정리할 수 있게 돼서 매우 뿌듯하다.

728x90
728x90

'경일 > React' 카테고리의 다른 글

[React] router & redux  (0) 2022.05.03
[React] state, event, 조건에 따른 render  (0) 2022.04.13
[React] 첫걸음(JSX, Babel, Component, Props)  (0) 2022.04.13

댓글