๐Ÿ˜‡ Access token ๋งŒ๋ฃŒ์‹œ ์žฌ๋ฐœ๊ธ‰ํ•˜๊ธฐ

Doozuuยท2023๋…„ 8์›” 2์ผ
0

React

๋ชฉ๋ก ๋ณด๊ธฐ
23/23

์ œ์ผ ๊ณจ์นซ๋ฉ์–ด๋ฆฌ์˜€๋˜ ๋…€์„....๊ทธ๋ž˜๋„ ๋งˆ์นจ๋‚ด ํ•ด๊ฒฐํ–ˆ๋‹ค๐Ÿ˜‚


๐Ÿ“Œ Access token / Refresh token ๋งŒ๋ฃŒ์‹œ ์ฒ˜๋ฆฌ ๋กœ์ง

  • Axios ์š”์ฒญ์„ ๋ณด๋ƒˆ์„ ๋•Œ err.response.data.code๊ฐ€ EXPIRED_TOKEN์ด๋ฉด โ€œํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹คโ€๋ผ๋Š” ๋ฌธ๊ตฌ๊ฐ€ ๋‹ด๊ธด ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์›Œ์ค€๋‹ค.
  • ๋ชจ๋‹ฌ์ฐฝ์—์„œ ์žฌ๋ฐœ๊ธ‰ ๋ฒ„ํŠผ์„ ๋ˆ„๋ฅด๋ฉด ํ˜„์žฌ access token์„ ๋‹ด์•„์„œ post ์š”์ฒญ์„ ๋ณด๋‚ธ๋‹ค.
    • refresh token์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ๊ธ‰๋œ ๊ฒฝ์šฐ :
      • response๋กœ ๋ฐ›์€ refresh token์„ ๋‹ค์‹œ localstorage์— ์ €์žฅํ•˜๊ณ  /home ์œผ๋กœ ๋ฆฌ๋‹ค์ด๋ ‰ํŠธ์‹œํ‚จ๋‹ค.
      • ์ฃผ์˜ํ•  ์  : window.location.reload()๋กœ ์ƒˆ๋กœ๊ณ ์นจ์„ ๊ผญ ํ•ด์ค€๋‹ค. (์ด๋ ‡๊ฒŒ ํ•ด์•ผ localStorage.setItem๊ฐ€ ํ™•์‹คํžˆ ์ž‘๋™๋œ๋‹ค.)
    • refresh token๋„ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ :
      • localStorage.clear()๋กœ ํ˜„์žฌ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ๋งŒ๋ฃŒ๋œ ํ† ํฐ์„ ์ œ๊ฑฐํ•ด์ค€๋‹ค.
      • ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ redirect ์‹œํ‚จ๋‹ค.
      • ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ์ž๋™ ๋กœ๊ทธ์•„์›ƒ ๋˜์—ˆ๋‹ค๋Š” ์•Œ๋ฆผ์ฐฝ์„ ๋ณด์—ฌ์ค€๋‹ค.



์˜ˆ์‹œ ) ๋‹‰๋„ค์ž„ ์„ค์ • ํŽ˜์ด์ง€์—์„œ ํ† ํฐ ๋งŒ๋ฃŒ ํ•ธ๋“ค๋งํ•˜๊ธฐ

์—๋Ÿฌ ์ฒ˜๋ฆฌ ์ฝ”๋“œ

์ž…๋ ฅ๋ฐ›์€ ๋‹‰๋„ค์ž„์„ ๋‹ด์•„์„œ ๋ฐฑ์—”๋“œ์— put ์š”์ฒญ์„ ๋ณด๋‚ด๊ณ  ์—๋Ÿฌ๊ฐ€ ์žˆ์„ ๊ฒฝ์šฐ throw error๋ฅผ ํ•ด์ค€๋‹ค.
โญ๏ธ throw error๋ฅผ ํ•˜์ง€ ์•Š์œผ๋ฉด ์œ„์—์„œ ์—๋Ÿฌ๋ฅผ ์ œ๋Œ€๋กœ ์ฒ˜๋ฆฌํ•  ์ˆ˜ ์—†๋‹ค.

