Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
211 changes: 211 additions & 0 deletions keyword/chapter04/keyword.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,211 @@
# 인증 vs 인가

## 인증

### 인증이란 무엇인가요

- 인증은 사용자가 누구인지 확인하는 방식입니다.애플리케이션에서 사용자의 신분을 확인하고 특정 리소스에 대한 접근을 제어하는 과정을 인증이라고도 합니다.

### 인증의 방법과 예시

- 인증에는 흔히 4가지의 방식이 있는데

1. 아이디와 비밀번호

- 회원가입할 때 적은 정보를 데이터베이스에 저장해 로그인할 때 확인하는 방법입니다.

2. 소셜 로그인

- 구글이나 네이버 등에서 회원가입했던 정보를 통해 신원을 보증받습니다.

3. 2단계 인증

- 휴대폰 문자나 이메일 인증을 통해 추가적으로 인증을 받습니다.

4. 생체 인증

- 지문이나 홍채인식, 얼굴인식 등 생체적 정보를 통해 로그인하는 방식을 말합니다.

### 인증의 위치

- 인증은 프론트엔드에서 로그인 화면을 통해 인증을 시도합니다.
- 아이디와 비밀번호가 정상적으로 확인되면 세션이나 토큰을 발급해서 인증된 사용자임을 확인할 수 있습니다.

## 인가

### 인가란 무엇일까요

- 인가(Authorization)는 인증된 사용자가 특정 리소스에 접근할 수 있는지 권한을 부여하는 절차입니다. 예를 들어, ID와 비밀번호를 입력해 로그인하는 것이 인증이고, 로그인 후 특정 게시물을 읽거나 댓글을 다는 등의 행위는 인가에 해당합니다.

### 인가의 방법과 예시

- 3가지 방식이 있습니다.

1. 역할 기반

- 사용자에게 역할을 부여해서 제한된 권한만 사용 가능하게 만듭니다.

2. 속성 기반

- 나이, 소속, 결제 상태등 특정 속성에 따라 권한을 부여합니다.

3. 정책 기반

- 시간과 공간에 따라 권한을 부여합니다. 예시=> 회사 와이파이

### 인가의 위치

- 인가는 보통 라우팅 단계에서 나타납니다.

## 인증과 인가의 차이

- 앞서 설명했던 것과 같이 인증은 누구인지, 인가는 무엇을 할 수 있는지를 물어봅니다.
- 인증은 로그인 전, 인가는 로그인 후이며 오류코드 또한 401, 403으로 다릅니다.

## 인증(Authentication), 인가(Authorization)의 흐름 도식화

- 도식화를 보면 이해되는 바는 다음과 같습니다.

1. 사용자 요청 (예: /mypage, /admin, /api/posts 등)
2. 인증

- 사용자가 누구인지 확인합니다.(아이디 비번검사, OAuth 검증 등)

3. 인가

- 사용자가 무슨 권한을 가지고 있는지 확인합니다.(권한이 없을때는 403 error)
이러한 순서대로 흐름이 이어진다는 것을 알 수 있습니다.

# JWT vs Session

## JWT

### JWT 핵심 개념

- 사용자가 로그인하면 서버가 토큰을 발급하는데, 클라이언트는 이후 모든 API요청을 이 토큰의 헤더에 붙여서 보냅니다.
- Authorization: Bearer <AccessToken>
- 서버는 토큰안에 있는 정보만 확인합니다.

### JWT 장점

1. 확장성

- 서버가 세션을 기억할 필요 X 서버 확장성 증가

2. 서비스 간 공유 용이

- 마이크로 서비스 환경에서도 동일한 토큰을 전달해서 인증 공유

3. 클라이언트 친화적

- 웹 모바일 등 여러 종류의 클라이언트에서 같은 방식을 사용할 수 있습니다.

### JWT(JSON Web Token) 단점과 주의사항

- JWT는 토큰이 생명이므로 토큰이 유출되어버리면 심각한 보안 문제가 발생할 수 있습니다.
- 토큰은 만료기간이 정해져 있으므로 즉시 무효화가 어렵습니다. 이를 해결하기 위해서 짧은 수명의 토큰인 AccessToken을 발급하거나 RefreshToken을 함께 발급하고 주기적으로 회전하거나 서버에서 블랙리스트를 관리해 강제로 만료시킵니다.

### JWT(JSON Web Token) 인증 클라이언트 흐름

1. 로그인 요청-> 토큰을 생성
2. API 요청-> 클라이언트가 Authorization: Bearer <AccessToken>헤더를 붙여 요청
3. 토큰 만료 대응-> AccessToken을 새로 발급, 리액트에선 axios interceptor을 사용해 로직을 자동화
4. 로그아웃 처리-> 서버에 RefreshToken 무효화 요청, 클라이언트에 저장된 토큰 제거

# 토큰은 무엇인가

## Basic Token

