HomeAboutMeBlogGuest
© 2025 Sejin Cha. All rights reserved.
Built with Next.js, deployed on Vercel
RG 프로젝트
RG 프로젝트
RG 프로젝트
RG 프로젝트

RG 프로젝트


💻 프로젝트
개발인원 | 프론트 3명 , 백엔드 5명
개발기간 | 22.07 - 22.08 (3주)
 
🔗  Links
배포 주소 | https://riding-is-good.netlify.app/
FE Git |
Team-Forest-RG-FE
Github
Team-Forest-RG-FE
Owner
prgrms-web-devcourse
Updated
Oct 24, 2022
팀 노션 |
🌳
[팀 05] Forest

서비스 소개

RG 는 쉽고 빠르게 자전거 모임을 주최하고, 참여할 수 있는 서비스 입니다.
기존의 모임 서비스(네이버카페 및 도싸 커뮤니티)가 가지는 문제를 해결하고자 하였습니다.
  • 모임주최자는, 정해진 형식이 없는 자유 게시글 형식으로 게시글 작성에 오랜 시간이 소요된다.
  • 모임참여자는, 내가 참여할만한 자전거 모임을 빠르게 발견하기 어렵다.
RG는 다음과 같은 기능을 통해 문제를 해결합니다.
  1. 쉽고 빠르게 작성할 수 있는 자전거 모집 특화 Form 제공
  1. 다양한 필터를 통한 빠른 모임 검색
  1. 주최/참가한 모임 관리 기능 제공

🛠 기술 스택

  • React | Typescript | Recoil | Storybook

🧑🏽‍💻 담당 역할 및 기능

  • 인증 및 인가, 유저 관련 페이지 담당
  • 팀 내 스크럼 진행 및 노션 문서화 담당

🖥 세부 개발 내용

1. 인증 및 인가

  • 1) 인증 로직 설계
    • AccessToken을 LocalStorage에 저장하는 인증 로직 설계
    • AccessToken을 Recoil State로, RefreshToken을 Cookie에 저장하는 인증 로직으로 리팩토링
      • RefreshToken을 secure, httpOnly 옵션을 설정하여 CSRF 공격을 대비
  • 2) CustomRoute를 통한 로그인 유지 및 인가 처리
    • Route를 감싸고 있는 AuthRoute 구현
      • 페이지 이동 / 새로고침이 발생할 때 마다, 현재 state로 보관 중인 AcessToken의 유효성을 확인
      • API 요청 응답에 따라, 현재 로그인 여부(isAuth)를 update
  • 3) AxiosInterceptor를 활용하여 토큰 refresh 진행
    • AxiosInterceptor의 request 옵션을 통해, 현재 AceessToken을 항상 request Header에 담아 전송
    • AccessToken 만료의 경우, Cookie에 저장된 RefreshToken을 통해 새로운 AccessToken을 발급받고, 기존 request를 다시 요청함
      • RefreshToken 만료 응답이 올 경우, 기존 저장된 Token을 모두 삭제하고 로그인 페이지로 redirect

2. 유저 페이지

  • 1) 유저 정보 수정 및 유저 평가 기능 구현
  • 2) Storybook을 통한 개발 및 컴포넌트 공유

💡 성장 경험

다양한 환경에서의 협업 경험

  • 백엔드 엔지니어와의 협업
  • 온오프라인 환경에서의 협업
  • 1:1 및 다수의 인원이 존재할 때의 협업

👀  서비스 화면

메인화면
메인화면
라이딩 모집 Form
라이딩 모집 Form
 
라이딩 내역/평가 페이지
라이딩 내역/평가 페이지
 
접어두기
https://velog.io/@yaytomato/프론트에서-안전하게-로그인-처리하기

진행상황

  • 추후 진행 userId를 전역상태로 저장 (AuthCheck 후 받은 id로 update)
  • 추후 진행 반응형을 위한 util작성
  • 고민사항
    • 다른 사람 프로필 보는 페이지 간략히 따로 만들지
    • 마이페이지의 기능 독립의 필요성 (현재 userId와 같은지 신경쓸 필요 없다는 점)
      • 보안상, 토큰을 활용하기 때문에 이점
  • 추후진행
    • 라이딩 내역 3개 탭으로 분리
      • 라이딩예정, 라이딩 완료, 개설한 라이딩
  • env관련
    • 옛날 환경변수 적용되고 있음 확인 node환경에서 읽히도록 처리
      • new DotenvPlugin({ systemvars: true }), 브라우저환경에서 읽히도록
      • 요것도 적용안하니 안되었는데, 가능해졌음
      • notion image
    • jwt token 관련 best article (jwt, ractquery, mui, ts)
  • register페이지
    • 레지스터폼임을 안내하는 타이틀 상단에 추가하기
    • post > register 리다이렉트 후 제출 > post 로 이동하지 않는 이슈
  • 카카오 로그인 UI 내리기
  • 닉네임 한글과영어만 가능하도록 프론트 validation
  • bug
    • register안하고 마이페이지 접속 시 에러발생

