[HTTPS / 깃허브 OAuth] 로그인-로그아웃 구현하기 (프론트엔드) +7/20 수정

young·2022년 7월 18일
2

Learn more

목록 보기
15/22

💡 Before learn...

인증 방식만 이해하면 쉽다.
OAuth 사용으로 각각의 서비스별로 정보를 일일이 기입하면서 회원가입 할 필요가 없어짐

  • Authorization code grant type
    => Access Token을 발급받는 유형
    인증 코드를 받아서 그걸로 Access Token을 발급받음

  • refresh token grant type
    => Access Token 만료됐을 경우 refresh token으로 Access Token 발급받음
    깃허브 OAuth는 리프레시 토큰 지원 X

위 두가지만 잘 활용하면 된다.

깃허브 OAuth 설정

https://github.com/settings/developers
위 링크에서 OAuth 앱을 등록한다. 참고문서

client id와 client secret은 환경변수로 설정하여 적재적소에 활용

authorization callback url => authorization code를 받을 주소
OAuth 매커니즘이 인증 과정이 끝난 후 리디렉션을 통해 다시 내 앱으로 이동하는 원리이므로 callback url이 필요하다.

resource owner = 사용자
resource server = 사용자의 정보를 가지고 있는 서버 = 깃허브
authorization server = 인증 담당 서버


📌 클라이언트 및 서버의 환경 (변수) 설정

🧩 client/.env

.env 파일을 생성하여 위에서 생성한 client id를 넣어준다.
이때, 클라이언트가 리액트로 만들어졌기 때문에 변수명에 REACT_APP_을 넣어줘야 한다.

REACT_APP_CLIENT_ID='yourClientID'

🧩 server/.env

  1. 서버는 Node.js 환경이므로 따로 붙여주지 않아도 된다.
    서버에서는 CLIENT_IDCLIENT_SECRET 모두 사용한다.
    CLIENT_SECRET은 항상 비밀로 지켜야 한다.
  1. 이전과 같이 key.pem과 cert.pem을 install해서 HTTPS 인증을 받을 수 있게 한다.

이제 클라이언트와 서버를 다시 시작하면 적용된다.




📌 Authorization code 받아오기

🧩 client/Login.js

먼저 "깃허브로 로그인" 버튼을 누르면, 사용자 인증을 위해 깃허브로 이동해야 한다.
=> button의 onClick 이벤트 핸들러 함수 작성

//const CLIENT_ID = process.env.REACT_APP_CLIENT_ID
onClick = () => {
  window.location.assign(
`https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}`
)}

이제 로그인 버튼을 누르면 깃허브 계정 인증 요청 창이 뜬다.

🧩 client/App.js

깃허브 앱에 요청(로그인)을 보내 Authorization code를 받아온다.

로그인 버튼 클릭 -> 인증 확인을 누르면
Authorizaion server로부터 클라이언트의 callback url로 리디렉션 되고, url parameter에서 code=authorizationCode를 확인할 수 있다.

"callback url로 리디렉션" => 이제 App.js 코드가 발동됨

useEffect hook을 사용해서 종속성 배열을 빈 배열로 설정하고,
컴포넌트가 마운트 됐을 때 url에서 Authorization code를 받아오는 함수를 작성한다.

  useEffect(() => {
    const url = new URL(window.location.href); //window.location.href: 현재 url을 가져온다.
    const authorizationCode = url.searchParams.get("code");
    if (authorizationCode) {
      getAccessToken(authorizationCode); //getAccessToken: 인증코드로 액세스토큰을 받아오는 기능을 하는 함수
    }
  }, []);

이제 url parameter에 있는 전달받은 코드(authorizationCode)를 서버에 넘겨서 Access Token을 받아와야 한다.




📌 Access token 받아 오기

🧩 client/App.js

인자로 들어오는 Authorization Code로 다시 OAuth App(깃허브)에 요청해서 Access Token을 받는다.
Access Token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 요청하지 않고 서버로 보내서 서버에서 Access Token을 요청한다.

  1. 서버의 /callback 엔드포인트로 Authorization Code를 보내주고 응답 body에서 Access Token을 받아온다.

  2. Access Token을 받아온 후 state에 Access Token을 저장한다.

const getAccessToken = async (authorizationCode) => {
    axios
      .post("https://localhost:4000/callback", {authorizationCode})
      .then((res) =>{
        setAccessToken(res.data.accessToken) //Access Token 저장
        setIsLogin(true) //액세스 토큰을 받아와 로그인 상태 true로 변경
      })
      .catch((err) => console.log(err));
  };
  1. setIsLogin(로그인 상태 변경 함수)과 accessToken은 Mypage의 props로 넘겨준다.

이때 서버는 클라이언트의 요청을 받고 CLIENT_ID, CLIENT_SECRET, 요청 바디의 인증코드를 data로 묶어서 깃허브에 Access Token 요청을 보낸다.
정상적인 요청이라면 res.data.access_token에서 깃허브에서 보내온 Access Token을 확인할 수 있다.
로컬 서버는 Access Token을 클라이언트의 응답으로 보낸다.




📌 로컬 서버를 통해 Github 리소스 서버에 유저 정보 요청

🧩 client/Mypage.js

  1. 받아온 Access Token으로 Mypage에서 리소스에 대한 API 요청을 한다.
    = 사용자 정보를 엔드포인트 /userinfo에 요청한다.
  2. 응답으로 받은 로컬 서버의 리소스와 깃허브 Resource 서버의 깃허브 유저 정보를 Mypage에 렌더링한다.