- Basic Token(베이직 토큰)이란 클라이언트가 사용자 이름과 비밀번호를 Base-64로 인코딩하여 Authorization 헤더에 포함해 서버로 전송하는 HTTP 기본 인증 방식을 의미합니다.
- 단순하지만 보안상 위험하기 때문에 자주 사용되지는 않고 테스트 형식으로만 사용됩니다.

## Bearer Token

- Authorization: Bearer <토큰> 형태로 토큰을 헤더에 담아 전송하는 방식입니다.
- 현재 가장 널리 사용되는 방식이고, "이 토큰의 소유자에게 접근 권한을 부여하라"는 의미를 가집니다

## Access Token vs Refresh Token

- Access Token는 수명이 짧으므로 사용자 인증/인가 확인용도로 사용됩니다.
- Refresh Token은 Access Token이 만료되었을 때 새로운 Access Token을 발급받기 위해 사용됩니다. Refresh Token은 수명이 길고 서버가 탈취를 감지하면 바로 폐기할 수도 있습니다.
- 동작 흐름으로는 로그인->API 요청-> Access Token만료-> Refresh Token 사용->재로그인 필요 순으로 진행됩니다.

# 클라이언트 저장소 전략

## 쿠키

- 쿠키는 사용자의 웹 브라우저에 저장되는 작은 텍스트 파일로 브라우저가 자동으로 서버에 전송합니다.
- 만료일을 설정 가능하고, 도메인/경로 제한이 가능합니다.
- HttpOnly,Secure등 보안 속성을 제공합니다.
- 보안성이 높지만 자동전송이기에 CSRF공격에 위험이 있습니다.

## 로컬 스토리지

- 로컬 스토리지는 쿠키와 다르게 브라우저에 반 영구적으로 저장되고, 저장용량이 쿠키에 비해 큽니다.
- 쿠키처럼 자동으로 전송되지 않고 직접 꺼내 쓰는 방식을 사용합니다.
- 사용하고 쉽고 간단하지만 XSS공격에 취약하다는 단점을 가지고 있습니다.

## 세션 스토리지

- 브라우저 탭 단위 저장소로 탭이나 창을 닫게되면 자동으로 삭제됩니다.
- 자동으로 삭제되어서 보안적으로는 나을 수 있으나 로그인이 유지가 안되어서 불편함을 겪을 수 있습니다. 이 또한 로컬 스토리지와 비슷하게 XSS공격에 취약합니다.

## 저장소 전략 비교

- 쿠키는 브라우저가 관리하는 작은 텍스트 파일로, 서버가 응답할 때 Set-Cookie 헤더로 발급하고 브라우저가 이후 동일 도메인 요청에 자동으로 포함하여 전송합니다. 쿠키는 용량이 작아(대략 수 KB) 많은 데이터를 담기엔 적합하지 않지만, HTTP 요청과 함께 자동 전송된다는 특징 때문에 세션 유지(로그인 상태 등)에 자주 사용됩니다. 보안 관련 설정으로 HttpOnly(JavaScript 접근 금지), Secure(HTTPS에서만 전송), SameSite(타 도메인 요청에 대한 전송 규칙) 등을 지정할 수 있어, 적절히 설정하면 XSS나 CSRF 위험을 완화할 수 있습니다. 다만 자동 전송 특성 때문에 CSRF 공격에 취약할 수 있으므로 CSRF 토큰 또는 SameSite 정책을 병행하는 것이 중요합니다.
- 로컬스토리지는 도메인별로 브라우저에 키-값 형태로 데이터를 저장하는 클라이언트 측 저장소입니다. 저장 용량은 쿠키보다 훨씬 크고(수 MB 수준), 브라우저를 닫아도 데이터가 유지되므로 사용자 설정, UI 상태, 캐시된 데이터 등을 보관하기에 편리합니다. 하지만 로컬스토리지는 JavaScript에서 자유롭게 접근 가능하므로 XSS 공격에 매우 취약합니다. 또한 로컬스토리지에 저장한 데이터는 HTTP 요청에 자동으로 포함되지 않으므로 서버로 전송하려면 명시적으로 붙여 보내야 합니다. 따라서 민감한 인증 토큰(특히 장기 보관용)은 로컬스토리지 저장을 피하는 것이 권장됩니다.
- 세션스토리지는 로컬스토리지와 비슷하지만 ‘탭 단위’로 격리된 저장소입니다. 같은 브라우저의 새 탭을 열면 데이터가 복제되지 않으며, 탭을 닫으면 데이터가 자동 삭제됩니다. 따라서 탭 단위의 임시 상태(예: 멀티탭 동작 중 유지해야 하는 폼 데이터, 임시 작업 상태) 관리에 유용합니다. 세션스토리지도 JavaScript로 접근 가능하므로 XSS에 취약하며 새로고침에는 유지되지만 탭을 닫으면 사라진다는 특성을 고려해야 합니다.

# Custom Hook을 왜 사용하는가?

## Custom Hook을 왜 사용할까?

- 커스텀 훅은 React의 내장 훅(useState, useEffect, useRef 등)을 조합하여 특정 기능을 모듈화하고 재사용할 수 있도록 만든 함수입니다.
- 아래 5가지의 이유로 사용됩니다.