작업내용

[Epic] 인증 및 인가 by OAuth

전체 흐름

  • 카카오 Oauth 이해하기 > 인증 관련 로컬 상태 저장(by쿠키) > 새로고침 및 토큰 만료 시 업데이트 로직 > 추가 회원가입 정보 입력 > 인가관련 처리
사례
  • 로그인 구현 예제1
      1. 로그인 => 인가코드발급
      1. 인가 코드 => 토큰 발급(access, refresh)
      1. access토큰을 서버로 넘겨 사용자 인증 후 DB 저장 => JWT 토큰 발급
      1. JWT 토큰 localStorage에 저장 후, 자동 로그인 처리
  • 사례2
      1. 프론트앤드에서 카카오 로그인으로 인가코드 요청
      1. 카카오 로그인 & 서비스 이용등록 수락 등록 ->
      1. 리다이렉트 호출(프론트앤드) ->
      1. 인가코드를 이용해 토큰정보 받기(백앤드) ->
      1. 토큰정보를 이용해 유저정보 받기(백앤드) ->
      1. 유저정보 리턴(프론트앤드)

0. Axios 모듈 만들기

  • 서버용 ✅
  • 카카오 oauth용 ✅
    • oauth host가 2개 (kapi.kakao, kauth.kako)로 공통화하지않고 분리하여 사용 ✅

1. 카카오 Oauth 이해하기 ✅

카카오 Oauth 이해하기
  • 전체 소개 > Rest API 단계별 상세 소개

1.5 백엔드와 일정 및 현재 상황 공유 ✅

2. 토큰 로컬 상태 저장 및 쿠키 저장

jwt관리 로직+ 인증 관련 전역 상태 저장 by쿠키 , recoil

3. 이후 작업

kakao server로 직접 요청이 아닌 server 로 간접 요청하기 (API변경 필요)
로그인 성공 시, main 페이지로 redirect
(Adv) 로그인 페이지 진입한 페이지로 redirect
페이지이동/새로고침 시 authUser 호출하여, 토큰 확인 및 인증유지
AuthRoute를 통한 인가 관련 처리
 

[feat] accessToken 만료 시 교체

[Goal]
사용자가 만료된 Token (but refresh는 있는) 으로 요청 시, 요청을 처리하고 새로운 토큰을 받아, 현재 토큰과 교체한다.
사용자가 완전히 만료된 Token일 경우 (refreshToken 만료), 요청을 수행하지 않고, 에러와 함께 로그아웃 처리한다.
[Todo]
요구사항 명확히 파악
response Header로 오는 Token 확인하기
해당 Token으로 전역 토큰 및 localStorage Token 교체
 
[Detail]
요구사항 명확히 파악
  • server API 변수명 교체
    • 현재 accessToken, token, jwt토큰, refesh토큰, refrsh기간 용어 혼재
      • accessToken으로 변수명 합의?
  • 내가 생각한 로직
    • accessToken 만료
      • 403에러 (axisoInterceptor.reponse 확인 후 로직 진행)
        • refesh 요청
          • → 성공(refresh기간 정상): 새로운 accessToken 받고, 이전 요청 진행
            → 실패 (refresh기간 만료): 이전요청 실패 및 로그아웃 처리
 
  • accessToken 정상 , refresh기간 정상
    • 200
  • accessToken 만료 , refresh기간 정상
    • 200 + response Header에 새로운 Token
    • Client에서 교체 작업 필요
  • accessToken 만료 , refresh기간 만료
    • 401
    • Client 에서 로그아웃 처리
  • accessToken 없을때
    • 403
Token교체
  • 언제 교체할 것인가?
    • 매번 responseHeader에 Token 있는지 체크
  • 어디서 교체할 것인가?
    • api단 vs axiosInterceptor
 

이슈리스트

배포환경에서 env 접근 불가 이슈
🍋
배포 환경에서 oauth 관련 이슈

[Epic] 추가 회원가입 정보 Form

 
 

[Epic] 프로필 페이지

1. [feat] 레이아웃 및 목업 데이터 구성

💡
최대한 빠르게 개발해보기! 빠르게 개발해보는 연습
SideNavigation
UserInfo
  • 유저이미지
  • 유저닉네임
  • 자전거종류
  • 매너점수
  • 경력
UserMenu
  • 마이페이지
  • 라이딩 관리
    • 라이딩 현환
    • 라이딩평가
  • 계정 관리
    • 프로필 수정
      • 프로필 이미지
      • 닉네임
      • 자기소개
      •  
      • 실력
      • 경력
      • 자전거종류
      • 선호지역
    • 개인정보 수정
      • 전화번호
      • 카카오계정 이메일
      • (adv) 성별, 나이, …
  • 로그아웃
 

