Jieunny์˜ ๋ธ”๋กœ๊ทธ

S3) Unit 7. [์‹ค์Šต] OAuth(๊นƒํ—ˆ๋ธŒ) ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ ๋ณธ๋ฌธ

CodeStates/Training

S3) Unit 7. [์‹ค์Šต] OAuth(๊นƒํ—ˆ๋ธŒ) ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

Jieunny 2023. 3. 9. 14:28

๐Ÿ“ฃ  ๊นƒํ—ˆ๋ธŒ ๋กœ๊ทธ์ธ ์ธ์ฆ ์‚ฌ์šฉํ•ด์„œ ๋กœ๊ทธ์ธ ๊ธฐ๋Šฅ ๊ตฌํ˜„ํ•˜๊ธฐ

- client๋งŒ ๊ตฌํ˜„

๐Ÿญ. client ํŒŒ์ผ ๊ตฌ์กฐ

src

    ใ„ด pages

        ใ„ด components

            ใ„ด Loading.js : ๋กœ๋”ฉ ํ™”๋ฉด ์ปดํฌ๋„ŒํŠธ

            ใ„ด UserInfo.js : ๋กœ๊ทธ์ธ ์„ฑ๊ณต ํ•˜๋ฉด ๋‚˜ํƒ€๋‚˜๋Š” ์œ ์ € ์ •๋ณด ์ปดํฌ๋„ŒํŠธ

 

        ใ„ด Login.js : Authorization code ๋ฐ›์•„์˜ค๊ธฐ

            - Github์— ์š”์ฒญ์„ ๋ณด๋‚ด์„œ Authorization code๋ฅผ ๋ฐ›์•„์˜จ๋‹ค.

 

        ใ„ด MyPage.js : ๋กœ์ปฌ ์„œ๋ฒ„๋ฅผ ํ†ตํ•ด Github ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์— ์œ ์ € ์ •๋ณด ์š”์ฒญ, ๋กœ๊ทธ์•„์›ƒ

            -  Authorization code๋ฅผ ๋ฐ›์•„์™”๋‹ค๋ฉด App.js์˜ getAccessToken ํ•จ์ˆ˜๊ฐ€ ์‹คํ–‰๋œ๋‹ค.

            -  ์ด ํ•จ์ˆ˜๋ฅผ ํ†ตํ•ด ์„œ๋ฒ„์˜ /callback ์—”๋“œํฌ์ธํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ , ์‘๋‹ต์œผ๋กœ ๋ฐ›์•„์˜จ Access Token์€ App ์ปดํฌ๋„ŒํŠธ์— state๋กœ ์ €์žฅํ•˜๊ณ , MyPage ์ปดํฌ๋„ŒํŠธ์—์„œ props ๋ฐ›์•„์„œ ์‚ฌ์šฉํ•œ๋‹ค.

            - ๋ฐ›์•„์˜จ Access Token์„ ์ด์šฉํ•ด์„œ /userInfo๋กœ ์š”์ฒญํ•˜๋ฉด serverResource์™€ Github Resource ์„œ๋ฒ„์— ์š”์ฒญํ•œ ๋ฆฌ์†Œ์Šค์ธ githubUserData๊ฐ€ ์‘๋‹ต์œผ๋กœ ์ „๋‹ฌ๋œ๋‹ค.

            - ๊นƒํ—ˆ๋ธŒ ์œ ์ € ์ •๋ณด์™€ ๋กœ์ปฌ ์„œ๋ฒ„์— ์žˆ๋Š” ์ •๋ณด๊ฐ€ ๋งˆ์ดํŽ˜์ด์ง€ ํ™”๋ฉด์— ๋‹ด๊ฒจ์•ผ ํ•œ๋‹ค.

            - ๋กœ๊ทธ์•„์›ƒ ๊ตฌํ˜„ : ๋กœ์ปฌ ์„œ๋ฒ„์˜ /logout์— ์•ก์„ธ์Šค ํ† ํฐ์„ ์ด์šฉํ•ด ์š”์ฒญ์„ ๋ณด๋‚ด์„œ ์œ ์ €์˜ ํ† ํฐ์„ ์ง€์šฐ๊ณ , ์ƒํƒœ๋ฅผ ์—…๋ฐ์ดํŠธ ํ•œ๋‹ค.

 

    ใ„ด App.js : Access Token ๋ฐ›์•„์˜ค๊ธฐ

 