1. 코드 중복 제거
2. 관심사 분리
3. 유지보수 용이
4. 테스트 용이 -> UI가 없이도 가능
5. 명시적 의존성 -> 훅의 의존성 배열을 통해 “언제 실행/정리할지”를 선언적으로 관리

## Custom Hook은 어디에 보관할까?

- src/hooks/안에 넣어 사용한다. 반드시 그래야하는 것은 아니지만 일반적으로 여기에 넣는다.

## Custom Hook 기본 문법

- import { useState } from "react";

const ToggleExample = () => {
const [isOpen, setIsOpen] = useState(false);

return (
<div>
<h1>{isOpen ? "열림" : "닫힘"}</h1>
<button onClick={() => setIsOpen(!isOpen)}>토글</button>
</div>
);
};

export default ToggleExample;

- 훅의 이름을 지을 때는 반드시 use가 앞에 붙는다.

## Custom Hook 직접 만들어보기

- 보통 토글을 구현할 때는 useState만 단독으로 사용했지만 이는 토글 로직이 중복되고 재사용이 어렵다는 문제가 있었습니다. 커스텀 훅으로 이를 개선할 수 있습니다. 반복되는 로직을 useToggle이라는 Custom Hook으로 분리하면 훨씬 깔끔해집니다.
import useToggle from "../hooks/useToggle";

const ToggleExample = () => {
const [isOpen, toggle] = useToggle(false);

return (
<div>
<h1>{isOpen ? "열림" : "닫힘"}</h1>
<button onClick={toggle}>토글</button>
</div>
);
};

export default ToggleExample;
32 changes: 32 additions & 0 deletions mission/chapter04-1/my-app/.gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
# Logs
logs
*.log
npm-debug.log*
yarn-debug.log*
yarn-error.log*
pnpm-debug.log*
lerna-debug.log*

# Dependencies
node_modules
dist
dist-ssr
*.local

# Editor directories and files
.vscode/*
!.vscode/extensions.json
.idea
.DS_Store
*.suo
*.ntvs*
*.njsproj
*.sln
*.sw?

# Environment files
.env
.env.local
.env.development.local
.env.test.local
.env.production.local
73 changes: 73 additions & 0 deletions mission/chapter04-1/my-app/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,73 @@
# React + TypeScript + Vite

This template provides a minimal setup to get React working in Vite with HMR and some ESLint rules.

Currently, two official plugins are available:

- [@vitejs/plugin-react](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react) uses [Babel](https://babeljs.io/) for Fast Refresh
- [@vitejs/plugin-react-swc](https://github.com/vitejs/vite-plugin-react/blob/main/packages/plugin-react-swc) uses [SWC](https://swc.rs/) for Fast Refresh

## React Compiler

The React Compiler is not enabled on this template because of its impact on dev & build performances. To add it, see [this documentation](https://react.dev/learn/react-compiler/installation).

## Expanding the ESLint configuration

If you are developing a production application, we recommend updating the configuration to enable type-aware lint rules:

```js
export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...

// Remove tseslint.configs.recommended and replace with this
tseslint.configs.recommendedTypeChecked,
// Alternatively, use this for stricter rules
tseslint.configs.strictTypeChecked,
// Optionally, add this for stylistic rules
tseslint.configs.stylisticTypeChecked,

// Other configs...
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```

You can also install [eslint-plugin-react-x](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-x) and [eslint-plugin-react-dom](https://github.com/Rel1cx/eslint-react/tree/main/packages/plugins/eslint-plugin-react-dom) for React-specific lint rules:

```js
// eslint.config.js
import reactX from 'eslint-plugin-react-x'
import reactDom from 'eslint-plugin-react-dom'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
// Other configs...
// Enable lint rules for React
reactX.configs['recommended-typescript'],
// Enable lint rules for React DOM
reactDom.configs.recommended,
],
languageOptions: {
parserOptions: {
project: ['./tsconfig.node.json', './tsconfig.app.json'],
tsconfigRootDir: import.meta.dirname,
},
// other options...
},
},
])
```
23 changes: 23 additions & 0 deletions mission/chapter04-1/my-app/eslint.config.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
import js from '@eslint/js'
import globals from 'globals'
import reactHooks from 'eslint-plugin-react-hooks'
import reactRefresh from 'eslint-plugin-react-refresh'
import tseslint from 'typescript-eslint'
import { defineConfig, globalIgnores } from 'eslint/config'

export default defineConfig([
globalIgnores(['dist']),
{
files: ['**/*.{ts,tsx}'],
extends: [
js.configs.recommended,
tseslint.configs.recommended,
reactHooks.configs['recommended-latest'],
reactRefresh.configs.vite,
],
languageOptions: {
ecmaVersion: 2020,
globals: globals.browser,
},
},
])
13 changes: 13 additions & 0 deletions mission/chapter04-1/my-app/index.html
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
<!doctype html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>my-app</title>
</head>
<body>
<div id="root"></div>
<script type="module" src="/src/main.tsx"></script>
</body>
</html>
Loading