[feat] 마이페이지 라우팅 및 프로필 조회 API 연동

  • 마이페이지 라우팅
  • userId를 전역상태로 가지고 있어야만 가능
 

[feat] 프로필 페이지와 마이페이지의 구분

  • 프로필 페이지
    • 다른 사람의 정보를 볼 수 있다.
  • 마이페이지
    • 전역상태로 관리 중인 userState로만 접근하도록 한다.
[Todo]
SideNavigation에서 UserInfo 컴포넌트의 분리
프로필 페이지 params로 UserInfo 띄우도록 수정
마이페이지 params로 id 받는 부분 제거

2. [feat] 라이딩 현황/상세페이지 기능 구현

[Goal]
  • 라이딩 내역
    • 라이딩예정, 라이딩 완료, 개설 라이딩 3개 탭 구현
  • 라이딩 상세
    • 라이딩 내역 상세 정보 카드 출력
    • 예정 라이딩의 경우 취소 하기
[Todo]
API 요청
Tab 컴포넌트 사용하여 레이아웃 및 Mock 데이터로 라이딩 내역 출력
CardUserAction을 통한 라이딩 상세페이지 이동 및 정보 출력
 
[Detail]
API 요청
  1. 상태별 구분하여 백엔드에서 넘겨주기
response: userId
{ "privacyProfile":{ ... } "ridingProfile" : { ... } "manner" : { ... } "ridings" : { "scheduled" : Riding[] // ridingDate >> new Date() "finished": Riding[] // ridingDate << new Date() "leading": Riding[] // userId === leader.id } }
 
  1. leader.id ridingDate participants.id

3. [feat] 프로필, 개인정보 수정 기능

[Goal]
  • 7가지 동시 수정 가능
    • 프로필이미지, 닉네임 , 자기소개
    • 시작년도, 선호지역, 실력, 자전거 종류
 
[Todo]
문서 확인하기
API 문서
React-hook-form 기본값 가져오기
7가지 인풋을 프로필수정Form 컴포넌트로 만들기
기본 값 불러오기
수정 API 만들기
[Detail]
문서확인하기
    How to change React-Hook-Form defaultValue with useEffect()?
    Thanks for contributing an answer to Stack Overflow! Please be sure to answer the question. Provide details and share your research! Asking for help, clarification, or responding to other answers. Making statements based on opinion; back them up with references or personal experience. To learn more, see our tips on writing great answers.
    https://stackoverflow.com/questions/62242657/how-to-change-react-hook-form-defaultvalue-with-useeffect
     
    수정 버튼을 통한 이미지 등록

    4. [feat] 라이딩 평가 기능 구현

    💡
    추후진행 : 카드에 시작시간 표시
    [Goal]
    • 라이딩 이후 사용자에 대한 평가를 남길 수 있다 라이딩 리더는 노쇼 라이딩 멤버를 기록할 수 있다.
    [Todo]
    API 및 평가 기능 이해
    레이아웃 및 더미 요청을 통한 구현
    API 연동
     
    [Detail]
    API 및 평가 기능 이해
    • 평가체크 여부 체크는 백에서 가능 (필터링에서 보내주기 가능)
    • 사용자 기준 흐름
      • 마이페이지 > 평가메뉴 > 평가가능라이딩 / 평가한 라이딩 탭 조회 api 필요 > 라이딩 카드 > 클릭
      • 평가 가능 라이딩 (라이딩 시작 시간 지나고, 평가 하지 않은 라이딩)
        • 평가페이지(id) 리더orNot 여부에 따라 다른 뷰
          • 해당 라이딩 참가자목록 출력
          • 유저닉네임, 체크박스 (유저닉네임으로 사람구별가능한가? 이름도 아니고..)
          • 평가 API 호출
      • 평가한 라이딩
        • 평가 내역 출력 API <Opt>
    • 조회 API
      • 평가가능 라이딩과 평가한 라이딩을 구별해서 받고 싶음.
        • request : userId
        • response
          • { canEvaluatingRiding, } …이부분 어렵네요.. 평가여부 체크를 어떻게 할건지 잘..
          { postId: 1, leaderId:2 riding: { title: "예정된 라이딩1", thumbnail: "https://res.cloudinary.com/frientrip/image/upload/ar_1:1,c_fill,dpr_2,f_auto,q_auto,w_375/product_banner_1596507027193_337445", ridingLevel: "상", zone: { code: 11010, name: "경기도 성남시 분당구", }, fee: 10000, estimatedTime: "120분", createdAt: "2022-08-17T17:42:37", ridingDate: "2022-08-19T17:42:37", bicycleType: ["MTB"], ridingCourses: ["중앙 공원", "능골 공원", "탑골 공원"], maxParticipant: 5, minParticipant: 2, participants: [ { id: 1, nickname: "testUser", profileImage: "https://programmers.co.kr/assets/icons/apple-icon-6eafc2c4c58a21aef692d6e44ce99d41f999c71789f277317532d0a9c6db8976.png", }, ], }, },
    • 리더 or Not 여부
    • 평가 완료한 라이딩 체크
     
    • 리더 > 멤버 평가, 멤버 > 멤버 평가 같은 API 사용해도 되지 않을까?
      • 같은 페이지, 같은 API 사용
      • 자신이 리더인 게시글에만 노쇼 체크박스가 보이도록 처리
      • 기본값: noshow: false recommened:true
        • request
        • ratingUser: 1, // UserId by Token (나) postId:1, ratedMembers: [ { userId: 2, recommened: true, noshow:false } ]
     
     
     
    레이아웃 및 더미 요청을 통한 구현
    • ✅ 기본적인 Routing 세팅
    • 유저평가폼 레이아웃 잡기
      • mockData 불러와서 레이아웃 구성
        • userId > from state
        • postd > from url
     
    react hook form 데이터 파싱하기
    noshow: true | false
    recommened: “true” | “false” | null
    • “true” ⇒ true
    • “false” ⇒ false
    • null ⇒ true
    before
    { "1 noShow": true, "1 recommended": null, "2 noShow": false, "2 recommended": "false", "3 noShow": false, "3 recommended": "true" }
    After
    [ { memberId: 1, recommended: true, noshow: false }, { memberId: 2, recommended: true, noshow: false }, { memberId: 3, recommended: true, noshow: false } ]
     
    [{}, {}, {}] // 빈값 만들고, before객체 순회 [{noshow:true}] [{noshow:true, recommened:true}] [{noshow:true, recommened:true}, {noshow: false}] [{noshow:true, recommened:true}, {noshow: false, recommended:false}]
     

    5. [feat] 메인페이지

    [Goal]
    • 특정 필터에 맞는 4가지 카드 뿌려주기
    [Todo]
    필터링 할 것 4가지 선정
    4가지 선정
    선정 후 API 어떻게 불러오는 지 확인
    해당 API 로 MockData 구성
    MockData 기반으로 레이아웃 및 디자인 구성
    API 연동
     
    [Detail]
    필터링 4가지 선정
    • 4가지
      • 선호 자전거
      • 선호지역
      • 실력별
      • 랜덤
    • 사용자
      • 선호 자전거 > user.ridingProfile.bicycles [”로드", “MTB”]
      • 선호 지역 > user.ridingProfile.favoriteRegionCode 11
      • 실력별 > user.ridingProfile.level 중
    • 포스트
      • 선호 자전거 > post.riding.bicycleType [”로드", “MTB”]
        • https://rg-server.p-e.kr/api/v1/ridingposts?bicycleCode=2
      • 선호 지역 > post.riding.code 35060
      • 실력별 > post.riding.ridingLevel “상" | “중" | “하"
    • API
      • ✅
        /api/v1/ridingposts
     
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)
    JWT를 쓴다. refreshToken은 secure httpOnly 쿠키로, accessToken은 JSON payload로 전달받는다. 웹 어플리케이션이 mount 될 때 마다 refreshToken을 이용해 새로운 accessToken을 받아와 웹 어플리케이션 내 지역 변수에 저장하고 사용한다. 이 방식으로 CSRF 취약점 공격과 (다른 선택지보다) XSS 공격에서 안전할 수 있다. 하지만 웹 어플리케이션이 XSS 공격에 취약하다면 어떤 방식을 선택하던 보안이 위험하기에 꼭 XSS 처리를 해야 한다.
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)
    https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)
    JWT를 쓴다. refreshToken은 secure httpOnly 쿠키로, accessToken은 JSON payload로 전달받는다. 웹 어플리케이션이 mount 될 때 마다 refreshToken을 이용해 새로운 accessToken을 받아와 웹 어플리케이션 내 지역 변수에 저장하고 사용한다. 이 방식으로 CSRF 취약점 공격과 (다른 선택지보다) XSS 공격에서 안전할 수 있다. 하지만 웹 어플리케이션이 XSS 공격에 취약하다면 어떤 방식을 선택하던 보안이 위험하기에 꼭 XSS 처리를 해야 한다.
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)
    https://velog.io/@yaytomato/%ED%94%84%EB%A1%A0%ED%8A%B8%EC%97%90%EC%84%9C-%EC%95%88%EC%A0%84%ED%95%98%EA%B2%8C-%EB%A1%9C%EA%B7%B8%EC%9D%B8-%EC%B2%98%EB%A6%AC%ED%95%98%EA%B8%B0
    🍪 프론트에서 안전하게 로그인 처리하기 (ft. React)