const putNickName = async () => {
    try {
      const res = await axios.put(
        "https://server.bageasy.net/members/nickname",
        {
          nickname: nickname,
        },
        {
          headers: {
            Authorization: token,
          },
        },
      );
      return res.data;
    } catch (err) {
      console.log(err);
      throw err; // ์ค‘์š”!! ์ด๊ฑฐ ์•ˆ ๋„ฃ์œผ๋ฉด ์—๋Ÿฌ ๊ฐ์ง€๋ชปํ•จ
    }
  };

putNickName์„ ์‹คํ–‰ํ•ด์„œ error๋ฅผ ์บ์น˜ํ•ด ์—๋Ÿฌ ์ฝ”๋“œ๊ฐ€ "EXPIRED_TOKEN"์ผ ๊ฒฝ์šฐ setIsExpired(true)๋กœ ์„ค์ •ํ•ด์ค€๋‹ค.
(์•„๋ž˜์—์„œ isExpired๊ฐ€ true์ผ ๊ฒฝ์šฐ ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ชจ๋‹ฌ์ฐฝ์„ ๋„์šฐ๋„๋ก ์„ค์ •ํ•ด๋‘์—ˆ๋‹ค.)

 const PutNickName = async () => {
    if (nickname.length < 2) {
      setIsFocused(true);
    } else {
      try {
        const data = await putNickName(); // ๋‹‰๋„ค์ž„ put ์š”์ฒญ
        setIsOverlap(false);
        setIsFocused(false);
        setTemp("");
        navigate("/home");
      } catch (err) {
        if (err.response && err.response.data.code === "EXPIRED_TOKEN") {
          // ํ† ํฐ ๋งŒ๋ฃŒ
          setIsExpired(true);
        } else if (
          err.response &&
          err.response.data.code === "DUPLICATE_NICKNAME"
        ) {
          // ๋‹‰๋„ค์ž„ ์ค‘๋ณต
          setIsOverlap(true);
          setIsFocused(true);
          setTemp(nickname);
        }
      }
    }
  };

ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ชจ๋‹ฌ์ฐฝ์—์„œ ์žฌ๋ฐœ๊ธ‰ ๋ฒ„ํŠผ์„ ํด๋ฆญํ•˜๋ฉด ํ˜„์žฌ access token์„ ๋ฐฑ์—”๋“œ๋กœ ๋ณด๋‚ธ๋‹ค.

  • refresh ํ† ํฐ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ๊ธ‰๋˜๋ฉด ํ† ํฐ์„ ๋‹ค์‹œ ์ €์žฅํ•˜๊ณ  ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™ํ•œ๋‹ค.
  • refresh ํ† ํฐ๋„ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ ์ €์žฅ๋˜์–ด ์žˆ๋Š” ํ† ํฐ์„ ์‚ญ์ œํ•˜๊ณ  ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™์‹œํ‚จ๋‹ค.
