๐ŸŽ‰ ๊นƒํ—ˆ๋ธŒ OAuth 2.0 ์ด์‹ํ•˜๊ธฐ

hee.moonยท2022๋…„ 12์›” 28์ผ
0

๋…ธ๋“œJS๋กœ ๊ฐ„๋‹จํ•œ ์„œ๋ฒ„๋ฅผ ๋งŒ๋“ค๊ณ  ๊ฑฐ๊ธฐ์— OAuth ๊ธฐ๋Šฅ์„ ๋ถ™์˜€๋‹ค.


OAuth๋Š” ๊นƒํ—ˆ๋ธŒ, ๊ตฌ๊ธ€, ํŽ˜์ด์Šค๋ถ ๋“ฑ์—์„œ ๋ฐœ๊ธ‰ํ•˜๋Š” Access Token์„ ๋งค๊ฐœ๋กœ ๋‚ด ์„œ๋น„์Šค๊ฐ€ ๊นƒํ—ˆ๋ธŒ ๋“ฑ์— ์ ‘๊ทผํ•ด์„œ CRUD๋ฅผ ํ•  ์ˆ˜ ์žˆ๋Š” ๊ธฐ๋Šฅ์ด๋‹ค.

Access Token์„ ๋งค๊ฐœ๋กœ ์‚ฌ์šฉํ•˜๋Š” ์ด์œ ๋Š” 1) ์‚ฌ์šฉ์ž์˜ ์•„์ด๋””์™€ ๋น„๋ฐ€๋ฒˆํ˜ธ ๋ฐ์ดํ„ฐ๋ฅผ ์‚ฌ์šฉํ•˜๋Š” ๊ฒƒ๋ณด๋‹ค ์•ˆ์ „ํ•˜๊ณ , 2) ํ•„์š”ํ•œ ์ •๋ณด์—๋งŒ ์ ‘๊ทผํ•  ์ˆ˜ ์žˆ๊ธฐ ๋•Œ๋ฌธ์ด๋‹ค.


1. ์—ญํ•  ๊ตฌ๋ถ„ํ•˜๊ธฐ


โœ‹ ๋‚ด ์„œ๋น„์Šค(= Client)

  • OAuth ๊ณต์‹์šฉ์–ด๋กœ๋Š” Client๋ผ๊ณ  ์ง€์นญํ•œ๋‹ค.
  • OAuth๋ฅผ ํ†ตํ•ด ๊ตฌ๊ธ€ ๋“ฑ์œผ๋กœ๋ถ€ํ„ฐ Access Token์„ ๋ฐœ๊ธ‰๋ฐ›์„ ์ˆ˜ ์žˆ๋‹ค.

๐ŸŽ… ์‚ฌ์šฉ์ž(= Resource Owner)

  • OAuth ๊ณต์‹์šฉ์–ด๋กœ๋Š” Resource Owner๋ผ๊ณ  ์ง€์นญํ•œ๋‹ค.

๐ŸŒˆ ๊นƒํ—ˆ๋ธŒ, ๊ตฌ๊ธ€ ๋“ฑ(= Resource Server)

  • OAuth ๊ณต์‹์šฉ์–ด๋กœ๋Š” Resource Server๋ผ๊ณ  ์ง€์นญํ•œ๋‹ค.
  • ๋ฐ์ดํ„ฐ๋ฅผ ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ์„œ๋ฒ„๋‹ค.
  • Access Token์„ ๋ฐœ๊ธ‰ํ•œ๋‹ค.

๐Ÿ” Authorization Server

  • ์ธ์ฆ ์ฒ˜๋ฆฌ๋ฅผ ์ „๋‹ดํ•˜๋Š” ์„œ๋ฒ„๋‹ค.
  • OAuth ๊ณต์‹๋ฌธ์„œ์—์„œ Resource Server์™€ ๋ณ„๊ฐœ์˜ ๊ฐœ๋…์œผ๋กœ ์„ค๋ช…ํ•˜๊ณ  ์žˆ๋‹ค. ์–ด์จŒ๋“  ๋‘˜๋‹ค ๊นƒํ—ˆ๋ธŒ ์„œ๋ฒ„๋‹ค.

2. ๋“ฑ๋ก(Register)