์„œ๋ฒ„์˜ .env : CLIENT_ID์™€ CLIENT_SECRET ์„ค์ •

โœ”๏ธ ๊นƒํ—ˆ๋ธŒ์— ๋‚ด๊ฐ€ ๊ตฌํ˜„ํ•  OAuth ์•ฑ์„ ๋“ฑ๋กํ•˜๋Š” ๊ณผ์ •์ด ๋จผ์ € ์‹คํ–‰๋˜์–ด์•ผ ํ•œ๋‹ค.

โžฐ Homepage URL ๋ฐ Authorization callback URL์€ ํ•ด๋‹น ๊ณผ์ œ์˜ ํด๋ผ์ด์–ธํŠธ ์ฃผ์†Œ(http://localhost:3000)๋กœ ๋ฆฌ๋””๋ ‰์…˜ ํ•œ๋‹ค.

โžฐ ์ด ๊ณผ์ •์„ ํ†ตํ•ด CLIENT_ID์™€ CLIENT_SECRET์„ ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.


๐Ÿฎ. ์ฝ”๋“œ ๊ตฌํ˜„

๐Ÿ“Œ Login.js

export default function Login() {
  const CLIENT_ID = '๋‚ด CLIENT_ID';
  // ๋ฐ›์•„์˜จ ํด๋ผ์ด์–ธํŠธ ID๋ฅผ ๊ฐ€์ ธ์™€์„œ github ํŽ˜์ด์ง€๋กœ ๋“ค์–ด๊ฐ€๊ฒŒ ํ•ด์ค€๋‹ค.

  const loginRequestHandler = () => {
    // TODO: GitHub๋กœ๋ถ€ํ„ฐ ์‚ฌ์šฉ์ž ์ธ์ฆ์„ ์œ„ํ•ด GitHub๋กœ ์ด๋™ํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค. ์ ์ ˆํ•œ URL์„ ์ž…๋ ฅํ•˜์„ธ์š”.
    // OAuth ์ธ์ฆ์ด ์™„๋ฃŒ๋˜๋ฉด authorization code์™€ ํ•จ๊ป˜ callback url๋กœ ๋ฆฌ๋””๋ ‰์…˜ ํ•ฉ๋‹ˆ๋‹ค.
    // ์ฐธ๊ณ : https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps

    return window.location.assign(
      `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}`
    );
    
  };
}

โžฐ CLIENT_ID ๊ฐ€์ ธ์˜ฌ ๋•Œ process.env.CLIENT_ID๋กœ ํ–ˆ๋Š”๋ฐ undefined๊ฐ€ ๋‚˜์™€์„œ ๊ทธ๋ƒฅ ํ•˜๋“œ์ฝ”๋”ฉ์œผ๋กœ ๋„ฃ์–ด์คฌ๋‹ค.. ์ด๋ ‡๊ฒŒ ํ•˜๋ฉด ์•ˆ๋˜๋Š” ๊ฒƒ ๊ฐ™๊ธดํ•œ๋ฐ ์ด๋”ฐ ์‹ค์‹œ๊ฐ„ ์‹œ๊ฐ„์— ์„ค๋ช…ํ•ด์ฃผ์‹œ๊ฒ ์ง€.. -> ๊ทธ๋ƒฅ ๋„ฃ์–ด๋„ ์ƒ๊ด€ ์—†๋‹ค๊ณ  ํ•จ

โžฐ ์™œ process.env.CLIENT_ID๊ฐ€ ์•ˆ๋˜๋ƒ? -> ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์ œ๋Œ€๋กœ ๊ฐ€์ ธ์˜ค์ง€ ๋ชปํ•ด์„œ -> ๋ฆฌ์•กํŠธ์—์„œ ํ™˜๊ฒฝ ๋ณ€์ˆ˜๋ฅผ ์“ฐ๋ ค๋ฉด .env ํŒŒ์ผ ์„ค์ •ํ•  ๋•Œ REACT_APP_CLIENT_ID ๋กœ ์จ์•ผํ•จ(๋ณ€์ˆ˜ ์•ž์— REACT_APP_ ์ถ”๊ฐ€ํ•ด์ค˜์•ผํ•œ๋‹ค!!)

 

๐Ÿ“Œ App.js

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

  const getAccessToken = async (authorizationCode) => {
    // ๋ฐ›์•„์˜จ Authorization Code๋กœ ๋‹ค์‹œ OAuth App์— ์š”์ฒญํ•ด์„œ Access Token์„ ๋ฐ›์„ ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    // Access Token์€ ๋ณด์•ˆ ์œ ์ง€๊ฐ€ ํ•„์š”ํ•˜๊ธฐ ๋•Œ๋ฌธ์— ํด๋ผ์ด์–ธํŠธ์—์„œ ์ง์ ‘ OAuth App์— ์š”์ฒญ์„ ํ•˜๋Š” ๋ฐฉ๋ฒ•์€ ๋ณด์•ˆ์— ์ทจ์•ฝํ•  ์ˆ˜ ์žˆ์Šต๋‹ˆ๋‹ค.
    // Authorization Code๋ฅผ ์„œ๋ฒ„๋กœ ๋ณด๋‚ด์ฃผ๊ณ  ์„œ๋ฒ„์—์„œ Access Token ์š”์ฒญ์„ ํ•˜๋Š” ๊ฒƒ์ด ์ ์ ˆํ•ฉ๋‹ˆ๋‹ค.
    // TODO: ์„œ๋ฒ„์˜ /callback ์—”๋“œํฌ์ธํŠธ๋กœ Authorization Code๋ฅผ ๋ณด๋‚ด์ฃผ๊ณ  Access Token์„ ๋ฐ›์•„์˜ต๋‹ˆ๋‹ค.
    // Access Token์„ ๋ฐ›์•„์˜จ ํ›„ state์— Access Token์„ ์ €์žฅํ•˜์„ธ์š”
    const res = await axios.post("http://localhost:4000/callback", {
    // /callback์œผ๋กœ authorizationCode๋ฅผ ๋ณด๋‚ด์ฃผ๊ณ , ์‘๋‹ต์œผ๋กœ Access Token์„ ๋ฐ›์•„์˜จ๋‹ค.
      authorizationCode: authorizationCode,
    });
    setAccessToken(res.data.accessToken);
    // ๋ฐ›์•„์˜จ ์•ก์„ธ์Šค ํ† ํฐ์„ state์— ๋„ฃ์–ด์ฃผ๊ณ 
    setIsLogin(true);
    // ๋กœ๊ทธ์ธ์ด ์„ฑ๊ณต์ ์œผ๋กœ ์™„๋ฃŒ๋˜์—ˆ์œผ๋‹ˆ ์ƒํƒœ๋„ ๋ฐ”๊ฟ”์ค€๋‹ค.
  };
  useEffect(() => {
    // Authorization Server๋กœ๋ถ€ํ„ฐ ํด๋ผ์ด์–ธํŠธ๋กœ ๋ฆฌ๋””๋ ‰์…˜๋œ ๊ฒฝ์šฐ, Authorization Code๊ฐ€ ํ•จ๊ป˜ ์ „๋‹ฌ๋ฉ๋‹ˆ๋‹ค.
    // ex) http://localhost:3000/mypage?code=5e52fb85d6a1ed46a51f
    const url = new URL(window.location.href);
    const authorizationCode = url.searchParams.get('code');
    if (authorizationCode) {
      getAccessToken(authorizationCode);
    }
  }, []);
}

 

