본문 바로가기

programming/Web

구글 로그인 라이브러리 변경 - 4. 라이브러리를 사용하지 않고 google OAuth 로 direct 요청 방식 구현

정리할 내용

  • Google Identity 간단 개념 (+ oauth) 👉 링크
  • 구글 라이브러리에서 변경되는 내용 👉 링크
  • authorization 에서 flow  선택 (gsi/client 라이브러리 사용)
  • 라이브러리를 사용하지 않고 google oauth api로 direct 요청하기
  • 토이플젝을 통해서 code → token 얻어내는 과정 및 유의사항
  • 기존 로그인 플로우 & 변경되는 로그인 플로우 설명

 

Direct Using Google OAuth 2.0

  • 다시 한 번 목적을 생각해보자
  • 나는 access_token 과 refresh_token을 사용하여 “로그인”을 이용할 것이다
    • refresh_token을 따로 관리해서 자동로그인 시켜주고 로그인이 거의 영원히 풀리지 않도록 하기 위해서
  • 그리고 지금 라이브러리가 deprecated 되어서 새로운 라이브러리로 대체를 시켜줘야하는 상황
  • 한가지 더 옵션이 있다
  • 신규 라이브러리도 사용하지 않고 “Using OAuth 2.0” → google oauth api 로 direct 로 요청하는 방식 (링크)

 

  • 로그인 flow using google oauth 2.0

 

 

 

Step 1: Configure the client object 

  • If you are directly accessing the OAuth 2.0 endpoints, you can proceed to the next step.
  • 라이브러리를 사용하는경우 client 객체를 init 해주어야 하지만, 다이렉트로 OAuth 2.0 엔드포인트에 접근하는 경우는 필요없다

Step 2: Redirect to Google's OAuth 2.0 server

  • To request permission to access a user's data, redirect the user to Google's OAuth 2.0 server.
  • 이제 직접 authorization(권한 부여) 를 요청한다
  • 핵심
    • oauth2Endpoint = "https://accounts.google.com/o/oauth2/v2/auth";
    •  파라미터 값
      • redirect_uri: "http://localhost:5000/direct-callback"  -> google oauth 서버로 부터 code를 받을 곳 지정
      • response_type: "code" -> token으로 요청하면 바로 access_token 을 준다
      • scope: "email profile" -> 사용할 google api 범위, 이때 지정한 범위 안에서만 access_token으로 요청가능
      • access_type: "offline" -> 이 값을 반드시 지정해야 authorization_code를 token으로 교환할 때 access_token과 함께 refresh_token 도 얻을 수 있다
      • prompt: "consent" -> 이 값을 반드시 지정해야 authorization_code를 token으로 교환할 때 access_token과 함께 refresh_token 도 얻을 수 있다
  • OAuth 에 GET 요청을 하기 위한 코드 구현방법은 두가지가 있다 (더 있을 수도) 둘 중에 마음에 드는것으로 선택하면 된다

 

[Option 1]

Oauth 문서에는 프론트에서 oauth 엔드포인트로 요청을 보낼 때 CORS를 허용하지 않아서 js로 동적으로 form을 만들어서 요청해야한다고, JS Sample을 아래와 같이 제공하고 있다

 

  • direct.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Using Google OAuth 2.0</h1>
    <button onclick="oauthSignIn()">코드 요청</button>
    <script>
      /*
       * Create form to request access token from Google's OAuth 2.0 server.
       */
      function oauthSignIn() {
        var oauth2Endpoint = "https://accounts.google.com/o/oauth2/v2/auth";

        // Create <form> element to submit parameters to OAuth 2.0 endpoint.
        var form = document.createElement("form");
        form.setAttribute("method", "GET"); // Send as a GET request.
        form.setAttribute("action", oauth2Endpoint);

        // Parameters to pass to OAuth 2.0 endpoint.
        var params = {
          client_id:
            "YOUR_CLIENT_ID",
          redirect_uri: "http://localhost:5000/direct-callback",
          response_type: "code",
          scope: "email profile",
          access_type: "offline",
          prompt: "consent",
        };

        // Add form parameters as hidden input values.
        for (var p in params) {
          var input = document.createElement("input");
          input.setAttribute("type", "hidden");
          input.setAttribute("name", p);
          input.setAttribute("value", params[p]);
          form.appendChild(input);
        }

        // Add form to page and submit it to open the OAuth 2.0 endpoint.
        document.body.appendChild(form);
        form.submit();
      }
    </script>
  </body>
