[OAuth] OAuth2.0 Facebook 로그인 구현

2020. 7. 5. 14:10Frontend

 

 

https://www.techlicious.com/tip/facebook-account-cloning/

 

 

지난 포스팅에서 OAuth 표준이 무엇인지에 대해서 설명했습니다. 이번 포스팅에서는 직접 Expressjs에서 페이스북 로그인을 구현하는 과정을 설명하도록 하겠습니다.

 

 

 

간단하게 OAuth2.0 표준에 대해서 정리하고 넘어가자면, OAuth 2.0 표준이란 새로운 서비스(예를 들면 쇼핑몰)에서 사용자에게 간편한 회원가입 기능을 제공하면서 제한된 용도로만 사용자를 식별하고, 사용자의 정보를 사용할 수 있도록 하기 위한 프로토콜입니다. ID/PASSWORD를 사용자로부터 직접 입력받는 대신, Facebook, Google등의 사용자가 이미 가입한 인증 서비스로부터 AccessToken을 받아 사용자를 식별하도록 하는 것입니다. 

 

 

 

 

Facebook 로그인이 이루어지는 과정. (출처 Facebook)

 

 

Process

 

 

 

https://developers.facebook.com/

 

Facebook for Developers

Facebook for Developers와 사용자를 연결할 수 있는 코드 인공 지능, 비즈니스 도구, 게임, 오픈 소스, 게시, 소셜 하드웨어, 소셜 통합, 가상 현실 등 다양한 주제를 둘러보세요. Facebook의 글로벌 개발��

developers.facebook.com

 

 

 

우선 위 사이트에 접속해서 Facebook 계정으로 로그인을 수행한 후에 내 앱(my app)탭에 들어갑니다. 거기서 용도에 맞게끔 새로운 앱을 만들어줍니다. "내 앱" 탭에 들어가면 다음과 같은 화면이 나오게 됩니다. 

 

 

여기서 우리가 만들 새 앱의 의미는 로그인 기능을 수행할 인증 서버(인증 API)입니다. 다시말해 Facebook 로그인을 수행하려는 클라이언트로부터 요청을 받아 대신 사용자 인증을 해주고, 유효한 사용자인 경우 AccessToken을 발급하여 요청한 Redirect_uri로 토큰을 전송해주는 역할을 하는 것입니다. AccessToken, Redirect_uri에 대한 자세한 설명과 OAuth2.0에 대한 전반적인 프로세스는 이전 포스팅을 참고해주세요

 

 

 

 

 

 

앱을 생성하고 나면 이렇게 아래와 같이 생성된 앱이 보여집니다. 이 앱을 클릭하여 설정 -> 기본 설정 탭으로 이동합니다. 여기서는 생성된 앱의 고유 아이디와, 시크릿 코드, 앱의 이름과 아이콘, 사용자 이메일등 기본적인 앱의 속성을 확인하고 수정할 수 있습니다.

 

 

 

 

 

 

 

 

여기서 앱의 고유 아이디와, 시크릿 코드는 클라이언트에서 앱으로 로그인 요청을 보낼때 파라미터로 꼭 넘겨야 하는 정보이므로 아주 중요합니다.  고유 아이디와 시크릿 코드를 확인한 후에, 왼쪽 하단의 제품 탭을 클릭하여 Facebook 로그인을 활성화 시켜줍니다. Facebook 로그인을 활성화 시킨다는 의미는 이 앱에서 Facebook을 통한 로그인을 허용하겠다는 의미가 됩니다. 

 

 

 

 

 

 

Facebook로그인을 제품에 추가하고 나면 아래 사진에 보이는 것처럼 제품 하단에 Facebook 로그인 탭이 나타나게 됩니다. 한번 클릭해서 설정 옵션을 확인해 줍니다. 이 옵션에는 OAuth에 관한 여러 가지 옵션들을 설정해 줄 수 있습니다. 

 

 

