퀘스트 | 인사이트/로그 | 웹에서 소셜 로그인 8개 연동하기

웹에서 소셜 로그인 8개 연동하기
인사이트/로그개발 관련

웹에서 소셜 로그인 8개 연동하기

#소셜로그인연동 #네이버 #카카오 #페이스북 #애플 #구글 #패스포트 #콜백 #값파싱 #프로필파싱

작성일 : 24.03.20 12:04

0

0

0

👉 본문을 50%이상을 읽으면 '여기까지다' 퀘스트가 완료됩니다(로그인 필수)

오늘은 렛플에서 구현되어있는 SNS 로그인(소셜)로그인의 구조에 대해 설명하고자 합니다.

연동대상은 카카오, 네이버, 구글,페이스북, 애플, 깃허브, 드리블, 피그마 총 8개입니다.

실제 구현하더라도 , SNS별로 주는 값이나, 표시형식도 다 다르기때문에,

그 값 찾는것들도 한세월이기도하고, 헤매었던 부분들도 있어서, 이 부분 같이 포함해서 설명해드리면 좋을 것 같네요.

1. 현재 렛플에서 연동하고 있는 소셜로그인의 종류

렛플에서는 총 8개의 소셜로그인을 도입하였으며

1) 회원가입 및 로그인에서 사용하는 소셜로그인은 5개이며,

원래는 4개정도만 지원하려다가 , IOS앱 때문에, 어쩔수없이 애플 로그인이 추가되었습니다.

앞서 말씀드렸다시피, 각 소셜 로그인 마다 아래와 같은 정보의 취득이 가능합니다.

https://letspl.me/quest/819

그러나 필수값이라면 아래 정도가 필수가 되겠죠

카카오 : [ 이름(닉네임),, 프로필이미지, 이메일, 성별, 생일, 연령대, 출생년도, 휴대전화번호, ]

네이버 : [ 이름(닉네임), 프로필이미지, 이메일, 성별, 생일, 연령대, 출생년도, 휴대전화번호 ]

구글 : [이름(닉네임), 프로필 이미지, 이메일]

페이스북 : [ 이름(닉네임) , 프로필 이미지, 이메일 , 연령대, 생일,성별 ]

+

애플 : [이름(닉네임), 이메일주소]

2) 소셜로그인을 활용한 정보추가 및 관리 부분은 3가지입니다.

깃허브 - 관리하고 있는 코드 저장소의 정보와 함께, 사용언어 정보를 가져오기위해서 연동합니다.

드리블 - 드리블에 저장되어있는 포트폴리오 정보를 가지고 오기위하여 연동합니다.

피그마 - 팀 공간에서의 신규 작업/댓글등의 인터랙션을 가져오기위하여 사용합니다.

2. 개발 전에 등록 및 설치가 필요한 부분

저희는

은 node.js를 쓰고 있어서, node+리액트으로 말씀드리고

은 리액트네이티브로 구현되어있어, 리액트네이티브로 말씀드리려고 합니다.

실제 각 서비스(구글 등)에서 권한 획득 및 설정은 서비스마다 다른 부분이 있고, 잘 정리된 글들이 있어서 이부분은 하단을 참고하시면 되겠습니다.

네이버 : https://developers.naver.com/docs/common/openapiguide/appregister.md

카카오 : https://developers.kakao.com/docs/latest/ko/kakaologin/prerequisite

구글 : https://notspoon.tistory.com/45

페이스북 : https://nachwon.github.io/insta-facebook/

애플 : https://velog.io/@apparatus1/React-%EC%9B%B9%EC%97%90-Sign-in-With-Apple-%EB%8F%84%EC%9E%85%ED%95%98%EA%B8%B0

깃허브/드리블/피그마 : https://letspl.me/quest/693

Passport의 버전에 따라서, 세션 등을 더이상 쓰지 않는 버전이 생기는 것으로 보입니다.

제가 3년전부터 해당 Passport Package를 사용했는데, 최근 버전에서는 호환이 안되기도 해서, 기존 버전인 0.41 그대로 사용하려고 합니다.

웹(리액트)에서 사용하는 패키지 

"passport": "0.4.1",
"passport-apple": "^2.0.2",
"passport-dribbble": "^1.0.1",
"passport-facebook": "^3.0.0",
"passport-figma2": "^1.0.0",
"passport-github2": "^0.1.12",
"passport-google-oauth20": "^2.0.0",
"passport-kakao": "^1.0.0",
"passport-local": "^1.0.0",
"passport-naver": "^1.0.6",
"verify-apple-id-token": "^3.1.2",

리액트네이티브에서는 아래패키지를 사용합니다.

다만 깃허브나 피그마, 드리블의 경우 리액트네이티브로 구현하기에는 빡세보여서 , 웹에서만 구현하였습니다.

앱(리액트네이티브)에서 사용하는 패키지

"@react-native-google-signin/google-signin": "^11.0.0"
"@react-native-seoul/kakao-login": "^5.3.1",
"@react-native-seoul/naver-login": "^3.0.0",
"@invertase/react-native-apple-authentication": "^2.3.0",
"@react-native-google-signin/google-signin": "^11.0.0",
"react-native-fbsdk-next": "^12.1.3",

