OAuth 2.0 & Social Login - ParkEunwoo/seoul-smart-app GitHub Wiki

μ†Œμ…œλ‘œκ·ΈμΈ

React native expo둜 κ°œλ°œμ„ μ§„ν–‰ν•˜λ©΄μ„œ, μ†Œμ…œλ‘œκ·ΈμΈμ„ κ΅¬ν˜„ν•˜λŠ”λ° μ•Œμ•„μ•Ό ν•  κ°œλ…λ“€μ„ λ¨Όμ € μ•Œμž.

OAuth 2.0μ΄λž€?

  • API(or μ„œλΉ„μŠ€) μ΄μš©μ‹œ 인증, λ¦¬μ†ŒμŠ€(제곡 μ„œλΉ„μŠ€)에 λŒ€ν•œ κΆŒν•œλΆ€μ—¬λ°©λ²• 쀑 ν•˜λ‚˜.
    • λΉ„μœ ν•˜μžλ©΄, 둜그인과 OAuthλŠ” λΆ„λ¦¬ν•΄μ„œ μ΄ν•΄ν•΄μ•Όν•œλ‹€! 방문증과 μ‚¬μ›μ¦μ˜ 차이..
  • μ„œλ²„-ν΄λΌμ΄μ–ΈνŠΈ 사이에 인증 μ™„λ£Œ μ‹œ κΆŒν•œλΆ€μ—¬μ˜ 결과둜 μ•‘μ„ΈμŠ€ 토큰(Access Token)을 λ°œκΈ‰ -> ν΄λΌμ΄μ–ΈνŠΈ: μ•‘μ„ΈμŠ€ 토큰을 μ΄μš©ν•΄ API(or μ„œλΉ„μŠ€)에 μ ‘κ·Ό/μ„œλΉ„μŠ€ μš”μ²­ -> μ„œλ²„: μ•‘μ„ΈμŠ€ 토큰 기반으둜 μ„œλΉ„μŠ€/μ ‘κ·Όμ—¬λΆ€λ₯Ό νŒλ‹¨ν•΄ 데이터 제곡
  • ꡬ글, 카카였, νŽ˜μ΄μŠ€λΆμ—μ„œ OAuth 2.0을 톡해 μ„œλΉ„μŠ€μ—λŒ€ν•œ 인증/κΆŒν•œλΆ€μ—¬λ₯Ό μ‚¬μš©ν•˜κ³ μžˆμŒ

μ‚¬μš©μž 인증

[ν΄λΌμ΄μ–ΈνŠΈ-μ„œλ²„ 졜초 μΈμ¦μ‹œ]

  • ν΄λΌμ΄μ–ΈνŠΈ: 이메일, λΉ„λ°€λ²ˆν˜Έλ₯Ό λ¦¬ν€˜μŠ€νŠΈ 바디에 λ‹΄μ•„ μ„œλ²„λ‘œ 인증 μš”μ²­
  • μ„œλ²„: ν™•μΈν•˜μ—¬ 인증된 ν΄λΌμ΄μ–ΈνŠΈ 정보λ₯Ό μ„Έμ…˜μ— μ €μž₯← νŒ¨μŠ€ν¬νŠΈκ°€ ν•˜λŠ” 일 [ν΄λΌμ΄μ–ΈνŠΈ-μ„œλ²„ n번째(>1) 인증 μ‹œ]
  • ν΄λΌμ΄μ–ΈνŠΈ(졜초 인증 λ‚΄λ ₯o): μ„œλ²„μ—μ„œ 받은 μ„Έμ…˜μ•„μ΄λ””λ₯Ό 쿠킀에 μ €μž₯ ν›„ 인증이 ν•„μš”ν•œ API 호좜 μ‹œ μ„Έμ…˜ 아이디 정보λ₯Ό ν•¨κ»˜ λ‹΄μ•„ μš”μ²­
  • μ„œλ²„ : μΏ ν‚€μ˜ μΈμ¦ν–ˆλ˜ λ‚΄λ ₯을 보고 api 응닡을 λ³΄λ‚΄μ€Œ

