Skip to content

Daily1Hour/PickMe-Auth-Parcel

Repository files navigation

사용자 관리 마이크로 꾸러미 조각

Single-Spa Parcel 구성

cicd codecov

🚩 목차

🛠️ 기술 스택

Amazon Cognito
Single-SPA Userscript
React React Query React_Hook_Form Jotai
Chakra UI Axios Yup JWT
Vite Rollup.js Terser
ESLint FSDSteiger Prettier TypeScript
Vitest Codecov TypeDoc Postman

💁 소개

이 애플리케이션은 Amazon Cognito를 이용해 사용자 회원가입, 로그인, 로그아웃 등의 인증 기능을 제공합니다.
회원가입 시 이메일 인증을 통해 사용자를 확인하며, 로그인 후에는 액세스 토큰, 리프레시 토큰, ID 토큰을 로컬 스토리지에 저장하여 인증 상태를 유지합니다.
안전하고 신뢰할 수 있는 사용자 인증 흐름을 제공합니다.

🎨 스크린샷


💡 주요 기능

🔐 Amazon Cognito

웹 및 모바일 앱을 위한 자격 증명 플랫폼
사용자 디렉터리, 인증 서버, OAuth 2.0 액세스 토큰 및 자격 증명에 대한 권한 부여 서비스

Cognito 사용자 풀 옵션

  • 사용자 풀 로그인 옵션: 사용자 이름 (아이디)
  • 가입 필수 옵션: 사용자 이름, 이메일

환경변수

VITE_COGNITO_USER_POOL_ID= # Cognito 사용자 풀 아이디
VITE_COGNITO_CLIENT_ID= # Cognito 앱클라이언트 아이디

🗃️ 로컬 스토리지 저장

  • idToken; 클라이언트에서 사용자 정보를 가져올 때 사용
  • accessToken; 백엔드 서비스에 접근할 때 사용
  • refreshToken; 두 토큰의 만료 시 갱신에 사용

📖 개발 문서

🧪 테스트 리포트

테스트 통과 여부와 커버리지 현황은 시각적으로 제공되며, 매 릴리즈 시 자동으로 최신 상태로 반영됩니다.
커버리지는 Codecov를 통해 분석됩니다. codecov

Vitest Codecov
테스트 리포트 바로가기 커버리지 대시보드 바로가기

📘 타입 문서

프로젝트에서 사용되는 타입 정의를 문서화한 자료입니다.
이 타입 문서는 매 릴리즈 업데이트 시 자동으로 최신 상태로 배포됩니다.

TypeDoc
    타입 문서 바로가기   

📐 다이어그램

🧭 시퀀스 다이어그램

sequenceDiagram
    actor User
    participant Frontend
    participant Cognito
    participant API Gateway
    participant Backend

    User ->> Frontend: 로그인 정보 입력
    Frontend ->> Cognito: 인증 요청 (username, password)
    Cognito -->> Frontend: 토큰(ID / Access / Refresh)
    note over Frontend: 토큰(ID / Access / Refresh)은 localStorage에 저장됨
    Frontend ->> Frontend: ID Token을 디코딩해 사용자 정보 추출

    alt Access Token 만료됨
        alt Refresh Token 유효
            Frontend ->> Cognito: 새 Access Token 요청 (Refresh Token)
            Cognito -->> Frontend: 새 Access Token 응답
            Frontend ->> API Gateway: API 요청
        else Refresh Token도 만료됨
            Frontend -->> User: 재로그인 요청
        end
    else Access Token 유효
        loop API 요청 반복
            Frontend ->> API Gateway: API 요청<br>Authorization: Bearer <ID Token><br>X-Access-Token: Bearer <Access Token>
            note right of Frontend: <ID Token>은 인증용, <Access Token>은 인가용으로 전송
            API Gateway ->> Cognito: ID Token 검증 (User Pool Authorizer)
            Cognito -->> API Gateway: 검증 결과 (Claim 포함)
            API Gateway ->> Backend: API 요청 전달<br>Authorization: Bearer <Access Token>
            Backend ->> Backend: Access Token 디코딩 및 권한 확인
            Backend -->> API Gateway: 응답 데이터
            API Gateway -->> Frontend: 응답 데이터
        end
    end