3. 패스포트를 꼭 써야되나요?

꼭 Passport.js를 굳이 사용할 필요는 없을 것 같습니다.

1개나 두개의 소셜로그인을 연동한다면, 패스포트까지 쓸 필요는 없는데, 저희는 8개나 되기 때문에, 코드의 재활용성을 높이기 위해서 , 패스포트를 써서 구현했습니다.

중요한건, 실제 어떻게 작동하는지에 대해서는 패스포트 사용없이 1개정도는 구현을 해볼 필요가 있습니다.

Oauth가 무엇인지 어떤식으로 보안을 지킬 수 있는지에 대해서 보기위해서는, 저는 Request라는 모듈을 써서 처음에는 직접 구현했었습니다.

위의 내용이 어떤 내용인지 이해하면, 오픈 API에 대한 이해도가 높아져서 , 나중에 도움이 되더라구요.

그러나 원리를 이해한 다음에는 , 근데 역시 패스포트가 편합니다.

모든 소셜 로그인은 “accessToken”을 가지고 통신을 하게 되고, “refreshToken”을 줍니다.

기본적으로는 일회성 로그인에 집중되어있고, 해당 토큰은 시간이 지나면 소멸되기 때문에, refreshToken을 가지고, 기한을 계속 늘려주는 구조입니다.

근데 refreshToken은 잘 사용하지는 않고 있습니다.

accessToken을 한번 받은다음에, 계속 갱신하려면, 프로필 이미지를 계속 받아온다거나 하는 식의 통신이 있어야 하는데 저희는 사용하지 않거든요

4. 키 값은 이메일이 아닌 ID입니다.

accessToken을 가지고, profile 이미지를 조회하게 되면 여러가지 정보를 줍니다.

기본적으로는 ID+ 이름 + 이메일은 대부분 받을 수 있습니다.

1) ID

해당 소셜 로그인에서 사이트별로 채번하는 값으로, 계정과 1대 다수로 연계됩니다.

예를 들면 제가 카카오를 통해서 렛플을 쓰거나, 다른 서비스를 쓴다면 , 렛플과 서비스에는 다른 ID를 던져줍니다.

다만, 내가 탈퇴 후 재가입하더라도, 동일한 ID를 던져주더라구요.

그러니 키 값으로 사용하기 적당해보입니다.

2) 이름

이름입니다. 미들네임 주기도 하고, 성,이름 나눠주기도 하고 서비스별로 제각각입니다.

3) 이메일

여러 소셜로그인별로 동일한 이메일을 쓰시는 분들도 많기 때문에,

이메일은 키 값으로 사용하면 나중에 문제될 여지가 많습니다.

회원안내도 그렇고, 동일한 내용이 두번 발송될 수도 있고, 잘못을 인지하기도 어려울 수 있습니다.

이메일을 받더라도, 기존에 DB상에 등록된 것이 있는지 여부를 체크해야 됩니다.

5. 실제 값을 어떻게 파싱하나요?

  1. 저희는 소셜로그인을 로그인정보 취득 으로 분리했습니다 . 각 목적별로는 호출 주소는 동일하지만 파라미터만 분리하고 있습니다.

예를 들면 회원가입의 경우

/?login_source= 로 구분하여 값을 네이버, 카카오 , 구글, 애플별로 달리 쓰고 있습니다.

깃허브/드리블/피그마는 목적이 달라, 아예 다른 서버URL를 사용하고 있습니다.

또한 회원가입과 로그인을 버튼은 다르지만 처리는 일원화됩니다

유저는 어느것으로 회원가입을 했는지 모를가능성이 크기 때문에,

회원가입/로그인 어느 버튼을 눌러도, 가입이 되어있으면, 로그인을 시키고, 가입이 안되어있으면, 회원가입으로 전환합니다.

회원가입/로그인

var express = require("express");
var passport = require("passport");
var NaverStrategy = require("passport-naver").Strategy;
var KakaoStrategy = require("passport-kakao").Strategy;
var GoogleStrategy = require("passport-google-oauth20").Strategy;
var FacebookStrategy = require("passport-facebook").Strategy;
var AppleStrategy = require("passport-apple");
var verifyAppleToken = require("verify-apple-id-token").default;
//server.js에서 설정

const app = express();
passport.serializeUser(function (user, done) {
  done(null, user);
});

passport.deserializeUser(function (obj, done) {
  done(null, obj);
});

app.use(passport.initialize());
app.use(passport.session());

  1. 1. 네이버

