jtwjs Dev Wiki
  • DEV_ROAD
    • 💪🏻 생존하기
    • Week 1
      • 개발 환경 세팅
      • 타입스크립트
      • 리엑트
      • Testing Library
      • Parcel & ESLint
    • Week 2
      • JSX
      • Virtual DOM
    • Week 3
      • React Component
      • React State
    • Week 4
      • Express
      • Fetch API & CORS
      • React Hook
      • useRef & Custom Hook
    • Week 5
      • TDD
      • React Testing Library
      • MSW
      • Playwrite
      • Snapshot
    • Week 6
      • Separtion of Concerns
      • Principle
      • DI, (Dependency Injection)
      • Reflect-metadata
      • TSyringe
      • External Store
      • Follow Redux
      • usestore-ts
      • useSyncExternalStore
    • Week 7
      • Routing
      • Routes
      • Router
      • Navigation
    • Week 8
      • Design System
      • Style Basics
      • CSS-in-JS
      • Styled-Components
      • Global Style & Theme
    • Week 9
      • 개발하기 전 준비
      • 상품 목록 페이지
      • 상품 상세 페이지
      • 장바구니 페이지
    • Week 10
      • 로그인
      • 로그아웃
      • 회원가입
      • 주문 목록 & 주문 상세
    • Week 11
      • 배송 정보 입력
      • 포트원 결제 요청
      • 배송 및 결제 정보 전달
    • Week 12
      • 관리자 웹사이트개발시작
  • DEV_NOTE
    • TypeScript
      • 기본적 문법
        • Enum
        • 다형성
          • Untitled
        • 구조적 타이핑
        • 제너릭 타입
        • 컨디셔널 타입
        • 함수 메서드 타이핑
        • infer로 타입스크립트의 추론 직접 활용
        • 재귀 타입
        • 템플릿 리터럴 타입
        • 추가적인 타입 검사 satisfies 연산자
        • 타입스크립트 건망증
        • 원시 자료형에도 브랜딩 기법 사용 가능
        • 타입 좁히기
        • 유용한 타입 만들기
        • 데코레이터 함수
        • 앰비언트 선언도 선언 병합이 된다.
        • 앰비언트 선언도 선언병합이 된다.
    • Testing
      • Unit Testing
      • 단위 테스트의 두 분파
      • 좋은 단위 테스트를 구성하는 4대 요소
      • 테스트 대역과 식별할 수 있는 동작
      • 단위 테스트 스타일
      • 가치 있는 단위 테스트를 위한 리팩토링
      • 통합 테스트
      • Cross Browsing Testing
      • 기능 테스트 종류
      • React Testing Pattern
      • 프론트엔드 테스트 입문
        • 테스트 범위
        • 단위 테스트 검증
        • Mock
        • UI 컴포넌트 테스트
        • 테스트 커버리지
        • 웹 통합 테스트
        • MSW
        • 스토리북
        • 시각적 회귀 테스트
        • E2E 테스트
        • Github Actions 설정
        • 깃허브 액션에서 E2E
      • 시프트 레프트
        • 테스트 기본중의 기본
        • 단위 테스트
        • 코드 복잡도
        • 리팩터링
        • 코드 리뷰
        • 통합 테스트 패턴
        • 시스템 테스트의 자동화
        • 탐색적 테스트
      • Test Tip
      • vitest
      • playwright
      • Test Data Generator
      • MSW
    • Algorithm
      • coding test
      • Data Structure
    • Next.js
      • Data Fetching
      • Hydration
      • Next 13
      • Optimization
      • Next 15
    • Tailwind
      • Tailwind CSS
      • Theme
      • Directives
      • Tool
      • Design System
    • Storybook
      • Storybook
      • CSF3
      • CDD
      • Headless Component
    • Funtional Programming
      • 함수형 프로그래밍
      • 참조 투명성
      • 부수효과
      • 함수 합성
      • 제너릭 타입 활용하기
      • 암묵적 입출력
      • 액션과 계산, 데이터
      • 계층형 설계
      • 호출 그래프
      • 함수형 설계
      • 불변성
      • 일급 함수
      • 함수형 도구
    • Git
      • Github Actions
      • Conflict
      • Branch 전략
    • Contents Format
      • Audio
    • 3D Graphic
      • 3D keyword
      • Three.js
      • Geometry
      • Material
      • Light
      • Camera
      • Decal
      • Rotation
      • Text
      • Shadow
      • Fog
      • Post Processing
      • Animation
      • Math
        • Vector Space
        • 벡터의 연산
        • 회전 계산
      • 3D 컨텐츠가 만들어지는 과정
      • R3F
      • Env
      • Scene
      • Transform
      • R3F
      • Interaction & Raycast
      • Rendering Algorithnm
      • Blender
      • Blender
    • Accessibility
      • 접근성이란
    • Interactive Web
      • Parallax
      • Canvas
      • requestAnimationFrame
      • Effect
      • HSL
      • React.js + Canvas
      • Matter.js
    • AWS
      • DevOps
      • Amplify
      • S3
      • 클라우드 컴퓨팅
        • 온프레미스와 클라우드
        • 클라우드 도입효과
        • 클라우드 컴퓨팅의 범위
        • 컴퓨팅 옵션
          • EC2 - Virtual Machin
          • ECS, EKS - Container
          • Lambda - Serverless
        • 네트워크 가상화
        • 스토리지
        • 데이터베이스
        • 데이터 수집
        • 머신 러닝 영역
        • IoT 영역
        • 블록체인 영역
      • 클라우드 아키텍처 설계
    • Network
      • Web Server & WAS
    • System Design
      • System Design
      • Component
      • 의존성을 배제한 개발
      • Error Handling
      • Architecture
        • 모노로틱 아키텍처
        • Clean Architecture
        • Layered Architecture
        • 이벤트 기반 아키텍처
      • 상황을 파악하는 메타인지
      • 중복 문제 해결하기
      • Monorepo Arhitecture
        • 모노레포 운영과 트러블슈팅
        • Module Federation
      • 코드 병목지점
      • API 대응
      • 공통 코드
      • Infra 구축
      • 모듈 기반의 개발 방식
      • Design System
        • 최소 수준의 아키텍처 설정
        • 더 효율적인 디자인시스템 만들기
        • 디자인 시스템과 UI 라이브러리 목적
        • 디자인 토큰
      • 효율적인 업무
        • 업무 프로세스 병목 파악
      • Clean Code
      • Design Pattern
        • CQRS Pattern
        • Strangler Fig Pattern
        • 데코레이터 패턴
        • 커맨드 패턴
        • 전략 패턴
        • 옵저버 패턴
      • A/B 테스팅
      • 대규모 리엑트 웹앱 개발
        • 복잡성 관리
        • 모듈성
        • 성능
        • 디자인 시스템
        • 데이터 패칭
        • 상태 관리
        • 국제화
        • 코드 조직화하기
        • 개인화 A/B 테스팅
        • 확장 가능한 웹 아키텍처
        • 테스팅
        • 툴링
        • 기술적 마이그레이션
        • 타입스크립트
        • 라우팅
        • 사용자 중심 API 디자인
        • 리액트 미래
    • Performance
      • React DevTools
      • Component 최적화
      • Page Load
      • API
    • MFA
      • MSA
      • MFA 도입하기
      • Monorepo
        • Monorepo Tool
        • Yarn Berry Workspace
        • Turborepo
      • MFA Composition
      • SPA 통합
      • Design System
      • Package Manager
        • Yarn
        • pnpm
      • Transpiler & Bundler
        • Babel
        • Rollup
        • esbuild
        • swc
        • Webpack
        • Vite
      • 분해와 통합을 위한 여러 기술 비교
    • State Management
      • Zustand
    • React v18
      • Automatic batching
      • Suspense
      • Transition
    • SEO
      • Search Engine Optimization
      • Open Graph Element
      • Metadata
    • FE Develop
      • Scrubbing
      • Clipboard
    • Refactoring
      • 리팩토링 깊게 들여다보기
      • 긴 코드 조각내기
      • 타입 코드 처리하기
      • 유사한 코드 융합하기
      • 데이터 보호
      • 코드 추가 및 제거
    • OAuth 2.0
    • Analytics
      • Mixpanel
    • ETC
      • VSCode
    • React Hook In Action
      • useContext & Provider
      • 커스텀 훅
      • 코드 분할하기 with Suspense, lazy
      • Suspense와 이미지 적재하기
      • useTransition, uesDeferredValue
      • SuspenseList