Loading

  1. 프론트엔드는 Cognito SDK를 사용해 사용자 인증을 자체적으로 처리하고, 응답으로 받은 토큰을 저장한다.
  2. 프론트엔드는 ID Token을 디코딩하여 사용자 정보를 활용한다.
  3. Access Token이 만료되면 Refresh Token으로 갱신하고, Refresh Token까지 만료되면 재로그인이 필요하다.
  4. API 요청을 하며 두 토큰을 하나의 요청에 각각 다른 헤더에 담아 전송된다.
    Authorization: Bearer <ID Token>
    X-Access-Token: Bearer <Access Token>
  5. API Gateway는 Cognito User Pool Authorizer를 통해 ID Token으로 사용자를 인증(authentication)한다.
  6. API Gateway는 X-Access-Token을 Authorization 헤더로 덮어써서 백엔드로 전달한다.
  7. 백엔드는 전달받은 Access Token을 디코딩되어 인가(authorization) 처리를 담당한다.

🚚 CI/CD 파이프라인

       GitHubActions GitHub Actions 바로가기

graph LR
    subgraph CD[🚀 CD 영역]
        direction LR
        Tag[태그 푸시] --> DeployGH[gh-pages에 배포] --> |자동 워크플로 실행|pages-build-deployment[GitHub Pages 배포 완료]
        Tag --> DeployAWS[Amazon S3에 배포] --> |콘텐츠 서빙|CloudFront[Amazon CloudFront]
        Tag --> Codecov[CodeCov에<br>테스트 커버리지 배포]
    end

    Build -.-> |📦 아티팩트|Tag

    subgraph CI[🧪 CI 영역]
        direction LR
        Push[브랜치 푸시] --> Lint[린트]
        Lint --> |🟢 통과|Test[테스트]
        Test --> |🟢 통과|Docs[문서화] --> Review
        Test --> |🟢 통과|Build[빌드]
        Build --> |🟢 통과|Review[리뷰]
        Review -->|✔️ 승인|Merge[머지]
    end

    click Build "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/vite-build.yml"
    click Review "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/auto-assign.yml"
    click DeployGH "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/deploy-gh-pages.yml"
    click pages-build-deployment "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/pages/pages-build-deployment"
    click DeployAWS "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/deploy-aws-s3.yml"
    click Codecov "https://github.com/Daily1Hour/PickMe-Auth-Parcel/actions/workflows/deploy-codecov.yml"
Loading

📂 폴더 구조

