node js OAuth 2.0 Login

Node.js 2019. 4. 16. 11:10

node js는 패스포트를 이용하면, 거의 모든 SNS 로그인을 구현할 수 있습니다. 하지만, 새로운 SNS 사이트나 OAuth 2.0 기능을 가진 사이트의 요청을 필요로 할 때를 대비해 기능을 잘 알아 둘 필요가 있습니다. 그리고 모듈을 쓸 필요도 없을 만큼 매우 간단하고 쉽습니다.

[공식 문서]

구글 OAuth 2.0 OverView : https://developers.google.com/identity/protocols/OAuth2?hl=ko .
구글 OAuth 2.0 for Web Server Applications : https://developers.google.com/identity/protocols/OAuth2WebServer?hl=ko .
유저 프로필 요청 : https://developers.google.com/gmail/api/v1/reference/users/getProfile .


[주요 사이트]

Google APIs 콘솔 : https://console.developers.google.com .

 

[참고 문서]

php를 이용한 Google OAuth2.0 Login : https://nicgoon.tistory.com/208 . (전반부 구글 클라우드 콘솔 설정 숙지.).

 

1. 준비사항.

1) 본 문서를 보기전, 구글 클라우드 API에서 클라이언트 ID를 만들어야 합니다. 해당 내용은 위 참고 문서 php를 이용한 Google OAuth2.0 Login 부분을 참고 하시면됩니다. 

2) 익스프레스 서버 설정.

익스프레스를 이용해 서버를 설정하였다면, 웹을 처리를 할 라우트를 만들어 주세요 저는 아래와 같이 라우트 주소를 설정하였습니다.

구글 로그인 라우트 기본 주소 : http://localhost/oauth2
인증코드 요청 페이지 : 
http://localhost/oauth2/request_code
토큰 교환 페이지 : 
http://localhost/oauth2/exchange_token

3) 클라이언트 아이디 관련 값들.

클라이언트 아이디 : 107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com
클라이언트 보안 비밀 : NaJSvmGdShGg1kkXkGdvHp4T
리디렉션 페이지 : http://localhost/oauth2/exchange_token

 

2. 라우트 기본 뼈대.

구글 로그인에서 사용되는 페이지는 최소 2개이고, 이 것을 저는 라우트를 하나 만들어 다음과 같은 코딩을 했습니다. request_code 는 인증코드 요청페이지, exchange_token 은 리디렉션 후 토큰 교환 입니다.

var express = require('express');
var router = express.Router();

/* GET home page. */
router.use('/:name', function(req, res, next) {
    


    // 요청한 파일명을 가지고 오는 메소드 입니다.
    let methodName = req.params.name;


    
    // 인증 코드를 요청한 경우 처리 분기 입니다.
    if( methodName == "request_code" )
    {
        request_code(req, res, next);
        return;                                     // 리턴 중요.
    }


    // 토큰 교환을 요청한 경우 처리 분기 입니다.
    else if( methodName == "exchange_token" )
    {
        exchange_token(req, res, next);
        return;                                     // 리턴 중요.
    }




    // 처리 방법을 확인하지 못한 경우. next 처리를 하도록 합니다.
    next();


});



// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{
    res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    res.end( "코드 요청하는 메소드." );
}



// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{
    res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    res.end("토큰을 교환하는 메소드.");
}





module.exports = router;

 

3. 유저 로그인 페이지 리디렉션하기.

주소를 만들때, 사용되는 옵션은 문서 처음에 있는 [구글 OAuth 2.0 for Web Server Applications] 링크를 참고 하시기 바립니다.

request_code 부분에 인증을 위한 주소를 만들어 리디렉션 하면, 사용자는 구글 로그인 창을 보게 됩니다. 코드는 아래와 같습니다.

// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{

    // 공통적으로 사용하는 상수들 입니다.
    var scope = "https://mail.google.com/";
    var client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
    var client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
    var redriect_url = "http://localhost/oauth2/exchange_token";



    // 인증을 위한 주소를 만들어 줍니다.
    let reqAddr = "https://accounts.google.com/o/oauth2/v2/auth"
    + "?client_id=" + client_id
    + "&redirect_uri=" + encodeURIComponent( redriect_url )
    + "&scope=" + encodeURIComponent( scope )
    + "&state=OK"
    + "&access_type=offline"
    + "&include_granted_scopes=true"
    + "&response_type=code"
    ;

    

    res.redirect( reqAddr );


}

로그인 하고, 권한도 모두 주면, 리디렉션 페이지가 호출 됩니다. 우리는 여기서, 토큰 교환을 시도할 것 입니다.

 

4. 토큰 교환 처리.

정식 문서를 보면, 아래와 같은 값을 보내면, 토큰을 주는 것으로 되어 있습니다. 

POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=your_client_id&
client_secret=your_client_secret&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code

우리가 받은 토큰 값등을 활용해 위의 페이지를 만들 것 입니다.

1) 리퀘스트 설치.