OAuth 2.0의 μ•‘μ„ΈμŠ€ 토큰(access token)

  1. μ„Έμ…˜μ— 인증 정보λ₯Ό μ €μž₯ ν•  ν•„μš”κ°€ μ—†μŒ
    • μ•‘μ„ΈμŠ€ 토큰에 인증정보가 있기 λ•Œλ¬Έ!
    • μ„œλ²„μ—μ„œλŠ” λ””μ½”λ”©ν•˜μ—¬ 확인 κ°€λŠ₯
  2. 인증 ν›„ νšλ“ν•œ μ•‘μ„ΈμŠ€ 토큰을 헀더에 λ„£μ–΄ 호좜
    • 개발 쀑 μ„œλ²„κ°€ μž¬κ΅¬λ™ 될 λ•Œ 둜그인 ν”„λ‘œν† μ½œμ„ ν˜ΈμΆœν•˜μ§€ μ•Šμ•„λ„ λœλ‹€.

OAuth 적용 절차

OAuth 2.0 기반의 μ‚¬μš©μž 인증 κΈ°λŠ₯을 ν†΅ν•˜μ—¬ 넀이버/μΉ΄μΉ΄μ˜€κ°€ μ•„λ‹Œ λ‹€λ₯Έμ„œλΉ„μŠ€μ—μ„œ λ„€μ΄λ²„μ˜ μ‚¬μš©μž 인증 κΈ°λŠ₯을 μ΄μš©ν•  수 있게 ν•˜λŠ” μ„œλΉ„μŠ€

  1. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 등둝 λ„€μ΄λ²„μ•„μ΄λ””λ‘œ 둜그인 μ μš©μ„ μœ„ν•΄ μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 등둝, ν΄λΌμ΄μ–ΈνŠΈ 아이디/μ‹œν¬λ¦Ώ ν‚€ λ°œκΈ‰
  2. μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 개발
  3. μ„œλΉ„μŠ€μ— OAuth 적용

OAuth의 κ°œλ…μ€ 이렇고, react native - expo ν”„λ‘œμ νŠΈμ— μ‹€μ§ˆμ μœΌλ‘œ μ‚¬μš©ν•˜κΈ° μœ„ν•œ λ‚΄μš©/μ½”λ“œλ“€μ„ 짚고 λ„˜μ–΄κ°„λ‹€. κ·ΈλŸ¬λ©΄μ„œ AuthSession, Axios 에 κ΄€ν•œ λ‚΄μš©μ„ 같이 μ΄ν•΄ν•˜λ„λ‘ ν•˜μž.


Axios

Axiosλž€?

  • HTTP ν΄λΌμ΄μ–ΈνŠΈ 라이브러리
  • 비동기 λ°©μ‹μœΌλ‘œ HTTP 데이터 μš”μ²­μ„ μ‹€ν–‰
  • λ‚΄λΆ€μ μœΌλ‘œ axiosλŠ” μ§μ ‘μ μœΌλ‘œ XMLHttpRequestλ₯Ό 닀루지 μ•Šκ³  β€œAJAX 호좜(μžλ°”μŠ€ν¬λ¦½νŠΈλ‘œ μ„œλ²„μ— 데이터λ₯Ό μš”μ²­)”이 κ°€λŠ₯ν•˜λ‹€. => μ‰½κ²Œλ§ν•΄, axiosλ₯Ό 톡해 HTTPμš”μ²­(μ›Ήμ„œλ²„-ν΄λΌμ΄μ–ΈνŠΈ κ°„ ν†΅μ‹ κ·œμ•½μœΌλ‘œ, λ¬Έμ„œ κ΅ν™˜μ„ μœ„ν•΄ μš”μ²­/μ‘λ‹΅ν•˜λŠ” 것)으둜 데이터λ₯Ό κ°€μ Έμ˜¬ 수 μžˆλ‹€!

axios μ„€μΉ˜

npm install axios --save; $ yarn add axios

μ‚¬μš© 법

  1. import
import axuis from "axios";
  1. .GET
  • 넀이버 oauth2.0을 톡해 access_token에 getν•œ 값을 μ €μž₯ν•œλ‹€.
const {
      data: { access_token }
    } = await axios.get(`https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&client_id=${NV_APP_ID}
    &client_secret=${NV_APP_SECRET}
    &code=${code}&state=${STATE_STRING}`);