Powered by GitBook
On this page
  • 로그인 기능
  • User Scenario
  • Authorization
  • 세션(Session)
  • 쿠키(Cookie)
  • OAuth
  • JWT (Json Web Token)
  • 토큰 관리하기
  • Local Storage
  • Cookie
  • 토큰 주입하기
  • Axios Interceptor
  1. DEV_ROAD
  2. Week 10

로그인

로그인 기능

User Scenario

  • 이메일과 패스워드를 입력하여 로그인을 한다.

  • 이메일 및 패스워드 값이 유효성에 어긋날시 에러 메시지가 노출된다.

  • 유효성에 어긋나거나 값이 채워지지 않은 경우 로그인 버튼은 Disabled 상태가 된다.

  • 헤더에 로그인 페이지로 이동되는 링크가 제공된다.

  • 로그인된 사용자는 헤더에 로그인 링크가 사라지고 장바구니 링크와 로그아웃 버튼이 제공된다.

  • 로그인에 성공하면 이전 페이지로 이동한다.

Unit Test

LoginForm.test.tsx

import { render, screen } from "@testing-library/react";
import userEvent from "@testing-library/user-event";
import { Route } from "react-router-dom";

import VALID_MSG from "@/constants/validMsg";
import { withAllContexts, withRouter } from "@/tests/utils";