useEffect hook을 사용해 컴포넌트가 마운트 되면 함수가 실행되도록 한다.

useEffect(() => {
    axios
      .post('https://localhost:4000/userinfo', { accessToken })
      .then((res) => {
        setIsLoading(true) //loading indicator 켜고 시작
      
        const {githubUserData, serverResource} = res.data; //응답으로 받은 로컬서버발 리소스, 깃허브서버발 리소스를 구조분해할당
      
      	//state에 저장
        setGithubUser(githubUserData) 
        setServerResource(serverResource)
      
        setIsLoading(false) //loading indicator 종료
    })
      .catch((err) => console.log(err))
  }, []);

이때 서버는 클라이언트 요청에서 전달받은 Access Token을 이용해 사용자의 정보를 가져온다.
깃허브 Authorization 서버가 아닌, Resource 서버로 요청을 보낸다.
응답으로 받은 깃허브 유저 정보와 함께 로컬 서버의 리소스를 클라이언트에 응답으로 보낸다.




📌 로그아웃 구현하기

🧩 client/Mypage.js

  1. props로 받은 Access token을 이용해 로컬 서버의 /logout 엔드포인트에 요청을 보낸다.
    (=> 서버에서 깃허브로 요청을 보내 사용자의 token을 삭제한다.)
  2. 요청이 성공하면 로그인 상태를 false로 변경한다.

로그아웃 button의 onClick 이벤트 핸들러 함수를 작성한다.

const logoutHandler = () => {
    axios.delete('https://localhost:4000/logout', {accessToken})
    .then((res) => setIsLogin(false)) //화면상의 로그아웃 설정
    .catch((err) => console.log(err.response.data))
  };

로그아웃까지 구현 완료!

7/20 추가

이때 서버에서는 클라이언트에서 전달받은 Access token을 이용해 사용자의 권한 부여를 취소한다.
엔드포인트 https://api.github.com/applications/${CLIENT_ID}/token로 DELETE 요청을 보낸다.
client id와 client secret을 각각 유저네임과 패스워드로 Authorization 헤더 설정한 후 요청을 보내면 발급받은 액세스 토큰을 지울 수 있다.

data: {access_token: accessToken,},
auth: { username: CLIENT_ID, password: CLIENT_SECRET, }

공식문서
You must use Basic Authentication when accessing this endpoint, using the OAuth application's client_id and client_secret as the username and password.

검색해보니 auth:{}로 묶어서 보내는 이유는 깃허브 인증방식이 octokit을 사용해서 그런 것 같은데 확실하지 않아서 더 알아봐야 할 것 같다.


🏝 깃허브 OAuth 로그인-로그아웃 전체 flow

  1. 사용자가 사이트(클라이언트)에 접속 ➞ 깃허브로 로그인하기를 클릭한다.
    window.location.assign()으로 깃허브 로그인 url로 이동 (process.env의 CLIENT_ID가 url parameter에 담겨서 이동)

  2. OPTIONS /callback
    POST /callback
    깃허브 아이디로 로그인하면 깃허브 Authorization 서버에서 Authorization code와 함께 클라이언트로 Redirect
    ➞ 응답으로 받아온 Authorization code로 Access token을 받아오기 위해 /callback으로 POST 요청
    ➞ 로컬 서버가 깃허브 Authorization 서버로 Access Token 요청
    ➞ Authorization 서버로부터 Access Token 응답 받음 && 클라이언트로 Access Token 전달, 로그인 상태 true로 변경
    ➞ 사용자의 로그인이 성공됨

    Access token은 보안 유지를 위해 클라이언트가 직접 요청하지 않는다.
    깃허브 Authorization 서버 ➞ 클라이언트 ➞ 로컬 서버 /callback

  1. OPTIONS /userinfo
    POST /userinfo
    로그인 상태가 true로 변경되어 Mypage 컴포넌트가 마운트 된다.
    ➞ 해당 컴포넌트에서 Access token을 가진 사용자 정보를 가져오기 위해 로컬 서버에 POST 요청을 보낸다.
    ➞ 로컬 서버가 깃허브 Resource 서버에 Access token을 가지고 유저 정보 GET 요청을 보낸다.
    ➞ 깃허브 Resource 서버에서 응답받은 깃허브 유저 정보 + 로컬 서버의 리소스로 가진 유저 정보를 클라이언트에 응답 보낸다.
    ➞ 받은 resource들을 가지고 클라이언트 화면에 로그인 회원 정보를 업데이트한다.

로그아웃

  1. 로그인된 상태에서 로그아웃 버튼 클릭이벤트 발생
    ➞ 로컬 서버로 Access Token을 보내서 DELETE 요청
    ➞ 로컬 서버에서 깃허브 Authorization 서버에 CLIENT_IDCLIENT_SECRET, Access Token을 가지고 Authorization 헤더 설정한 후 DELETE 요청을 보낸다.
    ➞ 요청 성공하면 응답코드 205와 성공 문구를 클라이언트에 전달 && 클라이언트에서 로그인 상태 false로 변경
    ➞ 사용자의 로그아웃 성공

Learn more...

OAuth 2.0 Simplified

profile
즐겁게 공부하고 꾸준히 기록하는 나의 프론트엔드 공부일지

0개의 댓글