setToken(data.access_token); //setState
  • bearer 토큰을 μ£Όκ³ λŠ” 것을 인증으둜 ν•œλ‹€. : Bearer ν† ν°μ΄λž€, μ•”ν˜Έν™”ν•˜μ§€ μ•Šμ€ κ·Έλƒ₯! ν† ν°μœΌλ‘œ 기본적으둜 HTTPS λ₯Ό μ‚¬μš©ν•˜κΈ° λ•Œλ¬Έμ— 토큰을 μ•ˆμ „ν•˜κ²Œ μ£Όκ³ λ°›λŠ” 것은 HTTPS의 μ•”ν˜Έν™”μ— μ˜μ‘΄ν•œλ‹€. APIλ₯Ό 호좜 ν•  λ•Œ κ°„λ‹¨ν•˜κ²Œ Header 에 λ„£μ–΄ λ³΄λ‚΄λŠ” κ²ƒμœΌλ‘œ, api ν…ŒμŠ€νŠΈλ„ κ°€λŠ₯ν•΄λ‹€!
    const config = {
      headers: {
        Authorization: `Bearer ${access_token}`
      }
    };

λ°›μ•„μ˜¨ 넀이버 api에 headerλ₯Ό λ‹΄μ•„ 보내 λ‹€μ‹œν•œλ²ˆ getν•˜μ—¬ dataλ₯Ό μ–»λŠ”λ‹€.

    const { data } = await axios.get(
      "https://openapi.naver.com/v1/nid/me",
      config
    );
    console.log(data);
    setUser(data);
  }

AuthSession

: webbrowser μœ„μ— κ΅¬μΆ•λœ μ›Ή λΈŒλΌμš°μ € 기반 OAuth flow(μ›ΉλΈŒλΌμš°μ € 기반 인증) 을 앱에 μΆ”κ°€ν•˜λŠ” 방법!

  • 앱이 λ‹€μ–‘ν•œ URL에 μžˆμ„ 수 μžˆκΈ°λ•Œλ¬Έμ—, expoμ—μ„œ νŠΉνžˆλ‚˜ μœ μš©ν•˜λ‹€!

μ›Ή λΈŒλΌμš°μ € 기반 인증 νλ¦„μ˜ μž‘λ™ 방식

  1. μ‹œμž‘ : user κ°€ login λ²„νŠΌ λˆ„λ¦„
  2. μ›ΉλΈŒλΌμš°μ € μ—΄κΈ°: 앱이 μ›ΉλΈŒλΌμš°μ €λ₯Ό μ—΄κ³ , 둜그인 νŽ˜μ΄μ§€λ₯Ό μœ„ν•œ openURLμ—λŠ” 일반적으둜 앱을 μ‹λ³„ν•˜κΈ°μœ„ν•œ 정보 + μ„±κ³΅μ‹œ redirection(λ¦¬λ””λ ‰μ…˜) ν•  URL이 포함됨. cf. μ›ΉλΈŒλΌμš°μ €λŠ” 쿠킀에 기인증여뢀 등을 λ‹΄κ³  μ›€μ§μ΄κΈ°λ•Œλ¬Έμ— 졜초 둜그인 ν›„μ—λŠ”, λ‹€μ‹œ 둜그인 ν•  ν•„μš”κ°€ μ—†λ‹€!
  3. 인증 κ³΅κΈ‰μžκ°€ λ¦¬λ””λ ‰μ…˜: 성곡적인 인증 μ‹œ 인증 κ³΅κΈ‰μž νŽ˜μ΄μ§€μ˜ URL이 제곡 ν—ˆμš©λœ λ¦¬λ””λ ‰μ…˜ URL의 ν—ˆμš© λͺ©λ‘μ— μ‘΄μž¬ν•˜κ³ , λ¦¬λ””λ ‰μ…˜μ—λŠ” μœ„μΉ˜ ν•΄μ‹œ, 쿼리 λ§€κ°œλ³€μˆ˜ λ˜λŠ” λ‘˜λ‹€μ˜ URL(ex. UserID, token) λ“±μ˜ 데이터가 ν¬ν•¨λ˜μ–΄μžˆμŒ
  4. 앱이 λ¦¬λ””λ ‰μ…˜ 처리, λ¦¬λ””λž™μ…˜ URLμ—μ„œ 데이터λ₯Ό ꡬ문뢄석

AuthSession μ‚¬μš©λ²•

  1. μ‚¬μš©μž 인증