</html>

 

[Option 2]

window.location.href 를 통해서 url을 변경시켜주는 것또한 방법이다

<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <h1>Using Google OAuth 2.0</h1>
    <button onclick="goToGoogleOAuth()">코드 요청</button>
    <script>
      function goToGoogleOAuth() {
        window.location.href = makeGoogleOauthUrl();
      }

      function makeGoogleOauthUrl() {
        var oauth2Endpoint = "https://accounts.google.com/o/oauth2/v2/auth";

        var params =
          "client_id=" +
          encodeURI(
            "YOUR_CLIENT_ID"
          ) +
          "&redirect_uri=" +
          encodeURI("http://localhost:5000/direct-callback") +
          "&response_type=" +
          encodeURI("code") +
          "&scope=" +
          encodeURI("email profile") +
          "&access_type=" +
          encodeURI("offline") +
          "&prompt=" +
          encodeURI("consent");

        return oauth2Endpoint + "?" + params;
      }

    </script>
  </body>
</html>

 

 

Step 3: Google prompts user for consent (생략) 👉 (링크)

 

 

Step 4: Handle the OAuth 2.0 server response

  • 요청을 보내고 나면 redirect_uri: /direct-callback 으로 code 값을 받는다
  • direct-callback.html 에서는 받은 param으로 받은 authorization_code를 localStorage에 저장하고, 다시 루트 (/direct) 로 페이지를 이동시킨다.

 

  • direct-callback.html
<!DOCTYPE html>
<html lang="en">
  <head>
    <meta charset="UTF-8" />
    <meta http-equiv="X-UA-Compatible" content="IE=edge" />
    <meta name="viewport" content="width=device-width, initial-scale=1.0" />
    <title>Document</title>
  </head>
  <body>
    <script>
      const urlParams = new URL(location.href).searchParams;
      const code = urlParams.get("code");
      window.localStorage.setItem("authCode", code);
      window.location.href = "http://localhost:5000/direct";
    </script>
  </body>
</html>

 

 

  • 다시 /direcct로 이동했을 때 로컬스토리지의 authCode 값을 확인하여, 백엔드 서버로 authCode를 보내주어야 한다.
  • direct.html 안에 authCode 로컬 스토리지를 체크하는 코드가 필요하다. 아래 코드를 추가한다.
  • 페이지 로딩시 바로 로컬 스토리지의 authCode를 체크하고, 값을 읽어서 백엔드 /locate API로 authCode를 전송한다
checkLocalStorage();
function checkLocalStorage() {
  const authCode = window.localStorage.getItem("authCode");
  if (authCode !== null) {
    // do server request
    fetch("<http://localhost:5000/locate>", {
      method: "POST",
      body: JSON.stringify({ code: authCode }),
      headers: new Headers({
        "Content-Type": "application/json",
      }),
    })
      .then((res) => res.json())
      .then((data) => {
        console.log(data);
        window.localStorage.setItem(
          "login_token",
          data.login_token
        );
        console.log("do login with login_token");
      });
  }
}

 

Step 5: Exchange for access_token with authorization_code

  • 백엔드 코드 (app.js)
// direct & authorization code flow : get tokens with authorization_code
app.post("/locate", async function (req, res) {
  const options = {
    uri: "https://oauth2.googleapis.com/token",
    method: "POST",
    qs: {
      client_id:
        "YOUR_CLIENT_ID",
      client_secret: "YOUR_CLIENT_SECRET",
      code: req.body.code,
      grant_type: "authorization_code",
      redirect_uri: "http://localhost:5000/direct-callback",
    },
    headers: {
      "Content-Type": "application/x-www-form-urlencoded",
    },
  };
  let token;
  request.post(options, function (error, response, body) {
    token = JSON.parse(body);
    console.log('token', token)
  });
  res.status(200).json({ login_token: "hello" });
});
  • 프론트로부터 받은 authorization_code를 가지고 oauth2.google 서버에 요청한다
  • redirect_uri 는 최초 요청했을 때 redirect_url 값과 동일하게 적어야한다
  • 받은 token (access_token, refresh_token, id_token)을 통해 유저의 가입유무를 검증하거나, 별도 처리를 하고, 자체적으로 login_token을 만들어 프론트에 응답한다
  • response