여기서 중요한 정보는 하단에 보이는 "유효한 OAuth 리디렉션 URI"탭입니다. 개발시에는 Localhost를 사용하기 때문에 여기에 추가해줄 필요가 없지만, 실제로 도메인을 등록하고 실서버에 배포한 이후에는 여기에 본인의 인증관련(예를 들면 www.servicedomain.com/auth/callback) uri를 추가해줍니다. OAuth 표준을 따르는 인증 서버에서는 (여기서는 Facebook) AccessToken을 발급한 후에 이를 파라미터 형식으로 redirect_uri에 넘겨주는데, 만약 이 redirect_uri를 검증하는 과정을 생략하게 되면 악의적인 제 3자가 redirect_uri를 변조해서 피싱사이트에 악의적으로 AccessToken을 넘기도록 할 수 있습니다. 따라서 인증서버는 사전에 등록된 redirect_uri가 아니면 해당 사이트로 리다이렉트 하지 않습니다.

 

 

실제로 서비스를 배포한 후에는 해당 서버의 Redirect_uri를 여기에 꼭 추가해주어야 합니다.

 

 

 

 

 

 

여기까지 진행했다면, 이제 Facebook에서 설정해야 할 단계들이 모두 끝났습니다. 따라서 이를 통해 로그인할 클라이언트 서버를 준비해야 합니다. 실제로 클라이언트는 훨씬 더 복잡하지만, OAuth를 통해서 Facebook의 유효한 AuthToken을 redirect_uri를 통해 받는 것이 이 포스팅의 주된 목적이기 때문에, 최소한의 코드와 최소한의 복잡도로 이를 구현하였음을 양해 바랍니다.

 

 

서버는 nodejs기반의 express를 사용하였습니다. 첫 렌더링 화면에는 Facebook로그인을 위한 버튼만 나타나게 구현하였으며, 로그인이 성공한 후에는 console에 유효한 AccessToken을 찍어주고 "Authorization Success"라는 문구만 나타나는 아주 단순한 애플리케이션을 구현하였습니다.

 

 

먼저 디렉토리를 만든 후(저는 facebook-login이라는 이름의 디렉토리를 만들었습니다.) 다음 명령어를 통해 패키지를 만들었습니다.

Package.json 파일에는 최소한의 Dependency (express, axios, query-string)만을 사용하였습니다.

 

 

npm init
// package.json

{
  "name": "facebook-login",
  "version": "1.0.0",
  "description": "facebook-login with expressjs",
  "main": "app.js",
  "scripts": {
    "test": "echo \"Error: no test specified\" && exit 1"
  },
  "author": "sckimynwa",
  "license": "MIT",
  "dependencies": {
    "axios": "^0.19.2",
    "express": "^4.17.1",
    "query-string": "^6.13.1"
  }
}

 

 

사용자가 /login/facebook으로 접속하면 (localhost:4000/login/facebook) Facebook 로그인을 진행할 수 있는 팝업 버튼만 하나 나타나게 됩니다. (React를 사용하지 않고, 순수 HTML을 사용하였기 때문에 아래 href 부분은 변수가 아닌 실제 값으로 수정해 주어야 합니다.)

 

 

이 버튼을 클릭하면 흔히 보던 Facebook로그인 팝업이 나타나게 되며, 로그인을 마치고 나면 파라미터로 집어넣었던 redirect_uri로 AccessToken과 함께 리다이렉트 됩니다.

 

 

app.get('/login/facebook', function(req, res) {
    const stringifiedParams = queryString.stringify({
        client_id: "331044621072978",
        redirect_uri: 'http://localhost:4000/auth/callback',
        scope: ['email', 'user_friends'].join(','), // csv format
        response_type: 'code',
        auth_type: 'rerequest',
        display: 'popup',
    });

    const facebookUrl = `https://www.facebook.com/v4.0/dialog/oauth?${stringifiedParams}`;
    res.type('text/html').status(200).send(`
        <!DOCTYPE html>
        <html>
            <body>
            <a href={facebookUrl}>
                Login with Facebook
            </a>
            </body>
        </html>
    `);
});

 

 

