Node- OAuth 2.0

참고
[1] https://developers.google.com/identity/protocols/oauth2
[2] https://blog.naver.com/mds_datasecurity/222182943542


Introduction

  • OauthClient(웹 어플리케이션)Resource Server(구글)로 부터 Resource owner(웹 어플리케이션의 유저들)의 Resource들을 사용하기위해 Authorization Server(구글)에게 Authentication(인증)과 Authorization(권한)을 받는 프로토콜이다. 즉 내가 만든 서버가 유저의 구글 로그인, 네이버 로그인, 네이버 메일 등을 사용하기 위해 구글, 네이버에게 내 서버와 유저를 인증하는 절차이다.
  • 이메일 인증을 위해 서버에서 Gmail을 이용하여 이메일을 유저들에게 보냈는데, 기존에는 Gmail의 아이디와 비밀번호만 서버에 저장하고 있고 서버에서 구글 이메일 서버에 접속해 아이디와 비밀번호를 통해 로그인하여 메일을 발송하였는데 최근에 구글에서 그 방법을 막기 시작하였다.
  • 서버에서 유저의 아이디와 비밀번호를 저장하여 구글에 유저를 대행하여 로그인하는 것이 서버에서 유저행세를 하며 구글에서 유저정보를 빼앗는 것은 보안상 좋지 않다고 볼 수 있다. 구글에서는 클라이언트 서버가 신뢰할만한 인증을 받는 것이 아니기 때문.
  • Clent 서버는 인증을 받으면 유저의 리소스를 사용할 수 있는 권리를 받는데 Access token, Refresh token을 통해 Resource Server로부터 유저의 Resource를 받아올 수 있다.
  • 핵심은 Authorization Server는 유저와 Client 모두를 인증하고 Client 서버에게 권한을 부여하는 것이다.

OAuth 2.0 protocol

  • OAuth 2.0을 다루는데 있어서 등장하는 요소들로는 Resource 소유자인 Resouce Owner(편의상 Client의 유저라고 부르겠다), 유저의 Resource를 사용할려는 Client, 유저의 Resource를 소유하고 있는 Resource server, 유저와 Client부터 인증과 권한을 부여하는 Authorization Server가 존재한다.
  • OAuth를 통해서 Client에게 Resource Server에서 유저의 Resource를 사용하도록 권한을 부여하는 여러 방법(4가지)가 존재하는데 그 중에서 가장 보편적으로 사용하는 Authorization Code Grant 방식을 알아보도록 하겠다.

Authorization Code Grant

  • Authorization Server에서 유저를 검증(Authentication)하고 어떤 Resource를 사용할 지 Scope을 정의한 후에 그 정보를 Authorization Code로 만들어 Client에게 전달하고 Client는 자신을 Authorization Server에게 받은 Code와 함께 Client을 인증하면 유저의 Resource를 사용할 수 있도록 하는 OAuth 프로토콜 중에 가장 보편적으로 사용하는 Authorization 방식이다.
  • Authorization Code는 인증된 유저 정보, Scope 정보, 아직 인증되지 않은 Client 정보가 있다. 그렇기 때문에 Client는 이 Code와 함께 Authorization Server에게 자신을 인증해야 한다. 유저 검증을 Client가 하는 것이 아닌 Authorization Server가 하고 Client에서는 유저 정보를 가지고 있을 필요가 없기 때문에 보안상 뛰어나다. 또한 유저 인증만 하였다고 Resource를 바로 쓸 수 있는 것이 아닌 Client도 검증 단계를 거쳐야만 Resource를 사용할 수 있다. 유저 정보만 검증하고 Client는 검증하지 않을 경우 Client Id는 오픈된 정보이기 떄문에 다른 누군가가 Client 행세를 하여 Code를 얻을 수 있기 때문이다.
  • Access tokenRefresh token을 사용하는 방식이다.

Authorization Code Grant 절차

  • 우선 Client를 Authorization Server에 등록하여 Client IdCleint Secret을 받아야한다. 이것은 나중에 Authorization Server에서 Client를 인증할 때 사용한다.
  • Cleint에서 어떤 특정한 일에서 유저의 Resource를 사용한다고 하면 그 특정한 일을 유저가 요청할 때, Client에서는 Client IdResource Scope 정보, 인증이 완료되면 인증 코드를 보낼 redirected Url 주소를 담아 Authorization Server에게 요청한다.
  • Authorization Server는 자체적으로 로그인 같은 방법으로 유저를 검증하고 해당 Scope에 대해서 사용 동의를 얻은 후에 유저 정보와 Scope 정보가 담긴 Authorization Code를 만들어 Client에게 전달한다.
  • 응답을 받은 Client는 Authorization CodeClient Id, Client Secret 정보를 다시 Authorization Server에게 전달한다.
  • Authorization Server는 받은 Client Id와 Client Secret을 통해 Client를 인증하고 해당 유저에 리소스에 접근할 수 있는 Access tokenRefresh token을 Client에게 발급한다.
  • Access token에는 유저 정보와 Scope 정보, Client 정보가 담겨있다.
  • Access token을 재발급 받기 위해서는 Client Id, Client Secret, Refresh token 3개가 필요하다.
  • Access token과 Refresh token을 이용하기 때문에 다음에 중복해서 인증할 필요가 없다.

OAuth 2.0을 이용한 GMail Api 관련 Token 얻기

  • Gmail을 구글에서 제공하는 서비스이기 때문에 구글에 Client를 등록해야 한다.
  • 구글 클라우드 플랫폼에 Api 및 서비스에서 Client를 등록하여 Client Id와 Secret을 받는다.
  • Gmail api 서비스에서 유저는 이메일을 보내는 발신자이다. 일반적인 OAuth 2.0을 사용하는 로그인 서비스에서는 Authorization code를 Client로 응답하여 매번 유저마다 다른 Authorization Code를 받아 인증해야하는 것과는 달리, 로그인 서비스에서는 유저의 이메일을 등록하여 등록된 이메일을 사용해서 전송하면 되기 때문에 웹 브라우저를 통해 사용할 이메일을 한번만 인증하면 된다. 구글에서 제공하는 oauthplayground를 사용한다.
  • oauthplayground는 브라우저에서 oauth관련 일을 처리할 수 있는 Authorization Server라 생각하면 쉽다.
  • oauthplayground에서 Client Id와 gmail scope을 등록하고, 구글 로그인을 통해 유저 인증을 하면 Authorization Code를 얻는다.
  • 받은 Authorization Code와 Clien Id, Client Secret을 입력하면 Access token과 Refresh token을 얻을 수 있다.

Access token과 Node mailer 패키지를 이용하여 이메일 전송하기

  • Node mailer을 이용하여 이메일 보내기에서 설명한 것처럼 mailer 패키지를 이용하여 메일을 전송하기 위해서는 Transporter 객체를 이용한다.
  • access token은 등록하지 않아도 된다. nodemailer가 자체적으로 refresh token, Client Id, Client Secret을 이용해서 access token을 발급하기 때문이다.
    let transporter = nodemailer.createTransport({
    host: "smtp.gmail.com",
    port: 465,
    secure: true,
    auth: {
      type: "OAuth2",
      user: "user@example.com",
      clientId: "000000000000-xxx0.apps.googleusercontent.com",
      clientSecret: "XxxxxXXxX0xxxxxxxx0XXxX0",
      refreshToken: "1/XXxXxsss-xxxXXXXXxXxx0XXXxxXXx0x00xxx",
    },
    });