열기
PickMe-Auth-Parcel
├─ src
│  ├─ main.tsx # 개발 서버 진입점
│  ├─ parcel.tsx # single-spa Parcel 빌드 진입점
│  ├─ app
│  │  └─ App.tsx # 프로바이더 스택
│  ├─ entities # 도메인 모델
│  │  └─ auth
│  │     ├─ index.ts
│  │     ├─ api
│  │     │  └─ dto.ts # dto 모델
│  │     ├─ config
│  │     │  └─ userPool.ts # Cognito 유저풀 정보 및 인스턴스
│  │     ├─ model # 모델 및 유효성 검사
│  │     │  ├─ index.ts
│  │     │  ├─ LoginCredential.ts
│  │     │  └─ SignupCredential.ts
│  │     ├─ repository # 브라우저 데이터 접근
│  │     │  └─ getLoggedIn.ts
│  │     └─ service # 유즈케이스
│  │        ├─ index.ts
│  │        ├─ login # 로그인
│  │        │  ├─ login.ts
│  │        │  │  ├─ login.test.ts
│  │        │  │  └─ login.usage.ts
│  │        │  ├─ forgotPassword.ts
│  │        │  │  ├─ forgotPassword.test.ts
│  │        │  │  └─ forgotPassword.usage.ts
│  │        │  └─ resetPassword.ts
│  │        ├─ session # 토큰 사용
│  │        │  ├─ getTokens.ts
│  │        │  │  ├─ getTokens.test.ts
│  │        │  │  └─ getTokens.usage.ts
│  │        │  └─ getUser.ts
│  │        └─ signup # 회원가입
│  │           ├─ signup.ts
│  │           │  ├─ signup.test.ts
│  │           │  └─ signup.usage.ts
│  │           └─ confirm.test.ts
│  │              └─ confirm.ts
│  ├─ features # 기능 구현체
│  │  ├─ authActions # 로그인/회원가입 기능
│  │  │  ├─ index.ts
│  │  │  ├─ api # 쿼리
│  │  │  │  ├─ index.ts
│  │  │  │  ├─ useLoginFetch.ts
│  │  │  │  ├─ useForgotPasswordFetch.ts
│  │  │  │  ├─ useResetPasswordFetch.ts
│  │  │  │  ├─ useSignupFetch.ts
│  │  │  │  └─ useConfirmFetch.ts
│  │  │  ├─ atom # 상태저장소
│  │  │  │  ├─ index.ts
│  │  │  │  └─ actionTypeAtom.ts
│  │  │  ├─ hook # 폼 커스텀훅
│  │  │  │  ├─ index.ts
│  │  │  │  ├─ useLoginForm.ts
│  │  │  │  ├─ useForgotPasswordForm.ts
│  │  │  │  ├─ useResetPasswordForm.ts
│  │  │  │  ├─ useSignupForm.ts
│  │  │  │  └─ useConfirmForm.ts
│  │  │  ├─ model # 스키마
│  │  │  │  ├─ index.ts
│  │  │  │  ├─ LoginSchema.ts
│  │  │  │  ├─ ForgotPasswordSchema.ts
│  │  │  │  ├─ ResetPasswordSchema.ts
│  │  │  │  ├─ SignupSchema.ts
│  │  │  │  └─ ConfirmSchema.ts
│  │  │  └─ ui
│  │  │     ├─ index.ts
│  │  │     ├─ forms
│  │  │     │  ├─ Field.tsx # 필드
│  │  │     │  ├─ Layout.tsx # 폼 레이아웃
│  │  │     │  ├─ LoginForm.tsx # 로그인 폼
│  │  │     │  ├─ ForgotPasswordForm.tsx # 비밀번호 찾기 폼
│  │  │     │  ├─ ResetPasswordForm.tsx # 비밀번호 리셋 폼
│  │  │     │  ├─ SocialLoginForm.tsx # 소셜로그인 폼
│  │  │     │  ├─ SignupForm.tsx # 회원가입 폼
│  │  │     │  └─ ConfirmForm.tsx # 회원가입 인증 폼
│  │  │     ├─ ActionLayout.tsx # 액션 레이아웃
│  │  │     └─ PopoverLayout.tsx # 팝오버 레이아웃
│  │  └─ userMenu # 로그인 인증 후 사용자메뉴 기능
│  │     ├─ index.ts
│  │     ├─ api
│  │     │  ├─ index.ts
│  │     │  ├─ useLoggedIn.ts
│  │     │  └─ useUserInfo.ts
│  │     └─ ui
│  │        └─ UserMenu.tsx
│  ├─ pages # 페이지
│  │  └─ auth
│  │     ├─ index.tsx
│  │     ├─ hook
│  │     │  └─ useTokens.ts
│  │     └─ ui
│  │        ├─ index.ts
│  │        ├─ AuthControls.tsx # 로그인/회원가입 컨트롤
│  │        └─ TokenInfo.tsx # 로그인 후 토큰 정보
│  ├─ shared # 공용
│  │  ├─ ActionType.ts
│  │  ├─ theme.ts
│  │  ├─ trans-ko.ts
│  │  ├─ styles
│  │  │  ├─ global.css
│  │  │  └─ index.js
│  │  └─ ui
│  │     ├─ atoms
│  │     │  ├─ index.ts
│  │     │  ├─ ButtonBackground.tsx
│  │     │  └─ StyledButton.tsx
│  │     ├─ index.ts
│  │     └─ molecules
│  │        ├─ index.ts
│  │        ├─ CircleButton.tsx
│  │        ├─ PrimaryButton.tsx
│  │        └─ SecondaryButton.tsx
│  ├─ third-party
│  │  └─ chakra-ui
│  └─ userscript # 유저스크립트
│     ├─ widget.meta.ts # 메타데이터
│     └─ widget.user.js # 스크립트
├─ tsconfig.json # ts 설정
│  ├─ tsconfig.app.json
│  ├─ tsconfig.node.json
│  └─ typedoc.json # 문서화 설정
├─ package.json # 의존성 설정
│  ├─ .prettierrc # 포맷터 설정
│  ├─ eslint.config.js # 린트 설정
│  └─ steiger.config.ts # FSD 린트 설정
└─ vite.config.ts # Vite 설정 파일
   └─ vite-env.d.ts # 환경변수 타입 정의