페이지를 요청하는 데 리퀘스트 객체를 사용할 것 입니다. 아래와 같이 리퀘스트를 설치해 주도록 합니다.

npm i request

2) 토큰 교환.

보내야 할 데이터 중 헤더 부분은 request가 알아서 채워 줍니다. (POST로 보낼 때 당연히 적어야할 부분). 그래서 데이터부분만 적어 주면됩니다. exchange_token 메소드를 다음과 같이 변경해 보도록 합니다.

// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        res.end( body );


    });



}

그리고 실행하면 정상적인 경우 다음과 같은 응답을 화면에 뿌려 줍니다. (자세히 보면 Access Token을 받아 온 것을 확인할 수 있을 것 입니다.)

5. 사용자 정보 가지고 오기.

교환된 토큰으로 아래와 같이 함수를 만들어 body와 res 값을 넣어 주면, 화면에 사용자 정보를 표시해 줍니다.

// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        // res.end( body );
        bodyobj = JSON.parse( body );
        read_profile( res, bodyobj.access_token );


    });



}


// 유저 정보를 확인하는 메소드 입니다.
function read_profile( res, token )
{

    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/gmail/v1/users/me/profile',
        headers:{
            'Authorization': 'Bearer ' + token
        },
        rejectUnauthorized:false
    };



    request.get( requestOption, function( err, httpResponse, body ){

        if( err )
        {

            res.end("사용자 정보를 확인하던 중 오류가 났습니다.<br>" + err);
            return;

        }

        // 사용자 정보를 출력합니다.
        res.end( body );

    });

}

별다은 오류가 없다면 다음과 같은 페이지를 볼 수 있을 것 입니다.

 

6. 전체 소스

익스프레서 서버에서 사용하기 위한 전체 소스 코드는 아래와 같습니다.

var express = require('express');
var router = express.Router();


var request = require('request');


// 공통적으로 사용하는 상수들 입니다.
var scope = "https://mail.google.com/";
var client_id = "107557211118-lpfm0l4gs5ffpas8cbjn5bp71f2hbag5.apps.googleusercontent.com";
var client_secret = "NaJSvmGdShGg1kkXkGdvHp4T";
var redriect_url = "http://localhost/oauth2/exchange_token";


/* GET home page. */
router.use('/:name', function(req, res, next) {
    


    // 요청한 파일명을 가지고 오는 메소드 입니다.
    let methodName = req.params.name;


    
    // 인증 코드를 요청한 경우 처리 분기 입니다.
    if( methodName == "request_code" )
    {
        request_code(req, res, next);
        return;                                     // 리턴 중요.
    }


    // 토큰 교환을 요청한 경우 처리 분기 입니다.
    else if( methodName == "exchange_token" )
    {
        exchange_token(req, res, next);
        return;                                     // 리턴 중요.
    }




    // 처리 방법을 확인하지 못한 경우. next 처리를 하도록 합니다.
    next();


});



// 인증 코드를 요청한 경우 처리 하는 메소드 입니다.
function request_code( req, res, next )
{



    // 인증을 위한 주소를 만들어 줍니다.
    let reqAddr = "https://accounts.google.com/o/oauth2/v2/auth"
    + "?client_id=" + client_id
    + "&redirect_uri=" + encodeURIComponent( redriect_url )
    + "&scope=" + encodeURIComponent( scope )
    + "&state=OK"
    + "&access_type=offline"
    + "&include_granted_scopes=true"
    + "&response_type=code"
    ;

    

    res.redirect( reqAddr );


}



// 토큰 교환 및 사용자 정보를 확인하는 경우 처리하는 메소드 입니다.
function exchange_token( req, res, next )
{


    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/oauth2/v4/token',
        method:'POST',
        form:{
            code:req.query.code,
            client_id:client_id,
            client_secret:client_secret,
            redirect_uri: redriect_url,
            grant_type: 'authorization_code'
        }
    };

    request.post( requestOption, function( err, httpResponse, body ){


        res.setHeader( "Content-Type", "text/html; charset=UTF-8" );
    

        // 오류가 난 경우 오류를 반환해 줍니다.
        if( err )
        {

            res.end("오류가 났습니다.<br>" + err);
            return;

        }

        // res.end( body );
        bodyobj = JSON.parse( body );
        read_profile( res, bodyobj.access_token );


    });



}


// 유저 정보를 확인하는 메소드 입니다.
function read_profile( res, token )
{

    // 요청할 데이터를 만들어 줍니다.
    let requestOption = {
        url:'https://www.googleapis.com/gmail/v1/users/me/profile',
        headers:{
            'Authorization': 'Bearer ' + token
        },
        rejectUnauthorized:false
    };



    request.get( requestOption, function( err, httpResponse, body ){

        if( err )
        {

            res.end("사용자 정보를 확인하던 중 오류가 났습니다.<br>" + err);
            return;

        }

        // 사용자 정보를 출력합니다.
        res.end( body );

    });

}




module.exports = router;

 

 

 

 

 

 

 

 

Posted by 창업닉군
,