import LoginForm from "./LoginForm";

const context = describe;

describe("LoginForm", () => {
  function renderLoginForm() {
    return render(
      withAllContexts(withRouter(<Route path="/" element={<LoginForm />} />))
    );
  }

  it("render correctly", () => {
    renderLoginForm();

    expect(screen.getByRole("textbox", { name: "E-mail" })).toBeInTheDocument();
    expect(screen.getByLabelText("Password")).toBeInTheDocument();
    expect(screen.getByRole("button", { name: "로그인" }));
  });

  context("when email value is invalid", () => {
    it(`display error message "${VALID_MSG.EMAIL}"`, async () => {
      renderLoginForm();

      const emailInput = screen.getByRole("textbox", { name: "E-mail" });
      await userEvent.type(emailInput, "xxxxxxx");

      expect(emailInput).toHaveValue("xxxxxxx");
      expect(screen.getByText(VALID_MSG.EMAIL)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderLoginForm();

      const emailInput = screen.getByRole("textbox", { name: "E-mail" });
      await userEvent.type(emailInput, "xxxxxxx");

      expect(screen.getByRole("button", { name: "로그인" })).toBeDisabled();
    });
  });

  context("when password value is invalid", () => {
    it(`display error message "${VALID_MSG.PASSWORD}"`, async () => {
      renderLoginForm();

      const passwordInput = screen.getByLabelText("Password");
      await userEvent.type(passwordInput, "xxxxxxx");

      expect(passwordInput).toHaveValue("xxxxxxx");
      expect(screen.getByText(VALID_MSG.PASSWORD)).toBeInTheDocument();
    });

    it("submit button is disabled", async () => {
      renderLoginForm();

      const passwordInput = screen.getByLabelText("Password");
      await userEvent.type(passwordInput, "xxxxxxx");

      expect(screen.getByRole("button", { name: "로그인" })).toBeDisabled();
    });
  });

  context("when the form is empty", () => {
    it("submit button is disabled", async () => {
      renderLoginForm();

      expect(screen.getByRole("button", { name: "로그인" })).toBeDisabled();
    });
  });
});

LoginFormStore.test.ts

import VALID_MSG from "@/constants/validMsg";

import LoginFormStore from "./LoginFormStore";

const context = describe;

describe("LoginFormStore", () => {
  let store: LoginFormStore;

  beforeEach(() => {
    store = new LoginFormStore();
  });

  describe("change email", () => {
    it("email is changed", () => {
      const email = "v_oyb@naver.com";
      store.changeEmail(email);

      expect(store.email).toBe(email);
    });

    context("when value is invalid", () => {
      it(`error is ${VALID_MSG.EMAIL}`, () => {
        store.changeEmail("invalid email");

        expect(store.error).toBe(VALID_MSG.EMAIL);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changeEmail("tester@example.com");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("change password", () => {
    it("password is changed", () => {
      const password = "password";
      store.changePassword(password);

      expect(store.password).toBe(password);
    });

    context("when value is invalid", () => {
      it(`error is ${VALID_MSG.PASSWORD}`, () => {
        store.changePassword("123123");

        expect(store.error).toBe(VALID_MSG.PASSWORD);
      });
    });

    context("when value is valid", () => {
      it("error is empty", () => {
        store.changePassword("password");

        expect(store.error).toBeFalsy();
      });
    });
  });

  describe("with reset", () => {
    it("all field values are initialized", () => {
      store.changeEmail("tester@example.com");
      store.changePassword("password");

      store.reset();

      expect(store.email).toBeFalsy();
      expect(store.password).toBeFalsy();
    });
  });
});
E2E Test
/// <reference types="cypress" />

// 이메일과 패스워드를 입력하여 로그인을 한다.
// 이메일 및 패스워드 값이 유효성에 어긋날시 에러 메시지가 노출된다.
// 유효성에 어긋나거나 값이 채워지지 않은 경우 로그인 버튼은 Disabled 상태가 된다.
// 헤더에 로그인 페이지로 이동되는 링크가 제공된다.
// 로그인된 사용자는 헤더에 로그인 링크가 사라지고 장바구니 링크와 로그아웃 버튼이 제공된다.
// 로그인에 성공하면 이전 페이지로 이동한다.

describe("Login", () => {
  beforeEach(() => {
    cy.visit("");
  });

  context("when email and password values are invalid", () => {
    it("unable to login", () => {
      cy.findByRole("link", { name: "Login" }).should("exist").click();

      cy.findByRole("textbox", { name: "E-mail" }).type("invalid email");
      cy.findByText("이메일 형식이 올바르지 않습니다.").should("exist");

      cy.findByLabelText("Password").type("invalid password1@");
      cy.findByText("비밀번호 형식이 올바르지 않습니다.").should("exist");

      cy.findByRole("button", { name: "로그인" }).should("be.disabled");
    });
  });

  context("when logged in", () => {
    it("login link disappears and is replaced by cart link and logout button", () => {
      cy.login();
      cy.visit("");
      cy.findByRole("link", { name: "Login" }).should("not.exist");
      cy.findByRole("link", { name: "Cart" }).should("exist");
      cy.findByRole("button", { name: "Logout" }).should("exist");
    });

    it("redirected back to the previous page", () => {
      cy.visit("products/0BV000PRO0001");

      cy.findByRole("link", { name: "Login" }).should("exist").click();
      cy.findByRole("textbox", { name: "E-mail" }).type("tester@example.com");
      cy.findByLabelText("Password").type("password");
      cy.findByRole("button", { name: "로그인" }).click();

      cy.url().should("include", "/products/0BV000PRO0001");
    });
  });
});

Authorization

인증을 받은 사용자가 이후 인증이 필요한 서비스를 이용할 때 서버에서 인증된 사용자인지 확인 후 허가를 해주는 것

세션(Session)

서버에서 가지고 있는 객체로, 특정 사용자의 로그인 정보를 유지하기 위해 사용한다.

  • 기본적으로 HTTP는 무상태 프로토콜로 서버는 클라이언트의 상태를 보존하지 않는다.

  • 세션을 이용하여 클라이언트의 상태를 유지할 수 있다.

  • 서버가 클라이언트에게 고유한 Session ID 를 부여하면, 클라이언트는 서버에 요청할 때 마다Session ID 를 함께 전송하여 서버에게 자신이 누구인지 알려주는 역할을 수행한다.

세션 인증 방식

  1. 로그인 요청

  2. 서버에서 세션 ID를 발급하고 서버 메모리에 저장 후 클라이언트에게 전달

  3. 브라우저에 세션 정보를 저장 & 이후 세션 ID와 함께 요청 수행

  4. 서버에 저장된 세션 리스트를 확인 후 인가 처리

장점

  • 클라이언트에게 세션 ID를 제공하고 회원에 대한 중요 정보는 서버에서 관리한다.

  • 클라이언트가 가지고 있는 세션 ID 자체에는 민감한 개인정보를 포함하고 있지 않다.

  • 메모리에 있는 세션 아이디들을 제어 가능하다

단점

  • 서버의 메모리는 휘발성 메모리 (재부팅하면 날라감)

  • 서버가 여러대인 경우 세션 유지가 번거로움

  • 서버에서 세션 정보를 기록하고 관리하므로 트래픽이 몰릴 경우 서버에 메모리 부하가 존재할 수 있다.

쿠키(Cookie)

사용자가 특정 사이트를 방문할 때 사용자 컴퓨터에 저장하는 기록 파일

  • 서버에 자원을 전혀 사용하지 않음

  • 쿠키 정보는 항상 서버에 전송된다.

  • 보안에 민감한 데이터는 저장하면 안된다.

  • 쿠키는 http, https를 구분하지 않고 전송 (secure 옵션을 적용하면 https 경우만 전송)

HttpOnly: 자바스크립트에서 접근 불가, HTTP 전송에만 사용됨 (XSS 공격 방지)

SameSite: XSRF 공격 방지, 요청 도메인과 쿠키에 설정된 도메인이 같은 경우만 쿠키 전송

OAuth

Open Authorization의 약자로 인증과 권한 부여를 위한 개방형 프로토콜

사용자가 자신의 리소스를 다른 애플리케이션과 공유할 수 있도록 허용하는 인증 권한부여 메커니즘

  • 주로 소셜 로그인 기능을 이용할 때 사용된다.(Google, Kakao)

  • 사용자는 자신의 계정 정보를 직접 제공하지 않고도 다른 서비스의 접근 권한을 부여할 수 있다.

  • 사용자가 설정한 권한에 대해서만 접근할 수 있다. with Access token

OAuth 2.0 핵심 구성 요소

  • Resource Owner: 특정 서비스를 사용하려는 사용자

  • Client: 특정한 개인/회사가 만든 서비스(웹/앱 서버)를 의미

  • Resource Server: 사용자의 개인정보를 가지고 있는 서버(Google, Kakao 등)

    • Client는 Access token을 Resource Server에 보내서 사용자의 개인정보를 받음

  • Authorization Server: 실질적인 권한을 부여하는 서버

    • 사용자는 자신의 SNS 계정 정보를 넘겨 Authroization Code를 받음

    • Client는 사용자로부터 받은Authroization Code를 넘겨 Access Token을 받는다

OAuth 2.0 동작 예시
  1. SNS 로그인 (클라이언트 → 웹서버)

  2. 서버에서 Client ID, Redirect URI를 클라이언트에게 응답 (서버 → 클라이언 트)

  3. 서버에서 받은 값들을 이용해 SNS 로그인 페이지 요청 (클라이언트 → Authorization Server)

  1. SNS 로그인 페이지 제공 (Authorization Sever → 클라이언트)

  1. 제공받은 SNS 로그인 페이지에서 로그인 수행 (클라이언트 → Authorization Server)

  1. 로그인이 성공하면 Authorization Code 전달 (Authroization Server → 클라이언트)

  1. 전달받은 Authorization Code를 가지고 Redirect URI 접속 (클라이언트 → 서버)

  1. Autorization Code를 Authorization Server에 전달 (서버 → Authroization Server)

  1. Access Token 발급 (Authroization Server → 서버)

  1. Access Token으로 API 호출 (서버 → Resource Server)

  1. 요청된 자원 전달 (Resource Server → 서버)

  1. 서비스 제공 (서버 → 클라이언트)

JWT (Json Web Token)

인증에 필요한 정보를 암호화한 JSOn 형식의 토큰

  • 토큰을 HTTP Header에 담아 요청해서 서버가 클라이언트를 식별할 수 있도록 한다.

    • Authorization: Barer {JWT}

  • 인증이 완료되면 서버에서 Token을 발급하고 저장하지 않고 클라이언트에게 통째로 전달한다.

JSON(JavaScript Object Notation) → 데이터를 주고 받기 위한 데이터 포맷 중 하나 (key: value) 쌍을 이루는 객체

JWT 구성요소

  • Header → 토큰 타입(jwt), signature에 사용되는 해싱 알고리즘 등의 정보 포함

  • Payload → key:value 형식의 정보(claim)들로 구성됨

  • Signature → Header + Payload + Server Secret Key 정보를 합하여 암호화(해싱 알고리즘)을 통해 생성된 값

토큰 관리하기

Local Storage

Cookie

토큰 주입하기

Axios Interceptor

PreviousWeek 10Next로그아웃

Last updated 1 year ago