🚀 실행 방법

💻 개발 서버 실행

$ npm install
$ npm run dev

🧬 Single-SPA 주입

  1. 빌드
  2. 배포 또는 프리뷰
  3. 배포 주소를 parcelURL로 사용하여 다른 애플리케이션에 주입

애플리케이션 빌드

$ npm install
$ npm run build

Parcel 컴포넌트 주입 방법

  • React

    import Parcel from "single-spa-react/parcel";
    
    export function myComponent(): React.ReactElement {
        const [parcelConfig, setParcelConfig] = useState<any>(null);
    
        useEffect(() => {
            const loadParcel = async () => {
                const { parcel: config } = await import(parcelURL);
                setParcelConfig(config);
            };
            loadParcel();
        }, []);
    
        return parcelConfig ? <Parcel config={parcelConfig} /> : <div>Loading...</div>;
    }
  • Vue

    <template>
      <Parcel
        :config="parcelConfig"
        :mountParcel="mountParcel"
      />
    </template>
    
    <script lang="ts">
    import Parcel from "single-spa-vue/parcel";
    import { mountRootParcel } from "single-spa";
    
    export default {
        components: { Parcel },
        data() {
            return {
                parcelConfig: import(parcelURL).then((module) => module.parcel),
                mountParcel: mountRootParcel,
            };
        },
    };
    </script>
  • Svelte

    <script lang="ts">
        import { onMount } from "svelte";
        import { mountRootParcel } from "single-spa";
    
        let container: HTMLDivElement;
    
        onMount(() => {
            let parcel: any;
            const loadParcel = async () => {
                const { parcel: parcelConfig } = await import(parcelURL);
    
                parcel = mountRootParcel(parcelConfig, {
                    domElement: container,
                });
            };
            loadParcel();
    
            return () => {
                if (parcel) {
                    parcel.unmount();
                }
            };
        });
    </script>
    
    <div bind:this={container}></div>

Parcel 유틸리티 함수 사용 방법

  • getTokens 함수
    현재 로그인되어있는 사용자의 토큰 3종을 읽어옵니다.
    이 기능은 동일 도메인에서 로그인되어 있어야 작동합니다.

    const { getTokens } = await import(parcelURL);
    
    const { idToken, accessToken, refreshToken } = await getTokens();

📜 유저스크립트

@pickme/auth를 위젯 형태로 페이지에 삽입하여,
사용자 관리 애플리케이션을 페이지에 통합할 필요 없이 개발 페이지에서도 사용할 수 있도록 합니다.

  1. 유저 스크립트 관리자 설치

  2. 스크립트 다운로드 (스크립트 관리자를 설치했으면 자동으로 감지합니다.)

    Download

  3. 개발 서버로 열린 http://localhost 도메인에서 자동으로 @pickme/auth가 페이지에 삽입됩니다.

About

사용자 관리 마이크로 꾸러미 조각

Topics

Resources

Stars

Watchers

Forks