redirect_uri는 localhost:4000/auth/callback이므로, 로그인이 끝나고 나면 facebook에서 AccessToken과 함께 위 URL 로 리다이렉트 시켜주며, 파라미터에 AccessToken 정보가 들어오게 됩니다. 이를 받아서 출력해주면 사용자 인증을 위해 사용할 AccessToken을 사용할 수 있습니다. 

 

 

이 코드는 AccessToken이 제대로 넘어오는지를 확인하기 위한 코드이기 때문에 이를 따로 세션이나 reducer에 저장하고 있지 않지만, 실제 서비스에서는 이 AccessToken을 가지고 있어야 사용자 관련 정보를 인증서버에 요청할 수 있기 때문에 이를 SessionStorage나 Reducer에 저장해 두었다가 REST API요청시에 Request Header에 실어서 보내는 방식을 사용합니다. 

 

 

async function getAccessTokenFromCode(code) {
    const { data } = await axios({
        url: 'https://graph.facebook.com/v4.0/oauth/access_token',
        method: 'get',
        params: {
            client_id: "331044621072978",
            client_secret: 's',
            redirect_uri: 'http://localhost:4000/auth/callback', 
            code,
        },
    });
    console.log(data);
    return data.access_token;
}

app.get('/auth/callback', async(req, res) => {
    const access_token = await getAccessTokenFromCode(req.query.code);
    console.log(access_token);
    res.send("authentification success");
});

 

 

 

AccessToken이 제대로 넘어온 것을 확인할 수 있습니다.

 

 

 

 

전체 코드

// app.js

const express = require('express');
const queryString = require('query-string');
const axios = require('axios');

const app = express();
async function getAccessTokenFromCode(code) {
    const { data } = await axios({
        url: 'https://graph.facebook.com/v4.0/oauth/access_token',
        method: 'get',
        params: {
            client_id: "331044621072978",
            client_secret: 's',
            redirect_uri: 'http://localhost:4000/auth/callback', 
            code,
        },
    });
    console.log(data);
    return data.access_token;
}


app.get('/login/facebook', function(req, res) {
    const stringifiedParams = queryString.stringify({
        client_id: "331044621072978",
        redirect_uri: 'http://localhost:4000/auth/callback',
        scope: ['email', 'user_friends'].join(','), // csv format
        response_type: 'code',
        auth_type: 'rerequest',
        display: 'popup',
    });

    const facebookUrl = `https://www.facebook.com/v4.0/dialog/oauth?${stringifiedParams}`;
    res.type('text/html').status(200).send(`
        <!DOCTYPE html>
        <html>
            <body>
            <a href={facebookUrl}>
                Login with Facebook
            </a>
            </body>
        </html>
    `);
});

app.get('/auth/callback', async(req, res) => {
    const access_token = await getAccessTokenFromCode(req.query.code);
    console.log(access_token);
    res.send("authentification success");
});

app.get('*', function(req, res) {
    res.send('hello world');
});

app.listen(4000, () => console.log('Now broswe to localhost:4000/graphql'));

 

 

 

 

자세한 파라미터 https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow#login

 

로그인 플로 직접 빌드 - Facebook 로그인 - 문서 - Facebook for Developers

 

developers.facebook.com

반응형

'Frontend' 카테고리의 다른 글

[HTML] iframe 태그란?  (0) 2020.08.23
[Browser] Console 객체  (0) 2020.07.26
[OAuth] OAuth 2.0  (0) 2020.07.04
JIT vs AOT 컴파일러  (1) 2020.06.16
[Crawler] Selenium으로 Everytime 크롤링하기  (1) 2020.05.10