[TIL] 10월 5일 LoginProcess

기록하며 공부하자·2021년 10월 5일
0

프론트엔드 개발자에게 가장 중요한 part를 생각해보면 바로 LoginProcess이다.

로그인 과정에서 백엔드로 어떠한 정보를 넘겨주고, 어떤 방식으로 넘겨주고, 로그인 세션을 얼마만큼 유지해야 하고 등등 회원가입,로그인페이지는 모든사이트에 있지만 절대 간단한 기능이 아니다.

LoginProcess (FRONT_END)

로그인과정에서 FRONT_END가 해야할일은 이용자에게 로그인할 페이지를 만들어 놓은후 로그인정보들을 입력받는다.

입력받은 정보를 토큰화해서 서버에 넘겨준다.

입력받은 정보는 JWT(Jason Web Token) 방식으로 저장되고 JWT 토큰에는 로그인하는 정보들이 담겨있다.

이 JWT 토큰은 누구나 정보를 확인할수 있다.

아래 링크에 접속해서 JWT형식을 입력하면 내용을 확인할수 있다.

https://jwt.io/

Encoded 부분에 JWT 형식의 문자열을 삽입하면 정보들이 나온다.

PAYLOAD 부분쪽에는 토큰 이름, 및 로그인 세션이 얼마나 되어있는지에 대한 정보들을 확인할수 있다.

다만, 민감한 개인정보들은 JWT 토큰에 담으면 안되기 때문에 이부분은 API 설정시 BACK_END 개발자가 민감한 정보들은 제외하고 담기도록 설정해야 한다.

이렇게 입력받은 정보들을 AccessToken화 해서 서버로 넘겨준다.

LoginProcess (Back_End)

FRONT_END로부터 로그인정보들을 받으면 BACK_END에서는 해당 정보들이 실제 가입된 유저인지 검증을 해줘야 한다.

BACK_END 에서는 서버에 있는 데이터가 실제 데이터베이스에 저장되어있는 유저인지 검증하는 과정을 진행하며 이때 비밀번호를 해싱하는 과정이 있다.

민감한 정보들은 탈취당할 우려가 있기 때문에 관리자도 회원들의 비밀번호를 모르도록 설정이 되어있다.
(웹사이트에서 비밀번호 찾기를 진해하면, 비밀번호를 알려주는것이 아니라 재설정하는것이 이러한 이유)

비밀번호 해싱 예시

입력한 데이터와 일치한다면 BACK_END 서버에서는 AccessToken을 발급해주며 이때 사용하는것이 위에서도 이야기한 JWT(Jason Web Token) 이다.
이부분에서는 로그인 세션시간 설정도 가능하기에 해당부분을 설정한다면 정보를 넣어서 AccessToken을 발급한다.

로그인 VScode 예시

전체적인 로그인 과정을 살펴봤으니 FRONT_END과정을 VScode로 적용해 볼수 있다.

로그인페이지에서 입력한 정보를 가지고 AcessToken을 발급받은 후 해당정보를 가지고 확인페이지로 이동해 확인페이지에서 {name}님 환영합니다! 라는 메세지를 띄워주도록 진행해보면 3가지 파일의 설정이 필요하다.

APP.JS, LoginPage, LoginSuccessPage

Context-API를 이용해 APP.JS에서 선언한 state를 각각의 페이지로 뿌려주는 방식으로 진행한다.

APP.JS 전체코드