https://auth.expo.io/@your-user-name/your-app-slug
  1. 인증 방식
  • 인증 성곡 μ‹œ 인증 κ³΅κΈ‰μžκ°€ ν•΄λ‹Ή Expo Auth URL둜 λ¦¬λ””λ ‰μ…˜
  • Expo Auth μ„œλΉ„μŠ€κ°€ λ‹€μ‹œ μ‘μš©ν”„λ‘œκ·Έλž¨μœΌλ‘œ λ¦¬λ””λ ‰μ…˜
  • μ΄λ•Œ, μΈμ¦μ„œλΉ„μŠ€κ°€ λ‹€μ‹œ λ¦¬λ””λ ‰μ…˜ν•˜λŠ” URL이 μ•±/λ…λ¦½μ‹€ν–‰ν˜• μ•± 체계인
https://auth.expo.io/@your-user-name/your-app-slug
λ˜λŠ”
yourscheme:// ~~~~

에 κ²Œμ‹œλœ URLκ³Ό μΌμΉ˜ν•˜μ§€ μ•ŠμœΌλ©΄ ->κ²½κ³ νŽ˜μ΄μ§€!

  • expoμ—μ„œ μ„œλΉ„μŠ€ url
https://auth.expo.io/@your-username/your-app-slug/start

을 μ„œλΉ„μŠ€ url둜 ν•˜μ—¬, authsesi 5. API

  • AuthSession.startAsync(options) : AuthSession
*let*result=awaitAuthSession.startAsync({
authUrl:`https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${NV_APP_ID}&redirect_uri=${encodeURIComponent(redirectUrl)}&state=${STATE_STRING}`,
});
  • AuthSession.getRedirecrUrl() : 인증 κ³΅κΈ‰μžκ°€ λ¦¬λ””λ ‰μ…˜ν•΄μ•Όν•˜λŠ” url을 κ°€μ Έμ˜΄ encodeURIComponent() : gettRedirectUrl ν•΄ κ°€μ Έμ˜¨ url을 인코딩
*let*redirectUrl=AuthSession.getRedirectUrl();
  • AuthSession.startAsync() : 인증 μ„Έμ…˜μ„ μ‹œμž‘ν•˜λŠ” λ©”μ„œλ“œ. : 인증 κ³΅κΈ‰μžλ‘œλΆ€ν„° μ „λ‹¬λœ 정보 (ex. userID)λ₯Ό μ‚¬μš©ν•˜μ—¬ 개체둜 ν™•μΈλ˜λŠ” 것을 λ°˜ν™˜!
const result = await AuthSession.startAsync({ authUrl: `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${NV_APP_ID}&redirect_uri=${encodeURIComponent( redirectUrl )}&state=${STATE_STRING}` });

this.setState({ result });

μœ„ μ½”λ“œμ—μ„œμ˜ url/ν˜•μ‹μ€ 인증 μ„œλΉ„μŠ€μ— 따라 λ‹€λ₯΄λ‹€. μœ„λŠ” λ„€μ•„λ‘œμ˜ 인증 μ„œλΉ„μŠ€ μ˜ˆμ‹œλ‹€.


OAuth provider λ₯Ό μ‚¬μš©ν•˜κΈ° μ „ μ…‹νŒ…

λ¦¬λ””λ ‰μ…˜μ„ ν•„μš”λ‘œ ν•˜λŠ” AuthSession을 μ‚¬μš©ν•˜λ €λ©΄ expo login을 κΌ­! ν•΄μ£Όμ–΄μ•Ό ν•œλ‹€.

bangsoyun-ui-MacBook-Pro:login-example pongso$ expo login
β”Œβ”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”
β”‚                                                             β”‚
β”‚   There is a new version of expo-cli available (3.0.10).    β”‚
β”‚   You are currently using expo-cli 2.18.0                   β”‚
β”‚   Run `npm install -g expo-cli` to get the latest version   β”‚
β”‚                                                             β”‚
β””β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”€β”˜
? Username/Email Address: pongsoyun
? Password: [hidden]

Success. You are now logged in as pongsoyun.
bangsoyun-ui-MacBook-Pro:login-example pongso$ exp start
We've built a brand new CLI for Expo!
Expo CLI is a drop in replacement for exp.
Install: npm install -g expo-cli
Use: expo --help
Read more: https://blog.expo.io/expo-cli-2-0-released-a7a9c250e99c
[02:09:26] Using project at /Users/pongso/loginExample/login-example
[02:09:30] Starting Metro Bundler on port 19001.
[02:09:32] Tunnel ready.
[02:09:32] Expo is ready.