//각 로그인처리하는 경로에서 처리
passport.use(
 new NaverStrategy(
    {
     clientID: 'clientID',
     clientSecret: 'clientSecret',
     callbackURL: 'callbackURL',
     },
function (accessToken, refreshToken, profile, done) {
      process.nextTick(function () {
         var user={}
         console.log(profile.id,);
         console.log(profile.displayName);
         console.log(profile.emails); 
         console.log(profile._json.profile_image) //default값은 https://ssl.pstatic.net/static/pwe/address/img_profile.png
         console.log(profile._json.age)//14~19 ,20~24 이런식으로 들어옵니다.
         console.log(profile._json.birthday) //MM-DD 형식입니다.
         console.log(profile._json.gender) //- F: 여성, M: 남성, U: 확인불가
         console.log(profile._json.mobile)
       
      return done(null, user);
//이후 redirect URL로 넘어갑니다.

   })
})
)

  1. 2. 카카오

//각 로그인처리하는 경로에서 처리
passport.use(
 new KakaoStrategy(
    {
     clientID: 'clientID',
     clientSecret: 'clientSecret',
     callbackURL: 'callbackURL',
     },
function (accessToken, refreshToken, profile, done) {
      process.nextTick(function () {
         var user={}
         console.log(profile._json.id);
         console.log(profile._json.properties.nickname);
         console.log(profile._json.kakao_account.email); 
         console.log(profile._json.properties.profile_image) //default값은 https://ssl.pstatic.net/static/pwe/address/img_profile.png
         console.log(profile._json.kakao_account.age_range)//14~19 ,20~24 이런식으로 들어옵니다.
         console.log(profile._json.kakao_account.birthday) //MMDD로 들어옵니다.
         console.log(profile._json.kakao_account.gender) // male/female 로 들어옵니다.
         console.log(profile._json.kakao_account.phone_number) //82 10-XXXX-XXXX 이런식으로 들어옵니다.
       
      return done(null, user);
//이후 redirect URL로 넘어갑니다.
  })
})
)

  1. 3. 구글

//각 로그인처리하는 경로에서 처리
passport.use(
 new GoogleStrategy(
    {
     clientID: 'clientID',
     clientSecret: 'clientSecret',
     callbackURL: 'callbackURL',
     },
function (accessToken, refreshToken, profile, done) {
      process.nextTick(function () {
         var user={}
         console.log(profile.id);
         console.log(profile.email); 
         console.log(profile.verified_email) //true,false로 줍니다.
         console.log(profile.name)
         console.log(profile.given_name) 
         console.log(profile.family_name) 
         console.log(profile.picture) //url형식으로 줍니다.
         console.log(profile.locale) //ko등으로 줍니다.
      return done(null, user);
//이후 redirect URL로 넘어갑니다.
  })
})
)

  1. 4. 페이스북

//각 로그인처리하는 경로에서 처리
passport.use(
 new FacebookStrategy(
    {
     clientID: 'clientID',
     clientSecret: 'clientSecret',
     callbackURL: 'callbackURL',
     },
function (accessToken, refreshToken, profile, done) {
      process.nextTick(function () {
         var user={}
         console.log(profile.id);
         console.log(profile.displayName);
         console.log(profile._json.email); 
         console.log(profile.profileUrl) 
      return done(null, user);
//이후 redirect URL로 넘어갑니다.
  })
})
)

  1. 5. 애플

애플의 경우, 프로필 정보를 바로 던져주진 않고, 암호화된 idToken을 던져주고, 이를 다시 복호화하는 작업을 해야합니다.

복호화 이후에 이름에 대한 정보를 받을 수 있습니다.

열받게 이름도 안줍니다. 더이상 처리하기 싫어서 , 이름 값은 알아보지 않고 처리하지 않았습니다.

//각 로그인처리하는 경로에서 처리
passport.use(
 new AppleStrategy(
    {
     clientID: 'clientID',
     clientSecret: 'clientSecret',
     callbackURL: 'callbackURL',
     teamID:"teamID",
     keyID:"keyID",
     key:"key",
     privateKeyLocation:"privateKeyLocation",
     scope: ["name", "email"],
     callbackURL:"callbackURL",
     },
function (req, accessToken, refreshToken, idToken, profile, done) {
      
      process.nextTick(function () {
    
      const jwtClaims = await verifyAppleToken({
          idToken: idToken,
          clientId: 'clientId',
      });
      var user={}
     
      console.log(jwtClaims.sub); //아이디값을 던져줍니다.
      console.log(jwtClaims.email); 
      console.log(jwtClaims.email_verified); 
      console.log(jwtClaims.is_private_email);
         
      return done(null, user);
  })
})
)

그다음에는, 애플을 제외하고는 callbackURL로 넘어와서(GET)으로 넘어와서, 데이터를 받아 가입.로그인 처리를 하면 됩니다.

다만, 애플은 post로 밖에 안됩니다. 그러니 애플은 따로 처리해야합니다. ㅠㅠ

저도 이미 구현되어있는 GET방식으로 해보려고 여러가지 시도를 하였지만,

최종적으로는 post로 별도로 구현을 완료하였습니다.

애플은 별도의 로직으로 구성한다고 생각하시는게 맘이 편합니다.

깃허브/드리블/피그마는 아래 내용과 겹치는 것 같아 별도로 작성하지는 않겠습니다.

궁금하시면 아래 글을 참고해보세요

노션,피그마,드리블,깃허브,비핸스,링크드인 연동#2

https://letspl.me/quest/693