// ํ† ํฐ ์žฌ๋ฐœ๊ธ‰
  const RefreshToken = async () => {
    try {
      const res = await axios.post(
        "https://server.bageasy.net/auth/reissue",
        {},
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      );
      console.log(res);

      if (res.status == 200) {
        // ํ† ํฐ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ๊ธ‰๋œ ๊ฒฝ์šฐ
        localStorage.setItem("bagtoken", res.data.accessToken); // ํ† ํฐ ์ €์žฅ
        handleNavigateHome(); // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
        window.location.reload(); // ์ƒˆ๋กœ๊ณ ์นจ
      }
      if (res.status == 401) {
        // refresh token๋„ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ -> ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•˜๋„๋ก
        localStorage.clear(); // ์ €์žฅ๋˜์–ด ์žˆ๋Š” ํ† ํฐ ์‚ญ์ œ
        handleNavigateLogin(); // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
        window.alert("ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
      }
    } catch (err) {
      console.log(err.response);
    }
  };



์ „์ฒด ์ฝ”๋“œ

  1. ๋‹‰๋„ค์ž„ ์„ค์ • ํŽ˜์ด์ง€
import styled from "styled-components";
import Arrow from "../assets/GoogleLogin/arrow.png";
import { useNavigate } from "react-router-dom";
import { useState, useEffect, useRef } from "react";
import TokenRefreshModal from "../components/Common/TokenRefreshModal";
import axios from "axios";

const Nickname = () => {
  const [nickname, setNickName] = useState(""); // ๋‹‰๋„ค์ž„ ์ž…๋ ฅ๋ฐ›๊ธฐ
  const [isOverlap, setIsOverlap] = useState(false); // ๋‹‰๋„ค์ž„ ์ค‘๋ณต ์ฒดํฌ
  const [isFocused, setIsFocused] = useState(false); // focus ์—ฌ๋ถ€
  const [temp, setTemp] = useState(""); // ํ˜„์žฌ ์ž…๋ ฅ๊ฐ’์ด ์ค‘๋ณต๋˜๋Š”์ง€ ์ฒดํฌ
  const [isExpired, setIsExpired] = useState(false);
  const inputRef = useRef(null); // focus ๊ฐ์ง€
  const navigate = useNavigate();
  const token = localStorage.getItem("bagtoken");

  const handleNavigateBack = () => {
    navigate(-1);
  };

  const handleNickName = e => {
    setNickName(e.target.value);
  };

  // focus ์—ฌ๋ถ€ ๊ฐ์ง€
  useEffect(() => {
    const inputElement = inputRef.current;
    inputElement.addEventListener("focus", () => setIsFocused(true));
    inputElement.addEventListener("blur", () => setIsFocused(false));

    return () => {
      inputElement.removeEventListener("focus", () => setIsFocused(true));
      inputElement.removeEventListener("blur", () => setIsFocused(false));
    };
  }, []);

  const putNickName = async () => {
    try {
      const res = await axios.put(
        "https://server.bageasy.net/members/nickname",
        {
          nickname: nickname,
        },
        {
          headers: {
            Authorization: token,
          },
        },
      );
      return res.data;
    } catch (err) {
      console.log(err);
      throw err; // ์ค‘์š”!! ์ด๊ฑฐ ์•ˆ ๋„ฃ์œผ๋ฉด ์—๋Ÿฌ ๊ฐ์ง€๋ชปํ•จ
    }
  };

  const PutNickName = async () => {
    if (nickname.length < 2) {
      setIsFocused(true);
    } else {
      try {
        const data = await putNickName();
        setIsOverlap(false);
        setIsFocused(false);
        setTemp("");
        navigate("/home");
      } catch (err) {
        if (err.response && err.response.data.code === "EXPIRED_TOKEN") {
          // ํ† ํฐ ๋งŒ๋ฃŒ
          setIsExpired(true);
        } else if (
          err.response &&
          err.response.data.code === "DUPLICATE_NICKNAME"
        ) {
          // ๋‹‰๋„ค์ž„ ์ค‘๋ณต
          setIsOverlap(true);
          setIsFocused(true);
          setTemp(nickname);
        }
      }
    }
  };

  return (
    <>
      {isExpired && <TokenRefreshModal />} // ๋ชจ๋‹ฌ์ฐฝ ๋„์šฐ๊ธฐ
      <NickNameContainer>
        <ArrowIcon src={Arrow} onClick={handleNavigateBack} />
        <Copy>๋‹‰๋„ค์ž„์„ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”!</Copy>
        <Copy2>์ดํ›„ ๋‹‰๋„ค์ž„ ๋ณ€๊ฒฝ์ด ๋ถˆ๊ฐ€ํ•˜๋‹ˆ ์‹ ์ค‘ํ•˜๊ฒŒ ๊ฒฐ์ •ํ•ด์ฃผ์„ธ์š”.</Copy2>
        <Container>
          <Input
            placeholder="์—ฌ๊ธฐ์— ์ž…๋ ฅํ•˜์„ธ์š”..."
            onChange={handleNickName}
            ref={inputRef}
            color={
              isFocused &&
              (nickname.length < 2 || (isOverlap && nickname === temp))
                ? "T"
                : "F"
            }
          />
          {isFocused && nickname.length < 2 && (
            <Copy3>- ๋‹‰๋„ค์ž„์„ 2๊ธ€์ž ์ด์ƒ ์ž…๋ ฅํ•ด์ฃผ์„ธ์š”.</Copy3>
          )}
          {isFocused && isOverlap && nickname === temp && (
            <Copy3>- ์ค‘๋ณต๋˜๋Š” ๋‹‰๋„ค์ž„์ž…๋‹ˆ๋‹ค.</Copy3>
          )}
        </Container>
        <Btn onClick={PutNickName}>ํ™•์ธ</Btn>
      </NickNameContainer>
    </>
  );
};

export default Nickname;
  1. ํ† ํฐ ์žฌ๋ฐœ๊ธ‰ ๋ชจ๋‹ฌ์ฐฝ
import { styled } from "styled-components";
import ModalBg from "../../assets/modal/modal_bg.png";
import axios from "axios";
import { useNavigate } from "react-router-dom";

const TokenRefreshModal = () => {
  const navigate = useNavigate();

  const token = localStorage.getItem("bagtoken");

  const handleNavigateHome = () => {
    navigate("/home");
  };

  const handleNavigateLogin = () => {
    navigate("/login");
  };

  // ํ† ํฐ ์žฌ๋ฐœ๊ธ‰
  const RefreshToken = async () => {
    try {
      const res = await axios.post(
        "https://server.bageasy.net/auth/reissue",
        {},
        {
          headers: {
            Authorization: `Bearer ${token}`,
          },
        },
      );
      console.log(res);

      if (res.status == 200) {
        // ํ† ํฐ์ด ์„ฑ๊ณต์ ์œผ๋กœ ๋ฐœ๊ธ‰๋œ ๊ฒฝ์šฐ
        localStorage.setItem("bagtoken", res.data.accessToken); // ํ† ํฐ ์ €์žฅ
        handleNavigateHome(); // ๋ฉ”์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
        window.location.reload(); // ์ƒˆ๋กœ๊ณ ์นจ
      }
      if (res.status == 401) {
        // refresh token๋„ ๋งŒ๋ฃŒ๋œ ๊ฒฝ์šฐ -> ๋‹ค์‹œ ๋กœ๊ทธ์ธ ํ•˜๋„๋ก
        localStorage.clear(); // ์ €์žฅ๋˜์–ด ์žˆ๋Š” ํ† ํฐ ์‚ญ์ œ
        handleNavigateLogin(); // ๋กœ๊ทธ์ธ ํŽ˜์ด์ง€๋กœ ์ด๋™
        window.alert("ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์–ด ์ž๋™์œผ๋กœ ๋กœ๊ทธ์•„์›ƒ ๋˜์—ˆ์Šต๋‹ˆ๋‹ค.");
      }
    } catch (err) {
      console.log(err.response);
    }
  };

  return (
    <Container>
      <P>์ธ์ฆ ํ† ํฐ์ด ๋งŒ๋ฃŒ๋˜์—ˆ์Šต๋‹ˆ๋‹ค. ๋‹ค์‹œ ๋ฐœ๊ธ‰ํ•ด์ฃผ์„ธ์š”.</P>
      <Btn onClick={RefreshToken}>์žฌ๋ฐœ๊ธ‰</Btn>
      <Modal src={ModalBg} />
    </Container>
  );
};

export default TokenRefreshModal;
profile
๋ชจ๋“ ๊ฒŒ ์ƒˆ๋กญ๊ณ  ์žฌ๋ฐŒ๋Š” ํ”„๋ก ํŠธ์—”๋“œ ์ƒˆ์‹น

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