넀이버 μ•„μ΄λ””λ‘œ 둜그인

  1. 넀이버 개발자 νšŒμ›κ°€μž… -> 둜그인 -> λ„€μ•„λ‘œ(넀이버 μ•„μ΄λ””λ‘œ 둜그인)/μ• ν”Œλ¦¬μΌ€μ΄μ…˜ 등둝
  2. api μ„€μ • > 둜그인 μ˜€ν”ˆ API μ„œλΉ„μŠ€ ν™˜κ²½ > ν™˜κ²½ μΆ”κ°€(Mobile web)
  • λ¦¬μ•‘νŠΈ λ„€μ΄ν‹°λΈŒ μ•± 개발 μ‹œ, and/ios 둜 λΉŒλ“œν•˜κ³  μ•± 개발 μ‹œμ—λŠ” android/ios 폴더 λ‚΄λΆ€λ‘œ λ“€μ–΄κ°€ λ”°λ‘œ μž‘μ—…μ„ 해주어도 λ˜μ§€λ§Œ, ν˜„μž¬ μš°λ¦¬λŠ” expoλ₯Ό μ΄μš©ν•˜μ—¬ κ°œλ°œμ„ μ§„ν–‰ν•˜κ³ μžˆκΈ° λ•Œλ¬Έμ— μ•±λ‚΄μ—μ„œ μ™ΈλΆ€ 웹을 μ˜€ν”ˆν•  것이닀.
[μ„œλΉ„μŠ€ URL]
https://auth.expo.io/@pongsoyun/login-example/start

[CallBack URL]
https://auth.expo.io/@pongsoyun/login-example

을 κΈ°μž¬ν•΄ μ€€λ‹€. 3. yarn start λ₯Ό 톡해 expo μ‹€ν–‰ 4. μ½”λ“œ λ„€μ•„λ‘œ κ΅¬ν˜„μ„ μœ„ν•΄, μœ„μ— κΈ°μž¬ν•œ κ°œλ…λ“€μ„ μ μš©ν•œ μ½”λ“œμ΄λ‹€.

import React, { useState } from "react";
import { StyleSheet, Text, View, TouchableOpacity } from "react-native";
import { AuthSession } from "expo";
import axios from "axios";

//
const NV_APP_ID = "YOUR_CLIENT_ID";
const NV_APP_SECRET = "YOUR_CLIENT_PW";
const STATE_STRING = "YOUR_SECRET_STRING";

const styles = StyleSheet.create({
  container: {
    flex: 1,
    backgroundColor: "#fff",
    alignItems: "center",
    justifyContent: "center"
  }
});

export default function App() {
	//useState(), token/code/user
  const [token, setToken] = useState();
  const [code, setCode] = useState();
  const [user, setUser] = useState();

	//authsession
  async function handlePressAsync() {
    let redirectUrl = AuthSession.getRedirectUrl();
    console.log(redirectUrl);
    console.log(encodeURIComponent(redirectUrl));

    const result = await AuthSession.startAsync({
      authUrl: `https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=${NV_APP_ID}&redirect_uri=${encodeURIComponent(
        redirectUrl
      )}&state=${STATE_STRING}`
    });

    console.log("result", result);

    setCode(result.code);

    handleGetAccess();
  }
	
	//http data request
  async function handleGetAccess() {
    const {
      data: { access_token }
    } = await axios.get(`https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&client_id=${NV_APP_ID}
    &client_secret=${NV_APP_SECRET}
    &code=${code}&state=${STATE_STRING}`);

    const config = {
      headers: {
        Authorization: `Bearer ${access_token}`
      }
    };

    setToken(data.access_token);

    const { data } = await axios.get(
      "https://openapi.naver.com/v1/nid/me",
      config
    );
    console.log(data);
    setUser(data);
  }


//TEXT press -> handlePressAsync()
  return (
    <View style={styles.container}>
      <TouchableOpacity onPress={handlePressAsync}>
        <Text>넀이버 μ•„μ΄λ””λ‘œ μ‹œμž‘ν•˜κΈ°</Text>
      </TouchableOpacity>
    </View>
  );
}
  1. CALL BACK 응닡정보
API μš”μ²­ μ„±κ³΅μ‹œ : http://콜백URL/redirect?code={codeκ°’}&state={stateκ°’} 
API μš”μ²­ μ‹€νŒ¨μ‹œ : http://콜백URL/redirect?state={stateκ°’}&error={μ—λŸ¬μ½”λ“œκ°’}&error_description={μ—λŸ¬λ©”μ‹œμ§€}
⚠️ **GitHub.com Fallback** ⚠️