OAuth

MODAC·2023년 1월 9일
0

몇년전만 하더라도 특정 웹 앱의 서비스를 이용하기 위해선 해당 웹 앱에 회원가입을 하는 것이 우선이었습니다. 하지만 소셜 로그인이 보편화된 현재는 대부분의 사람들이 네이버 또는 카카오에 이미 가입된 계정을 이용해 빠르게 서비스에 가입하는 것을 택하고 있습니다.

What OAuth?

오오쓰는 직접 서버에서 인증과 관련된 로직을 처리할 필요없이 인증을 중개하는 외부 서버를 이용한 기술입니다. 보안된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 프로토콜입니다.
웹이나 앱에서 흔히 찾아볼 수 있는 소셜 로그인 인증 방식은 OAuth 2.0라는 기술을 바탕으로 구현됩니다.

OAuth의 장점

  • 각 서비스 별로 ID와 PASSWORD를 분산 관리할 필요가 없이 통일된 서비스의 인증만으로 로그인이 가능함
  • 검증되지 않은 앱에서 소셜 로그인을 지원한다면 개인 정보를 노출하지 않을 수 있고 인증 권한에 대한 허가를 사용자에게 구함으로 더 안전한 사용이 가능함

OAuth에서 알아야 할 용어

  • Resorce Owner
    사용자 및 정보 제공자

  • Client
    리소스 오너를 대신하여 보호된 리소스에 엑세스하는 어플리케이션

  • Local Server
    클라이언트의 요청을 수락하고 응답할 수 있는 서버

  • Resorce Server
    사용자의 정보를 저장하고 있는 서버

  • Authorization Server
    Access Token을 발급하며 인증을 담당하는 서버

  • Authorization Grant
    클라이언트가 Access Token을 발급받는 방법
    Authorization Code Grant Type | Refresh Token Grant Type

  • Authorization Code
    Access Token을 발급받기 위한 Code

  • Access Token
    보호된 리소스에 액세스하는 데 사용되는 인증 토큰, Resource Server에 접근 가능

  • Refresh Token
    발급받은 Access Token이 만료될 때 갱신하는 토큰

OAuth 인증 흐름

Authorization Code Grant Type

Authorization Code를 받아 Authorization Code를 통해 Access Token을 받는 방식을 말합니다. 이 유형은 Access Token이 사용자나 브라우저에 표시되지 않는다는 것을 의미하므로 Access Token이 다른 사람에게 누출될 위험이 줄어듭니다.

Refresh Token Grant Type

Access Token을 발급받은 후 Access Token이 만료된 경우 Refresh Token을 활용해 새로운 Access Token으로 교환하는 데 사용됩니다. 이를 통해 사용자와의 추가 상호 작용 없이 계속 유효한 액세스 토큰을 가질 수 있습니다.

실습

Subject: Oauth 2.0을 이용한 소셜 로그인을 구현

사용자 인증은 개발자에게 친숙한 Github을 이용합니다. 이를 통해 외부 인증 서버(Github)에게 Access Token을 받아오고 다시 유저의 인증 정보를 요청하는 플로우를 구현합니다.

Goal

  • 직접 OAuth로 로그인 가능한 애플리케이션을 제작할 수 있다.
  • Oauth의 작동 방식을 이해할 수 있다.
  • "Client" - "Local Server" - "Authorization Server" - “Resource Server” 간 요청/응답을 주고받는 다이어그램을 그릴 수 있다.
  • OAuth의 키워드를 설명할 수 있다.
  • Authorization Code와 Access Token의 차이에 대해 이해할 수 있다.
  • Authorization 서버와 Resource 서버의 차이에 대해 이해할 수 있다.

파일 구조

1. Server

- 깃허브에 나의 앱을 등록했다면 .env 파일에 아이디와 시크릿 정보를 입력 및 저장

2. Client

- Client는 리엑트로 만들어진 클라이언트 앱 폴더이기 때문에 .env 파일에 REACT_APP_을 붙여서 사용

REACT_APP_CLIENT_ID= 유저 아이디 정보

- App.js

import "./App.css";
import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./pages/Login";
import Mypage from "./pages/Mypage";
import { useEffect, useState } from "react";
import axios from "axios";

function App() {
  const [isLogin, setIsLogin] = useState(false);
  const [accessToken, setAccessToken] = useState("");

  const getAccessToken = async (authorizationCode) => {
    const result = await axios.post("https://localhost:4000/callback", {
      authorizationCode,
    });
    const { accessToken } = result.data;
    try {
      setIsLogin(true);
      setAccessToken(accessToken);
    } catch (err) {
      alert(err);
    }
  };
  useEffect(() => {
    const url = new URL(window.location.href);
    const authorizationCode = url.searchParams.get("code");
    if (authorizationCode) {
      getAccessToken(authorizationCode);
    }
  }, []);
  return (
    <BrowserRouter>
      <div className="main">
        <div className="container">
          <Routes>
            <Route
              path="/"
              element={
                isLogin ? (
                  <Mypage accessToken={accessToken} setIsLogin={setIsLogin} />
                ) : (
                  <Login />
                )
              }
            />
          </Routes>
        </div>
      </div>
    </BrowserRouter>
  );
}

export default App;

- Login.js

const CLIENT_ID = process.env.REACT_APP_CLIENT_ID;

- Mypage.js

import axios from "axios";
import React, { useEffect, useState } from "react";
import Loading from "./components/Loading";
import User from "./components/UserInfo";

export default function Mypage({ accessToken, setIsLogin }) {
  const [githubUser, setGithubUser] = useState(null);
  const [serverResource, setServerResource] = useState(null);
  const [isLoading, setIsLoading] = useState(true);

  const logoutHandler = () => {
    // TODO: /logout을 통해 사용자가 로그아웃되도록 구현하세요.
    axios
      .delete("https://localhost:4000/logout", {
        data: { accessToken },
      })
      .then((res) => setIsLogin(false))
      .catch((e) => alert(e.response.stateusText));
  };

  useEffect(() => {
    // TODO: /userinfo를 통해 사용자 정보를 받아오세요.
    axios
      .post("https://localhost:4000/userinfo", { accessToken })
      .then((res) => {
        const { githubUserData, serverResource } = res.data;
        setGithubUser(githubUserData);
        setServerResource(serverResource);
        setIsLoading((state) => !state);
      })
      .catch((e) => alert(e.response.stateusText));
  }, [accessToken]);

  return (
    <>
      <div className="left-box">
        {!isLoading && (
          <span>
            {`${githubUser.login}`},
            <p>반갑습니다!</p>
          </span>
        )}
      </div>
      <div className="right-box">
        <div className="input-field">
          {isLoading ? (
            <Loading />
          ) : (
            <User
              githubUser={githubUser}
              serverResource={serverResource}
              logoutHandler={logoutHandler}
            />
          )}
        </div>
      </div>
    </>
  );
}

0개의 댓글

관련 채용 정보