โœ‹Client๊ฐ€ ๐ŸŒˆResource Server๋ฅผ ์ด์šฉํ•˜๊ธฐ ์œ„ํ•ด์„œ๋Š” ์‚ฌ์ „์— ์Šน์ธ์„ ๋ฐ›์•„์•ผ ํ•œ๋‹ค. ์ด๊ฒƒ์„ ๋“ฑ๋ก์ด๋ผ๊ณ  ํ•œ๋‹ค.
์„œ๋น„์Šค(๊นƒํ—™, ๊ตฌ๊ธ€, ...)๋งˆ๋‹ค ๋“ฑ๋ก๋ฐฉ๋ฒ•์ด ๋‹ค๋ฅด๋‹ค. ํ•˜์ง€๋งŒ ๊ณตํ†ต์ ์€ ์•„๋ž˜ 3๊ฐ€์ง€ ๋ฐ์ดํ„ฐ๋ฅผ ๋ฐ›๋Š”๋‹ค๋Š” ์ ์ด๋‹ค.

  • Client ID: โœ‹Client๋ฅผ ์‹๋ณ„ํ•˜๋Š” ์‹๋ณ„์ž
  • Client Secret: Client ID์— ๋Œ€ํ•œ ๋น„๋ฐ€๋ฒˆํ˜ธ
  • Authorized redirect URIs: ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๊ฐ€ โœ‹Client์— ๊ถŒํ•œ์„ ๋ถ€์—ฌํ•  ๋•Œ Authorization code๋ฅผ ์ „๋‹ฌํ•ด์ฃผ๋Š”๋ฐ, Authorized redirect URIs๋ฅผ ํ†ตํ•ด ์ „๋‹ฌํ•ด์ค€๋‹ค. ์ฆ‰ ์ฝ”๋“œ๋ฅผ ์ „๋‹ฌ๋ฐ›๋Š” ๊ฒฝ๋กœ๋ฅผ โœ‹Client๊ฐ€ ์ง€์ •ํ•ด์•ผ ํ•˜๋Š” ๊ฒƒ์ด๋‹ค.

3. ๐ŸŽ…Resource Owner ์Šน์ธ


OAuth๋ฅผ ํ†ตํ•ด ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์˜ ์—ฌ๋Ÿฌ ๊ธฐ๋Šฅ ์ค‘ ํ•„์š”ํ•œ ๊ธฐ๋Šฅ๋งŒ ์‚ฌ์šฉํ•˜๊ฒŒ ํ•  ์ˆ˜ ์žˆ๋‹ค. ์ด๋ฅผ ์œ„ํ•ด ์‚ฌ์šฉ์ž๊ฐ€ ๋™์˜๋ฅผ ํ•ด์•ผ ํ•œ๋‹ค. ๋™์˜๋Š” ๊นƒํ—ˆ๋ธŒ ํ™”๋ฉด์˜ ํด๋ฆญ์œผ๋กœ ์ด๋ฃจ์–ด์ง„๋‹ค.

'๊นƒํ—ˆ๋ธŒ๋กœ ๋กœ๊ทธ์ธํ•˜๊ธฐ' ๋ฒ„ํŠผ์— URL์„ ์—ฐ๊ฒฐํ•ด์ฃผ๋ฉด ๋œ๋‹ค. ์„œ๋ฒ„ API๋ฅผ ํ†ตํ•ด์„œ ๋ฆฌ๋””๋ ‰์…˜ํ•˜๋„๋ก ํ–ˆ๋‹ค. private API key๋ฅผ ๋ฆฌ์•กํŠธ์— ์ €์žฅํ•˜๋ฉด ๋ณด์•ˆ์— ์ทจ์•ฝํ•˜๊ธฐ ๋•Œ๋ฌธ์— ์„œ๋ฒ„๊ฐ€ ๊นƒํ—ˆ๋ธŒ์— ๋ฐ์ดํ„ฐ๋ฅผ ๋ณด๋‚ด๋„๋ก ํ–ˆ๋‹ค.

// express์˜ index.js
app.get("/api/users/github/start", (req, res) => {
    res.redirect(`https://github.com/login/oauth/authorize
	?client_id=${process.env.CLIENT_ID}
	&redirect_uri=${process.env.REDIRECT_URL}`);
});

// ๋ฆฌ์•กํŠธ์˜ LoginPage.js
const goToLoginUrl = () => {
        window.location.href = "/api/users/github/start";
};

๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ๊ฐ€ ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋กœ ์œ„ ์ฃผ์†Œ๋ฅผ ํ†ตํ•ด ์ ‘์†ํ•˜๊ฒŒ ๋˜๋ฉด ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” ๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ๊ฐ€ ํ˜„์žฌ ๋กœ๊ทธ์ธ๋˜์–ด ์žˆ๋Š”์ง€ ํ™•์ธํ•œ๋‹ค. ๋กœ๊ทธ์ธ์ด ์•ˆ๋˜์–ด ์žˆ์œผ๋ฉด ๋กœ๊ทธ์ธ ํ™”๋ฉด์„ ๋ณด์—ฌ์ค€๋‹ค.

๋กœ๊ทธ์ธ์ด ๋˜์—ˆ์œผ๋ฉด, ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” URL๋กœ ๋ฐ›์€ Client ID์™€ ์ž์‹ ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” Client ID๋ฅผ ๋น„๊ตํ•ด ๊ฐ™์€์ง€ ํ™•์ธํ•œ๋‹ค.

๊ทธ ๋‹ค์Œ redirect_uri๋ฅผ ๋น„๊ตํ•˜๊ณ  ๋‹ค๋ฅด๋ฉด ์—ฌ๊ธฐ์„œ ์ž‘์—…์ด ๋๋‚œ๋‹ค. ๊ฐ™๋‹ค๋ฉด ๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ์—๊ฒŒ scope์— ๋Œ€ํ•œ ๊ถŒํ•œ์„ โœ‹Client์—๊ฒŒ ๋ถ€์—ฌํ•  ๊ฒƒ์ธ์ง€ ํ™•์ธํ•˜๋Š” ํ™”๋ฉด์„ ๋ณด์—ฌ์ค€ ํ›„ ๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ๊ฐ€ ํ—ˆ์šฉ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” ํ•ด๋‹น ์œ ์ €์˜ user id์™€ scope, Authorization code๋ฅผ ๋‚ด๋ถ€์ ์œผ๋กœ ์ €์žฅํ•ด ๋†“๋Š”๋‹ค.


4. ๐ŸŒˆResource Server ์Šน์ธ


๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” Client ID์™€ Authorized redirect URIs๋งŒ ์ผ์น˜ํ•œ๋‹ค๊ณ  ํ•ด์„œ Access Token์„ ๋ฐ”๋กœ ๋ฐœ๊ธ‰ํ•˜์ง€ ์•Š๋Š”๋‹ค. ์•„์ง Client Secret์„ ์‚ฌ์šฉํ•˜์ง€ ์•Š์•˜๋‹ค!


๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” ์šฐ๋ฆฌ๊ฐ€ ์•ฝ์†ํ•œ Authorized redirect URIs์— Authorization code๋ฅผ ๋‹ด์•„์„œ ๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ์—๊ฒŒ ์ „์†กํ•œ๋‹ค.

๋ฆฌ๋””๋ ‰์…˜ HTTP ๋ฉ”์‹œ์ง€๋Š” Location์„ ํ—ค๋”๋กœ ํ•œ๋‹ค. Location ํ—ค๋”๋Š” ํŽ˜์ด์ง€๋ฅผ ๋ฆฌ๋””๋ ‰์…˜ํ•  URL์„ ๋‚˜ํƒ€๋‚ธ๋‹ค. โœ‹Client์˜ ์›น ๋ธŒ๋ผ์šฐ์ €๋ฅผ ๋ฆฌ๋””๋ ‰์…˜์„ค์ •ํ•œURL?code=์–ด์˜๋ผ์ด์ œ์ด์…˜์ฝ”๋“œ๋กœ ์ด๋™ํ•˜๋ผ๋Š” ์š”์ฒญ์ด๋‹ค.

์ด ์š”์ฒญ์— ์˜ํ•ด ์ด๋™์ด ๋˜๋ฉด ์œ„ ์‚ฌ์ง„์ฒ˜๋Ÿผ โœ‹Client๋Š” ๐ŸŽ…๋ฆฌ์†Œ์Šค ์˜ค๋„ˆ์˜ Authorization code๋ฅผ ๊ฐ–๊ฒŒ ๋œ๋‹ค.


โ˜…โ˜…โ˜…โ˜…โ˜… ์ด์ œ ์ด Authorization code๋ฅผ ๊ฐ€์ง€๊ณ  โœ‹Client๋Š” ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์™€ ์ง์ ‘ ํ†ต์‹ ํ•œ๋‹ค.