import "antd/dist/antd.css";
// import "../styles/globals.css";
import {
  ApolloClient,
  ApolloProvider,
  InMemoryCache,
  ApolloLink,
} from "@apollo/client";
import Layout from "../src/components/commons/layout";
import { Global } from "@emotion/react";
import { globalStyles } from "../src/commons/styles/globalStyles";
import "slick-carousel/slick/slick.css";
import "slick-carousel/slick/slick-theme.css";
import { initializeApp } from "firebase/app";
import { createUploadLink } from "apollo-upload-client";
import { createContext, useEffect, useState } from "react";
export const firebaseApp = initializeApp({
  apiKey: "AIzaSyD_yuZWLxBbhml8p0SJcfrz5KIQXaovt88",
  authDomain: "firstproject-f510a.firebaseapp.com",
  projectId: "firstproject-f510a",
  storageBucket: "firstproject-f510a.appspot.com",
  messagingSenderId: "668992000072",
  appId: "1:668992000072:web:ebfc244bd7a21af735487d",
  measurementId: "G-S9BM03DPJW",
});
export const GlobalContext = createContext(null);
function MyApp({ Component, pageProps }) {
  const [accessToken, setAccessToken] = useState("");
  const [userInfo, setUserInfo] = useState({});
  const value = {
    accessToken: accessToken,
    setAccessToken: setAccessToken,
    userInfo: userInfo,
    setUserInfo: setUserInfo,
  };
  useEffect(() => {
    // localStorage.clear();
    const accessToken = localStorage.getItem("accessToken") || "";
    setAccessToken(accessToken);
  }, []);
  const uploadLink = createUploadLink({
    uri: "http://backend03.codebootcamp.co.kr/graphql",
    headers: { authorization: `Bearer ${accessToken}` },
  });
  const client = new ApolloClient({
    link: ApolloLink.from([uploadLink]),
    cache: new InMemoryCache(),
  });
  return (
    <GlobalContext.Provider value={value}>
      <Global styles={globalStyles} />
      <ApolloProvider client={client}>
        <Layout>
          <Component {...pageProps} />
        </Layout>
      </ApolloProvider>
    </GlobalContext.Provider>
  );
}
export default MyApp;
export const GlobalContext = createContext(null);
const [accessToken, setAccessToken] = useState("");
  const [userInfo, setUserInfo] = useState({});
  const value = {
    accessToken: accessToken,
    setAccessToken: setAccessToken,
    userInfo: userInfo,
    setUserInfo: setUserInfo,
  };
useEffect(() => {
    // localStorage.clear();
    const accessToken = localStorage.getItem("accessToken") || "";
    setAccessToken(accessToken);
  }, []);

먼저 전체 컨테이너에서 사용하기 위해 GlobalContext, createContext를 선언해주고 초기값을 null로 설정한다.

그다음 전역적으로 사용할 state와 value 값들을 설정해 준다.
페이지가 그려지고 난후 실행될 useEffect를 적어준다.

const uploadLink = createUploadLink({
    uri: "http://backend03.codebootcamp.co.kr/graphql",
    headers: { authorization: `Bearer ${accessToken}` },
  });
//// 감싸는 부분
 <GlobalContext.Provider value={value}>
 </GlobalContext.Provider>

authorization은 headers 부분에 적어줘야 하기 때문에 uploadLink 아래부분에 적어준다.

Barer부분은 인증방식을 뜻한다.

이렇게 하면 APP.JS의 설정은 끝이나고 로그인페이지, 로그인 성공페이지에서 알맞는 값들을 불러와서 사용하면 된다.

LoginPage 전체코드

