Post

원티드 프리온보딩 - 로그인 기능 구현하기 3 (실습)

원티드 프리온보딩 - 로그인 기능 구현하기 3강 실습

실습 코드

세션-쿠키를 통해 로그인 기능 구현하기

1. 로그인 실패시 함수 종료. 로그인 성공시 ‘/page-a’로 이동

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
// pages/Login.js

const Login = () => {
  const { routeTo } = useRouter();

  const loginSubmitHandler = async (event) => {
    event.preventDefault();
    // FormData를 이용해서 로그인 시도
    const formData = new FormData(event.currentTarget);

    // TODO 3-1.: 로그인 실패시 함수 종료. 로그인 성공시 '/page-a'로 이동
    const loginResult = await login({
      username: formData.get("username"),
      password: formData.get("password")
    });

    if (!loginResult) return;
    routeTo("/page-a");
  };

  return (
    <div className="non-logged-in-body">
      <h1>로그인 페이지</h1>
      <p>
        로그인 성공시 Page A로 이동합니다.
        <br />
        실패시 alert를 띄웁니다.
      </p>
      <form onSubmit={loginSubmitHandler}>
        <label>
          Username:
          <input type="text" name="username" />
        </label>
        <label>
          Password:
          <input type="password" name="password" />
        </label>
        <button type="submit" value="Submit">
          submit
        </button>
      </form>
    </div>
  );
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
// api/login.js

export const login = async (args) => {
  // TODO 3-1: POST, '/auth/login' 호출
  // body에는 { username, password }가 들어가야 함
  // header가 올바르게 추가된 경우 쿠키는 자동으로 함께 전송됨
  const loginResponse = await axios.post(`${BASE_URL}/auth/login`, args, {
    headers: { "Content-Type": "application/json", credentials: "include" }
  });

  if (!loginResponse) {
    alert("로그인실패");
    return;
  }

  return loginResponse;
};

2. 로그인 여부에 따라 보여질 Rotuer 설정하기

  • 로그인(인증)을 해야 이동이 가능한 페이지는 GeneralLayout으로 감싸기
  • 로그인(인증)을 해야 이동이 가능한 페이지만 사이드바에 표시할 수 있게 SidebarContent 만들기
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
// router.js

const routerData = [
  {
    id: 0,
    path: "/",
    label: "Home",
    element: <Home />,
    withAuth: false
  },
  {
    id: 1,
    path: "/login",
    label: "로그인",
    element: <Login />,
    withAuth: false
  },
  {
    id: 2,
    path: "/page-a",
    label: "page-a",
    element: <PageA />,
    withAuth: true
  },
  {
    id: 3,
    path: "/page-b",
    label: "page-b",
    element: <PageB />,
    withAuth: true
  },
  {
    id: 4,
    path: "/page-c",
    label: "page-c",
    element: <PageC />,
    withAuth: true
  }
];

// GeneralLayout에는 페이지 컴포넌트를 children 으로 전달
export const routers = createBrowserRouter(
  routerData.map((router) => {
    if (router.withAuth) {
      return {
        path: router.path,
        element: <GeneralLayout>{router.element}</GeneralLayout>
      };
    } else {
      return {
        path: router.path,
        element: router.element
      };
    }
  })
);

// TODO 3-2: 라우터 객체에서 인증이 필요한 페이지만 필터링해 사이드바에 전달
// id, path, label을 전달하여 Sidebar에서 사용
export const SidebarContent = routerData.reduce((prev, router) => {
  if (!router.withAuth) return prev;
  return [...prev, { id: router.id, path: router.path, label: router.label }];
}, []);

3. GeneralLayout 컴포넌트의 Sidebar에 SidebarContent 전달하기

1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
// layout/GeneralLayout.js

const GeneralLayout = ({ children }) => {
  const [userProfile, setUserProfile] = useState(null);
  const { routeTo } = useRouter();

  // 중략...

  if (!userProfile) return <div>loading...</div>;

  return (
    <div className="general-layout">
      <Sidebar sidebarContent={SidebarContent} userProfile={userProfile} />
      <div className="general-layout__body">{children}</div>
    </div>
  );
};

export default GeneralLayout;

4. 로그인페이지 접속 시 이미 로그인된 유저인지 판별하기

  • 로그인된 유저: page-a 페이지로 이동
  • 로그인 안된 유저: login page 렌더
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
// Pages/Login.js

// TODO 3-2.: 이미 로그인된 유저인지 판별
const isLoggedIn = async () => {
  const userInfo = getCurrentUserInfo();
  userInfo ? true : false;
  return false;
};

const Login = () => {
  const { routeTo } = useRouter();

  // TODO 3-2.: 이미 로그인된 상태라면 page-a로 라우팅
  useEffect(() => {
    if (isLoggedIn()) {
      routeTo("/page-a");
    }
  }, []);

  const loginSubmitHandler = async (event) => {
    event.preventDefault();
    // FormData를 이용해서 로그인 시도
    const formData = new FormData(event.currentTarget);

    const loginResult = await login({
      username: formData.get("username"),
      password: formData.get("password")
    });

    if (!loginResult) return;
    routeTo("/page-a");
  };

  return <div className="non-logged-in-body">// 중략...</div>;
};

4. 로그인이 필요한 페이지 접속 시 로그인 여부를 확인하기

  • 유저 정보 가져오는 함수 작성 getCurrentUserInfo()
  • 유저 정보 가져오는 것을 실패하면 login page로 이동

1
2
3
4
5
6
7
8
9
10
11
12
13
// api/login.js

export const getCurrentUserInfo = async () => {
  // TODO 3-2: GET, '/profile' 호출
  // 호출 성공시 유저 정보 반환
  // header가 올바르게 추가된 경우 쿠키는 자동으로 함께 전송됨

  const userInfoResponse = await axios.get(`${BASE_URL}/profile`, {
    headers: { "Content-Type": "application/json", credentials: "include" }
  });

  return userInfoResponse ? userInfoResponse.data : null;
};
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
// layout/GeneralLayout.js

const GeneralLayout = ({ children }) => {
  const [userProfile, setUserProfile] = useState(null);
  const { routeTo } = useRouter();

  const fetchUserProfile = useCallback(async () => {
    // TODO 3-2: 페이지 이동시 마다 로그인 여부를 확인하는 함수 구현
    // 로그인 여부 확인 ('/profile' 호출 성공여부 확인)
    // 로그인 성공시 userProfile 상태 업데이트
    // 로그인 실패시 로그인 페이지로 이동 ('/login')
    const userInfo = await getCurrentUserInfo();
    if (!userInfo) {
      routeTo("/login");
      return;
    }
    setUserProfile(userInfo);
  }, []);

  useEffect(() => {
    // TODO 3-2: 페이지 이동시 마다 로그인 여부를 확인하는 함수 실행
    console.log("page changed!");
    fetchUserProfile();
  }, [children]);

  if (!userProfile) return <div>loading...</div>;

  return (
    <div className="general-layout">
      <Sidebar sidebarContent={SidebarContent} userProfile={userProfile} />
      <div className="general-layout__body">{children}</div>
    </div>
  );
};

export default GeneralLayout;
This post is licensed under CC BY 4.0 by the author.