{"id":28812547,"url":"https://github.com/tpdlshdmlrkfmcla/frontend_auth","last_synced_at":"2026-05-05T17:33:49.705Z","repository":{"id":223010546,"uuid":"754959608","full_name":"tpdlshdmlrkfmcla/Frontend_Auth","owner":"tpdlshdmlrkfmcla","description":"[React] Fronend Authentication 프론트엔드 인증 구현","archived":false,"fork":false,"pushed_at":"2024-02-23T18:09:27.000Z","size":522,"stargazers_count":0,"open_issues_count":0,"forks_count":0,"subscribers_count":1,"default_branch":"master","last_synced_at":"2026-04-06T08:48:43.159Z","etag":null,"topics":["react-router","react-router-dom","reactjs"],"latest_commit_sha":null,"homepage":"","language":"JavaScript","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":null,"status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/tpdlshdmlrkfmcla.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":null,"code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null,"dei":null,"publiccode":null,"codemeta":null}},"created_at":"2024-02-09T05:39:40.000Z","updated_at":"2024-02-17T16:17:50.000Z","dependencies_parsed_at":"2024-12-31T05:29:17.224Z","dependency_job_id":"4d1975a4-ae8c-4e99-ba13-daa995eeec31","html_url":"https://github.com/tpdlshdmlrkfmcla/Frontend_Auth","commit_stats":null,"previous_names":["chihyeonwon/frontend_auth","chihyunwon/frontend_auth","mr-won/frontend_auth","user20252228/frontend_auth","tpdlshdmlrkfmcla/frontend_auth"],"tags_count":0,"template":false,"template_full_name":null,"purl":"pkg:github/tpdlshdmlrkfmcla/Frontend_Auth","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpdlshdmlrkfmcla%2FFrontend_Auth","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpdlshdmlrkfmcla%2FFrontend_Auth/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpdlshdmlrkfmcla%2FFrontend_Auth/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpdlshdmlrkfmcla%2FFrontend_Auth/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/tpdlshdmlrkfmcla","download_url":"https://codeload.github.com/tpdlshdmlrkfmcla/Frontend_Auth/tar.gz/refs/heads/master","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/tpdlshdmlrkfmcla%2FFrontend_Auth/sbom","scorecard":null,"host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":286080680,"owners_count":32660392,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2026-05-05T11:29:49.557Z","status":"ssl_error","status_checked_at":"2026-05-05T11:29:48.587Z","response_time":54,"last_error":"SSL_connect returned=1 errno=0 peeraddr=140.82.121.5:443 state=error: unexpected eof while reading","robots_txt_status":"success","robots_txt_updated_at":"2025-07-24T06:49:26.215Z","robots_txt_url":"https://github.com/robots.txt","online":false,"can_crawl_api":true,"host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["react-router","react-router-dom","reactjs"],"created_at":"2025-06-18T14:41:18.956Z","updated_at":"2026-05-05T17:33:49.685Z","avatar_url":"https://github.com/tpdlshdmlrkfmcla.png","language":"JavaScript","funding_links":[],"categories":[],"sub_categories":[],"readme":"## react-router-dom\n```\n구현하고자 하는 애플리케이션은 서버 사이드 렌더링이 아닌 클라이언트 사이드 라우팅은 한 페이지에서만 동작하는 싱글 페이지 애플리케이션 SPA이다.\n서버로 어떤 요청도 날리지 않고 모든 라우팅은 클라이언트 코드, 즉 자바스크립트가 해결한다.\nhttp://localhost:3000/login url을 치면 리액트 라우터가 이를 가로채고 url을 해석해 Login 컴포넌트를 렌더링한다.\n인터넷이 끊기더라도 Login 페이지를 가져오는 요청은 실행된다. 보편적인 브라우저가 작동하는 방식인 서버사이드 라우팅과는 다르다.\n이 프로젝트에서는 클라이언트 사이드 라우팅 라이브러리인 react-router-dom을 사용한다.\n```\n#### react-router-dom 설치\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/13e76f7d-c00b-462e-acaa-48c2de9e883b)\n```\n리액트 애플리케이션이 존재하는 경로로 들어가서 react-router-dom을 설치한다.\n```\n#### Login Component\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/dff8102c-0b7e-4838-82b3-d585687df177)\n```\nhttp://localhost:3000/login에서 렌더링할 컴포넌트인 Login 컴포넌트를 작성한다.\n```\n#### AppRouter Component\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/53b99e0c-b3aa-4706-a0f7-543287920072)\n```\nlocalhost:3000/login 경로는 \u003cRoute path=\"login\u003e element={\u003cLogin /\u003e} /\u003e와 같이 컴포넌트를 선언하여 실제 경로를\n지정해준다.\n\u003cRoutes\u003e는 여러 개의 \u003cRoute\u003e를 관리하고 실제로 갖아 적합한 \u003cRoute\u003e를 찾아주는 컴포넌트이다.\n```\n#### index.js 수정\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/1385eaf7-7085-4f05-a97e-4ea35751fd1e)\n```\n가장 처음 렌더링되는 컴포넌트가 AppRouter 컴포넌트가 되도록 수정한다.\n```\n#### 로그인 페이지 테스팅\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/ae797354-cae6-45ba-aba2-dc28ba1acd40)\n```\n프론트앤드를 재시작하고 http://localhost:3000/login 페이지로 들어가면 로그엔 페이지 문구가 뜬다면\n라우팅이 제대로 동작하는 것을 알 수 있다.\n```\n#### ApiService: 403 redirect\n\n```\n\n```\n## Login 페이지\n```\n로그인 API 서비스는 /auth/signin 경로였다. 이 경로를 이용해 로그인하는 메서드를 ApiService.js에 작성한다.\n로그인 서비스 함수들을 ApiService.js에 작성한다.\n```\n#### ApiService: signin 함수\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/6d33adcc-a40a-40a6-b2fb-f641e8c7e475)\n```\nApiService의 call 메서드는 fetch 함수를 부른다. fetch를 이용하면 API 콜을 한 후, .then을 이용해 HTTP 응답을 받아온다.\n이때 받아온 HTTP의 응답의 Status 값이 200이라면 정상, 403이라면 인증에 실패한 것이다. 403인 경우 Login 화면으로 리디렉트하는\n로직을 작성해주었다.\n```\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/ef654f76-40a7-4ec7-996a-e8bcabdf3598)\n```\nhttp://localhost:3000으로 접근하면 login 페이지로 리디렉트되는 것을 확인할 수 있다.\n```\n#### Login.js\n```javascript\nconst Login = () =\u003e {\n  const handleSubmit = event =\u003e {\n    event.preventDefault();\n    const data = new FormData(event.target);\n    const username = data.get(\"username\");\n    const password = data.get(\"password\");\n    // ApiService의 signin 메서드를 사용 해 로그인.\n    signin({ username: username, password: password });\n  };\n\n  return (\n    \u003cContainer component='main' maxWidth='xs' style={{ marginTop: \"8%\" }}\u003e\n      \u003cGrid container spacing={2}\u003e\n        \u003cGrid item xs={12}\u003e\n          \u003cTypography component='h1' variant='h5'\u003e\n            로그인\n          \u003c/Typography\u003e\n        \u003c/Grid\u003e\n      \u003c/Grid\u003e\n      \u003cform noValidate onSubmit={handleSubmit}\u003e\n        {\" \"}\n        {/* submit 버튼을 누르면 handleSubmit이 실행됨. */}\n        \u003cGrid container spacing={2}\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cTextField\n              variant='outlined'\n              required\n              fullWidth\n              id='username'\n              label='아이디'\n              name='username'\n              autoComplete='username'\n            /\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cTextField\n              variant='outlined'\n              required\n              fullWidth\n              name='password'\n              label='패스워드'\n              type='password'\n              id='password'\n              autoComplete='current-password'\n            /\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cButton type='submit' fullWidth variant='contained' color='primary'\u003e\n              로그인\n            \u003c/Button\u003e\n          \u003c/Grid\u003e\n        \u003c/Grid\u003e\n      \u003c/form\u003e\n    \u003c/Container\u003e\n  );\n};\n```\n```\n로그인 컴포넌트는 이메일과 패스워드를 받는 인풋 필드, 로그인 필드로 이루어져 있다.\n사용자가 이메일과 패스워드를 입력한 후 로그인 버튼을 누르면 작성한 ApiService의 signi 메서드를 이용해서\n백엔드의 /auth/singin으로 요청이 전달된다.\n```\n#### 수정한 로그인 페이지 테스팅\n#### Postman으로 유저 생성\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/1b52dbda-6b9c-4d51-848d-42971ee4e385)\n#### 생성한 아이디, 비밀번호로 로그인 페이지에서 로그인 시도\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/a9fdbf57-c5da-4510-b8ff-3a122825994b)\n#### 로그인 성공 (Token alert 메시지 반환)\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/6aa19a11-b357-4b3c-88c5-68bf1b8c69b5)\n```\nPOSTMAN으로 새 유저를ㄹ 만든 후 login 페이지에서 로그인을 시도하여 로그인에 성공하면 alert 메시지에\n토큰이 기록되는 것을 확인할 수 있다.\n```\n## 로그인 성공\n#### 로그인 성공 시 메인 화면으로 리디렉트 ApiService: signin 함수 수정\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/e7f6311c-fea2-4f31-bec2-027d7dd4c327)\n```\n로그인에 성공한 경우에는 Todo 리스트가 있는 화면으로 돌아가야 한다. 따라서 토큰이 존재하는 경우 Todo 리스트 화면인\nlocalhost:3000/으로 돌아가는 로직을 작성해야 한다. ApiService의 signin 함수를 수정한다.\n```\n\n## 로컬 스토리지를 이용한 액세스 토큰 관리\n\n#### 로컬 스토리지 실습\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/12a81c1b-d198-4adf-baf7-cd56253dfab6)\n```\n브라우저의 개발자 도구에서 콘솔에 다음 코드를 작성하여 로컬 스토리지에 아이템을 저장하고 원하는 아이템을 가져왔다.\n```\n#### 스토리지 탭\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/e2d003ea-a2e7-4fa2-aba7-28fdaa605a47)\n```\nApplication의 Local Storage를 확인할 수 있다.\n주소 경로마다 따로 저장된다. 따라서 다른 도메인의 자바스크립트는 다른 도메인의 로컬 스토리지를 읽지는 못한다. \n```\n#### 액세스 코드 저장\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/7f26c757-cc23-45cf-960a-c40cb61afa78)\n```\n로그인 시 받은 토큰을 로컬 스토리지에 저장하기 위해서 ApiService의 signin 함수를 다음과 같이 수정한다.\n```\n#### ApiService.js: 엑세스 토큰 헤더에 추가\n\n```javasciprt\nexport function call(api, method, request) {\n  let headers = new Headers({\n    \"Content-Type\": \"application/json\",\n  });\n\n  // 로컬 스토리지에서 ACCESS TOKEN 가져오기\n  const accessToken = localStorage.getItem(\"ACCESS_TOKEN\");\n  if (accessToken \u0026\u0026 accessToken !== null) {\n    headers.append(\"Authorization\", \"Bearer \" + accessToken);\n  }\n\n  let options = {\n    headers: headers,\n    url: API_BASE_URL + api,\n    method: method,\n  };\n  if (request) {\n    // GET method\n    options.body = JSON.stringify(request);\n  }\n  return fetch(options.url, options)\n    .then(response =\u003e {\n      if (response.status === 200) {\n        return response.json();\n      } else if (response.status === 403) {\n        window.location.href = \"/login\"; // redirect\n      } else {\n        new Error(response);\n      }\n    })\n    .catch(error =\u003e {\n      console.log(\"http error\");\n      console.log(error);\n    });\n}\n```\n```\n모든 API의 헤더에 액세스 토큰을 추가하는 부분을 구현한다.\ncall에 토큰이 존재하는 경우 헤더에 추가하는 로직을 작성한다.\n```\n#### 로그인 성공 시 메인화면으로 라우팅\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/f8771255-a3e3-4272-b52d-eb3d0e06d7cf)\n```\nAPI 콜을 할 때마다 로컬 스토리지에서 토큰을 가져와서 헤더에 포함시킨다. 올바른 토큰이므로 백엔드는 인증에 성공하고\n메인 화면으로 정상적으로 라우팅될 수 있게 되었다.\n```\n## 로그아웃과 글리치 해결\n\n#### 로그아웃 서비스\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/48dab3a8-3ad8-4370-88d8-eb83e1009341)\n```\nApiService에 signout 함수를 작성한다. 로컬스토리지에 있는 액세스 토큰을 모두 null 값으로 만드는 로직이다.\n```\n#### App 컴포넌트에 네비게이션 바 추가\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/5f6481ef-f30c-4583-aa9c-3cd89cffd1a5)\n```\nmaterial UI에서 제공하는 AppBar와 Toolbar를 사용하여 네비게이션 바 컴포넌트를 생성하고 리스트 렌더링 위에\n이 컴포넌트를 추가한다.\n```\n#### 네비게이션 바 테스팅\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/137ea3fd-e23c-4380-8857-66c86a9789e6)\n```\n네비게이션 바의 맨 오른쪽에 로그아웃 버튼에 signout 함수를 연결하여 클릭하면 로그아웃이 되도록 기능을 구현하였다.\n```\n#### 로그아웃 기능 구현\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/8e54d4c0-3672-4633-bfb0-3efc3ee5cc7c)\n\n## UI 글리치 문제 해결\n```\nTodo 리스트 페이지에 접속한 후 로그인 페이지로 라우팅하기까지 걸리는 시간 때문이다.\n이 시간은 백엔드 서버에 todo를 요청하고 결과를 받아 확인하는 데 걸리는 시간이다.\n로그아웃 상태에서 아무것도 할 수 없지만 UI가 이렇게 오락가락하는 것은 사용성 측면에서 좋지 않다.\n이를 방지하기 위해서 백엔드에서 todo 리스트를 받아오기 전까지는 로딩 중이라는 메시지를 띄우도록 한다.\n```\n#### App.js 로딩중 로직 추가\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/9df96e2f-ef4d-41db-88a7-80603c403230)\n```\n로딩 중이 아닐 때는 todoListPage 컴포넌트를 렌더링하고 todo 백엔드 서버에 요청을 보내고 응답을 받아오는 중인\n로딩 중인 상태일 때는 로딩중...메시지가 보여지는 content 컴포넌트를 렌더링하도록 로직을 작성했다.\n```\n#### 로딩중 화면 테스트\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/c1262bba-0c1c-45e4-891d-8c354413d69b)\n```\n로그인하지 않은 상태에서 localhost:3000에 접근하면 로딩중..이라는 화면이 뜨다가 로그인 화면으로 전환되는 것을\n알 수 있다.\n```\n## 계정 생성 페이지\n\n#### ApiService: singup 함수 추가\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/5b633cc3-522a-46e7-95db-cb65f9d83c7a)\n```\nApiService에 signup 메서드를 추가한다. 이 메서드를 이용해서 백엔드에 signup 요청을 보낸다.\n```\n#### 계정 생성 페이지 SignUp.js\n```javascript\nfunction SignUp() {\n  const handleSubmit = event =\u003e {\n    event.preventDefault();\n    // 오브젝트에서 form에 저장된 데이터를 맵의 형태로 바꿔줌.\n    const data = new FormData(event.target);\n    const username = data.get(\"username\");\n    const password = data.get(\"password\");\n    signup({ username: username, password: password }).then(response =\u003e {\n      // 계정 생성 성공 시 login페이지로 리디렉트\n      window.location.href = \"/login\";\n    });\n  };\n\n  return (\n    \u003cContainer component='main' maxWidth='xs' style={{ marginTop: \"8%\" }}\u003e\n      \u003cform noValidate onSubmit={handleSubmit}\u003e\n        \u003cGrid container spacing={2}\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cTypography component='h1' variant='h5'\u003e\n              계정 생성\n            \u003c/Typography\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cTextField\n              autoComplete='fname'\n              name='username'\n              variant='outlined'\n              required\n              fullWidth\n              id='username'\n              label='아이디'\n              autoFocus\n            /\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cTextField\n              variant='outlined'\n              required\n              fullWidth\n              name='password'\n              label='패스워드'\n              type='password'\n              id='password'\n              autoComplete='current-password'\n            /\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cButton type='submit' fullWidth variant='contained' color='primary'\u003e\n              계정 생성\n            \u003c/Button\u003e\n          \u003c/Grid\u003e\n          \u003cGrid item xs={12}\u003e\n            \u003cLink to='/login' variant='body2'\u003e\n              이미 계정이 있습니까? 로그인 하세요.\n            \u003c/Link\u003e\n          \u003c/Grid\u003e\n        \u003c/Grid\u003e\n      \u003c/form\u003e\n    \u003c/Container\u003e\n  );\n}\n```\n```\n계정 생성 페이지는 form 부분에 인풋 필드가 들어가고 사용자는 각 인풋 필드에 해당 값을 입력한다.(아이디, 패스워드)\n사용자가 계정 정보를 입력한 후 버튼을 누르면 버튼에 연결된 submitHandle 함수가 실행된다.\n이 함수는 event.target, 즉 form에서 데이터를 가져와 HTTP 요청 바디를 작성한 후 ApiService의 signup 함수를 이용해\n계정 생성 요청을 날린다.\n\n이미 계정이 있읍니까? 로그인하세요 링크를 추가해서 원한다면 로그인 페이지로 이동할 수 있게끔 했다.\n```\n#### AppRouter: SingUp 라우트 추가\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/4a660665-fe62-4266-8a2d-9ff064b5598f)\n```\nAppRouter에서 SignUp 페이지로 가는 라우트를 추가했다. \n```\n#### 로그인 컴포넌트 수정\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/377bd754-a0f3-49ea-85e3-6c31f1acb621)\n```\n로그인 컴포넌트에서 계정 생성 페이지로 넘어가는 링크를 작성했다. \u003cLink\u003e의 to=\"/signup\"에 들어가는 값은\nAppRouter에서 지정한 경로와 같아야 한다.\n```\n#### 계정 생성 페이지 테스트\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/1eadfaa1-e4eb-45fa-adc0-3374a4a1c1f2)\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/61782ef3-953b-4563-a4e2-7662e75273f2)\n```\n로그인 페이지에서 하단의 \"계정이 없습니까? 여기서 가입하세요\" 링크를 눌러 계정 생성 페이지로 라우팅 되는 것을 확인했다.\n\n또 반대로 계정 생성 페이지에서 하단의 \"이미 계정이 있습니까? 로그인 하세요\" 링크를 눌러서 로그인 페이지로 라우팅 되는 것을 확인했다.\n```\n#### 계정 생성 테스트\n![image](https://github.com/chihyeonwon/Frontend_Auth/assets/58906858/c2204660-0c79-4e2a-9867-df7d64bfce09)\n```\n아이디로 hello1@world.com 패스워드로 12345를 계정 생성 페이지에서 입력한 후 계정 생성 버튼을 눌러 계정을 생성하였다.\n이후에 생성한 아이디와 비밀번호로 로그인하여 나머지 기능이 정상적으로 잘 작동되는 것을 확인하였다. \n```\n\n\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpdlshdmlrkfmcla%2Ffrontend_auth","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Ftpdlshdmlrkfmcla%2Ffrontend_auth","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Ftpdlshdmlrkfmcla%2Ffrontend_auth/lists"}