import { gql, useMutation } from "@apollo/client";
import { useRouter } from "next/router";
import { useState, useContext } from "react";
import { GlobalContext } from "../../_app";
const LOGIN_USER = gql`
  mutation loginUser($email: String!, $password: String!) {
    loginUser(email: $email, password: $password) {
      accessToken
    }
  }
`;
export default function LoginPage1() {
  const router = useRouter();
  const [loginUser] = useMutation(LOGIN_USER);
  const { setAccessToken } = useContext(GlobalContext);
  const [myEmail, setMyEmail] = useState("");
  const [myPassword, setMyPassword] = useState("");
  function onChangeMyEmail(event) {
    setMyEmail(event.target.value);
  }
  function onChangeMyPassword(event) {
    setMyPassword(event.target.value);
  }
  async function onClickLogin() {
    // if (accessToken === null) {
    //   alert("로그인을 먼저 해주세요");
    //   return;
    // }
    // console.log(accessToken);
    try {
      const result = await loginUser({
        variables: {
          email: myEmail,
          password: myPassword,
        },
      });
      console.log(result.data?.loginUser.accessToken);
      setAccessToken(result.data?.loginUser.accessToken);
      localStorage.setItem("accessToken", result.data.loginUser.accessToken);
      router.push("/quiz/21-02-login-success");
    } catch (error) {
      alert("회원가입을 먼저 해주세요");
    }
  }
  return (
    <>
      이메일 : <input type="text" onChange={onChangeMyEmail} />
      비밀번호 : <input type="password" onChange={onChangeMyPassword} />
      <button onClick={onClickLogin}>로그인하기</button>
    </>
  );
}

로그인 페이지에서는 loginUser라는 Mutation이 필요하기 때문에 gql을 import 한후 내용을 적어준다.

const result = await loginUser({
        variables: {
          email: myEmail,
          password: myPassword,
        },
      });
      console.log(result.data?.loginUser.accessToken);
      setAccessToken(result.data?.loginUser.accessToken);
      localStorage.setItem("accessToken", result.data.loginUser.accessToken);
      router.push("/quiz/21-02-login-success");

로그인 페이지에서는 onClick시 accessToken 값을 넘겨주어야 하고 해당 accessToken 값은 APP.JS에서 선언된 setAccessToken값에 저장시켜 로그인 성공페이지에서 보여줘야 한다.

variables 후에 setAccessToken에 accessToken 값을 넣어주고 세션을 유지하기 위해 임시적으로 localStorage에도 저장시킨다.

모든 과정이 끝나면 로그인 성공페이지로 이동한다.

LoginSuccesPage 전체코드

import { gql, useQuery } from "@apollo/client";
import { useRouter } from "next/router";
import { useContext, useEffect } from "react";
import { GlobalContext } from "../../_app";
// import { Modal } from "antd";
// import styled from "@emotion/styled";
const FETCH_USER_LOGGED_IN = gql`
  query fetchUserLoggedIn {
    fetchUserLoggedIn {
      email
      name
    }
  }
`;
// const MyModal = styled(Modal)``;
export default function LoginSuccessPage() {
  const router = useRouter();
  const { data } = useQuery(FETCH_USER_LOGGED_IN);
  const { userInfo, setUserInfo, accessToken } = useContext(GlobalContext);
  useEffect(() => {
    if (userInfo.email) return;
    if (accessToken === "") {
      router.push("/quiz/21-01-login");
      alert("로그인을 먼저 해주세요");
    }
    setUserInfo({
      name: data?.fetchUserLoggedIn.name,
      email: data?.fetchUserLoggedIn.email,
    });
    // return () => {
    //   // setAccessToken(accessToken);
    // };
  }, []);
  return (
    <>
      {/* {!accessToken && <MyModal></MyModal>} */}
      <div>환영합니다.</div>
      <div>
        당신의 이름은{data?.fetchUserLoggedIn.name} 이메일은
        {data?.fetchUserLoggedIn.email} 입니다.
      </div>
    </>
  );
}

로그인 성공페이지에서는 로그인된 유저의 정보를 받아와서 그려주면 완성이다.
로그인 성공페이지에서는 별다른 특이사항 없이 받아온 데이터를 바인딩해주면 페이지에 표시가 된다.

로그인 성공페이지에서는 토큰이 만료되면 다시 로그인하라고 하는부분을 구현하다가 실패했는데 새로고침시에는 되지만 세션이 끊기면 자동으로 돌아가는 부분은 실패했다.

이부분은 useEffect가 다시 실행되어야 하는데 setInterval로 구현해야할지, 구현이 안되는 부분인지는 조금더 고민해봐야 할거 같다.

profile
프론트엔드 개발자 입니다.

0개의 댓글