๋จผ์ € ์„œ๋ฒ„๋‹จ์—์„œ Github๋กœ๋ถ€ํ„ฐ Access Token์„ ๋ฐœ๊ธ‰๋ฐ›๊ธฐ ์œ„ํ•œ API๋ฅผ ๋งŒ๋“ค์–ด์•ผ ํ•œ๋‹ค.

  • ์ƒ์„ฑํ•œ API URL ๋’ค์— Authorization code๋ฅผ ๋‹ด์•„์„œ ์„œ๋ฒ„๋‹จ์— ๋ณด๋‚ด๋ฉด, Back-End์—์„œ๋Š” ๋น„๋™๊ธฐ ํ†ต์‹ ์œผ๋กœ Github์— Access Token ๋ฐœ๊ธ‰์„ ์š”์ฒญํ•œ๋‹ค. Authorization code๋Š” Front-End์—์„œ ์ฝ์–ด๋“ค์ธ๋‹ค.

  • ์ด๋•Œ ๋ณด๋‚ด์•ผ ํ•˜๋Š” ์ •๋ณด(Client ID, Client Secret, Authorization code)๋Š” ๋น„๋™๊ธฐ ํ†ต์‹  API URL์— ๋‹ด์•„์„œ ๋ณด๋‚ธ๋‹ค. ํ—ค๋”๋„ ํ•„์ˆ˜๋กœ ๋„ฃ์–ด์•ผ ํ•œ๋‹ค.

// express์˜ index.js
app.get("/getAccessToken", async (req, res) => {
    const params = `?client_id=${process.env.CLIENT_ID}&client_secret=${process.env.CLIENT_SECRET}&code=${req.query.code}`;
    await axios
        .post(
            "https://github.com/login/oauth/access_token" + params,
            {},
            {
                headers: {
                    Accept: "application/json",
                },
            }
        )
        .then((fromGithub) => {
            res.status(200).json(fromGithub.data);
        });
});

// ๋ฆฌ์•กํŠธ์˜ LoginPage.js
useEffect(() => {
  const queryString = window.location.search;
  const paramFromURL = new URLSearchParams(queryString);
  const codeFromParam = paramFromURL.get("code");

  // ๋กœ์ปฌ์Šคํ† ๋ฆฌ์ง€
  if (codeFromParam && localStorage.getItem("githubAccessToken") === null) {
    getAccessToken();
    async function getAccessToken() {
      await axios
        .get(
        `http://localhost:5000/getAccessToken?code=${codeFromParam}`,
        {}
      )
        .then((res) => {
        if (res) {
          window.localStorage.setItem(
            "githubAccessToken",
            JSON.stringify(res.data.access_token)
          );
          setRerender(!rerender);
        }
      });
    }
  }
}, []);

๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„(Github)๋Š” URL๋กœ ์ „์†ก๋ฐ›์€ Authorization code ๋ฐ Client Secret ๋“ฑ์ด ๊ฐ€์ง€๊ณ  ์žˆ๋Š” ๋ฐ์ดํ„ฐ์™€ ๋ชจ๋‘ ์ผ์น˜ํ•˜๋Š”์ง€ ํ™•์ธํ•œ๋‹ค.



5. ์•ก์„ธ์Šค ํ† ํฐ ๋ฐœ๊ธ‰


๋ชจ๋‘ ์ผ์น˜ํ•œ๋‹ค๋ฉด ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„์™€ โœ‹Client๋Š” Authorization code๋ฅผ ์ง€์šด๋‹ค(๋˜ ๋‹ค์‹œ ์ธ์ฆํ•˜์ง€ ๋ชปํ•˜๋„๋ก ์‚ญ์ œ).

์ด์ œ ๐ŸŒˆ๋ฆฌ์†Œ์Šค ์„œ๋ฒ„๋Š” Access Token์„ ๋ฐœ๊ธ‰ํ•˜๊ณ  โœ‹Client์—๊ฒŒ ๋ณด๋‚ด์ค€๋‹ค. โœ‹Client๋Š” ํ† ํฐ์„ ์ €์žฅ์†Œ์— ๋ณด๊ด€ํ•ด๋†“๊ณ  ํ™œ์šฉํ•  ์ˆ˜ ์žˆ๊ฒŒ ๋œ๋‹ค.


profile
Frontend Engineer

0๊ฐœ์˜ ๋Œ“๊ธ€