๐Ÿ“Œ Mypage.js

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

  const logoutHandler = () => {
    // TODO: /logout์„ ํ†ตํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋กœ๊ทธ์•„์›ƒ๋˜๋„๋ก ๊ตฌํ˜„ํ•˜์„ธ์š”.
    // prop์œผ๋กœ ๋ฐ›์€ Access Token์„ ์ด์šฉํ•ด /logout ์—”๋“œํฌ์ธํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    // ์š”์ฒญ์ด ์„ฑ๊ณตํ–ˆ๋‹ค๋ฉด isLogin ์ƒํƒœ๋ฅผ false๋กœ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    axios
      .delete("http://localhost:4000/logout", { data: {accessToken} })
      // ๋กœ๊ทธ์•„์›ƒ์€ ์„œ๋ฒ„์—์„œ delete ๋ฉ”์„œ๋“œ๋กœ ๋ฐ›๊ณ  ์žˆ์œผ๋ฏ€๋กœ delete๋กœ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
      .then((res) => {
        setIsLogin(false);
        setAccessToken("");
        setGithubUser(null);
        setServerResource(null);
        // ๋กœ๊ทธ์•„์›ƒ์ด ์„ฑ๊ณตํ–ˆ์œผ๋ฏ€๋กœ ๋กœ๊ทธ์ธ ์ƒํƒœ๋ฅผ ๋ฐ”๊ฟ”์ฃผ๊ณ , ๋ชจ๋‘ ์ดˆ๊ธฐํ™” ์‹œ์ผœ์ค€๋‹ค.
      })
      .catch((err) => console.log(err));
  };

  useEffect(() => {
    // TODO: /userinfo๋ฅผ ํ†ตํ•ด ์‚ฌ์šฉ์ž ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค์„ธ์š”.
    // prop์œผ๋กœ ๋ฐ›์€ Access Token์„ ์ด์šฉํ•ด /userinfo ์—”๋“œํฌ์ธํŠธ๋กœ ์š”์ฒญ์„ ๋ณด๋‚ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    // ์‘๋‹ต์œผ๋กœ ๋ฐ›์€ ๋ฐ์ดํ„ฐ๋ฅผ githubUser, serverResource์˜ ์ƒํƒœ๋กœ ์—…๋ฐ์ดํŠธํ•ด์•ผํ•ฉ๋‹ˆ๋‹ค.
    // isLoading ์ƒํƒœ๋ฅผ false๋กœ ์—…๋ฐ์ดํŠธํ•ด์•ผ ํ•ฉ๋‹ˆ๋‹ค.
    axios
      .post("http://localhost:4000/userInfo", { accessToken })
      // ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๊ธฐ ์œ„ํ•ด์„œ userInfo๋กœ ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
      .then((res) => {
      // ์„ฑ๊ณต์ ์œผ๋กœ ์œ ์ € ์ •๋ณด๋ฅผ ๋ฐ›์•„์˜ค๋ฉด
        const { githubUserData, serverResource } = res.data;
        // ๋ฐ›์•„์˜จ ๋ฐ์ดํ„ฐ์—์„œ ํ•„์š”ํ•œ ์ •๋ณด๋งŒ ๋ฝ‘์•„์„œ ์ €์žฅํ•œ๋‹ค.
        setGithubUser(githubUserData);
        // state์— ๊ฐ๊ฐ ์ €์žฅํ•ด์ฃผ๊ณ 
        setServerResource(serverResource);
        setIsLoading(false);
        // ๋กœ๋”ฉ์„ ๋๋‚ด๊ณ  ์œ ์ € ์ •๋ณด๋ฅผ ํ™”๋ฉด์— ๋„์›Œ์ค€๋‹ค.
      })
      .catch((err) => console.log(err));
  }, []);
}

๐Ÿฏ. ์‹œ์—ฐํ™”๋ฉด