본문 바로가기

카테고리 없음

section3/Unit7/[Backend] 인증 / 보안(11/14)

반응형

OAuth

OAuth란?

전통적으로 직접 작성한 서버에서 인증을 처리해주는 것과는 달리, OAuth는 인증을 중개해주는 메커니즘이다. 보안된 리소스에 액세스하기 위해 클라이언트에게 권한을 제공하는 프로세스를 단순화하는 프로토콜이다.

즉, 이미 사용자 정보를 가지고 있는 웹 서비스(GitHub, Google, Facebook 등)에서 사용자의 인증을 대신해주고, 접근 권한에 대한 토큰을 발급한 후, 이를 이용해 내 서버에서 인증이 가능해진다.

 

OAuth는 언제, 왜 쓸까?

몇년전만 하더라도 특정 웹 앱의 서비스를 이용하기 위해선 해당 웹 앱에 회원가입을 하는 것이 우선이었다. 하지만 소셜 로그인이 보편화된 현재는 대부분의 사람들이 네이버 또는 카카오에 이미 가입된 계정을 이용해 빠르게 서비스에 가입하는 것을 택하고 있다.

뿐만 아니라 서비스를 구현하는 개발자도 신규 회원가입이나 회원 관리를 신경쓰지 않아도 되기 때문에 사용자와 기업 모두 소셜 로그인을 선호하고 있는 추세이다.

유저 입장에서 생각해보자면, 우리는 웹상에서 굉장히 많은 서비스를 이용하고 있고 각각의 서비스들을 이용하기 위해서는 회원가입 절차가 필요한 경우가 대부분이다. 각각의 서비스별로 ID와 Password를 다 기억하는 것은 매우 귀찮은 일이다.

하지만 OAuth를 활용한다면 자주 사용하고 중요한 서비스들(예를 들어 google, github, facebook) 의 ID와 Password만 기억해 놓고 해당 서비스들을 통해서 외부 서비스로 소셜 로그인을 할 수 있다.

뿐만 아니라 OAuth는 보안상의 이점도 있다. 검증되지 않은 App에서 OAuth를 사용하여 로그인한다면, 직접 유저의 민감한 정보가 App에 노출될 일이 없고 인증 권한에 대한 허가를 미리 유저에게 구해야 하기 때문에 더 안전하게 사용할 수 있다.

 

OAuth 용어

  • Resource Owner: 사용자이며 정보 제공자이기도 하기 때문에 Resource Owner라고 한다.
  • Client: Resource Owner를 대신하여 보호된 리소스에 액세스하는 애플리케이션이다.
  • Local Server: Client의 요청을 수락하고 응답할 수 있는 서버이다.
  • Resource Server: 사용자의 정보를 저장하고 있는 서버이다.
  • Authorization Server: 인증을 담당하고 있는 서버이다. Access Token을 발급하는 인증 서버이다.
  • Authorization Grant: Client가 Access Token을 얻는 방법을 의미한다. 다음과 같은 방법들이 주로 사용된다.
    • Authorization Code Grant Type
    • Refresh Token Grant Type
  • Authorization Code: Authorization Grant의 한 타입으로 Access Token을 발급받기 위한 Code를 의미한다.
  • Access Token: 보호된 리소스에 액세스하는 데 사용되는 인증 토큰이다. 이 Access Token으로 이제 Resource Server에 접근할 수 있다.
  • Refresh Token: 발급받은 Access Token이 만료될 시 Refresh Token을 통해 새로운 Access Token을 발급받을 수 있다.

 

OAuth 인증 흐름 

1. Authorization Code Grant Type

Authorization Code를 받아 Authorization Code를 통해 Access Token을 받는 방식을 말한다. 이 유형은 Access Token이 사용자나 브라우저에 표시되지 않는다는 것을 의미하므로 Access Token이 다른 사람에게 누출될 위험이 줄어든다.

2. Refresh Token Grant Type

Authorization Code Grant Type으로 Access Token을 발급받은 후 Access Token이 만료된 경우 Refresh Token을 활용해 새로운 Access Token으로 교환하는 데 사용된다. 이를 통해 사용자와의 추가 상호 작용 없이 계속 유효한 액세스 토큰을 가질 수 있다.


과제 - OAuth 2.0

클라이언트 & 서버의 환경 변수 설정

클라이언트가 리액트로 만들어졌기 때문에 변수명에 REACT_APP_을 넣어줘야 한다.

REACT_APP_CLIENT_ID='yourClientID'

서버는 Node.js 환경이므로 따로 붙여주지 않아도 된다.

 

App.js

window.location.assign()으로 깃허브 로그인 url로 이동 (process.env의 CLIENT_ID가 url parameter에 담겨서 이동)된다.

const getAccessToken = async (authorizationCode) => {
    // 받아온 Authorization Code로 다시 OAuth App에 요청해서 Access Token을 받을 수 있습니다.
    // Access Token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 OAuth App에 요청을 하는 방법은 보안에 취약할 수 있습니다.
    // Authorization Code를 서버로 보내주고 서버에서 Access Token 요청을 하는 것이 적절합니다.
    // TODO: 서버의 /callback 엔드포인트로 Authorization Code를 보내주고 Access Token을 받아옵니다.
    // Access Token을 받아온 후 state에 Access Token을 저장하세요
    axios
      .post("https://localhost:4000/callback", {authorizationCode})
      .then((res) =>{
        setAccessToken(res.data.accessToken); 
        setIsLogin(true);
      })
      .catch((err) => console.log(err));
  };
  useEffect(() => {
    // Authorization Server로부터 클라이언트로 리디렉션된 경우, Authorization Code가 함께 전달됩니다.
    // ex) http://localhost:3000/mypage?code=5e52fb85d6a1ed46a51f
    const url = new URL(window.location.href); //window.location.href => 현재 url을 가져옴
    const authorizationCode = url.searchParams.get('code');
    if (authorizationCode) {
      getAccessToken(authorizationCode); //getAccessToken => 인증코드로 액세스토큰을 받아오는 기능을 하는 함수
    }
  }, []);

 

Login.js

const CLIENT_ID = process.env.REACT_APP_CLIENT_ID;

  const loginRequestHandler = () => {
    // TODO: GitHub로부터 사용자 인증을 위해 GitHub로 이동해야 합니다. 적절한 URL을 입력하세요.
    // OAuth 인증이 완료되면 authorization code와 함께 callback url로 리디렉션 합니다.
    // 참고: https://docs.github.com/en/free-pro-team@latest/developers/apps/identifying-and-authorizing-users-for-github-apps
    return window.location.assign(
      `https://github.com/login/oauth/authorize?client_id=${CLIENT_ID}`
    );
  };

 

Mypage.js

const logoutHandler = () => {
    // TODO: /logout을 통해 사용자가 로그아웃되도록 구현하세요.
    // prop으로 받은 Access Token을 이용해 /logout 엔드포인트로 요청을 보내야합니다.
    // 요청이 성공했다면 isLogin 상태를 false로 업데이트해야 합니다.
    console.log(accessToken)
    axios
      .delete("https://localhost:4000/logout", {data: {accessToken}})
      .then((res) => {
        console.log("logout!");
        setAccessToken('');
        setIsLogin(false);
        setGithubUser(null);
        setServerResource(null);
      });
    };

  useEffect(() => {
    // TODO: /userinfo를 통해 사용자 정보를 받아오세요.
    // prop으로 받은 Access Token을 이용해 /userinfo 엔드포인트로 요청을 보내야합니다.
    // 응답으로 받은 데이터를 githubUser, serverResource의 상태로 업데이트해야합니다.
    // isLoading 상태를 false로 업데이트해야 합니다.
    axios
      .post('https://localhost:4000/userinfo', { accessToken })
      .then((res) => {
        setIsLoading(true); 
        console.log(res);
      
        const {githubUserData, serverResource} = res.data; 

        setGithubUser(githubUserData) ;
        setServerResource(serverResource);
      
        setIsLoading(false);
    })
      .catch((err) => console.log(err))
  }, []);

 

한참 헤맸던 부분을 정리해보자.

 

※ axios.delete 요청 시 body에 data를 넣는 방법POST, PUT, PATCH 요청과 다르다!

프론트에서 Axios를 사용하여 서버에 요청을 할 때, POST, PUT, PATCH 요청의 2번째 인자에 넣은 데이터는 요청의 본문(body)에 데이터로 함께 전송된다.

axios.post(url[, data[, config]])

axios.post(`/post/like`, { data });

DELETE 요청시에는 두 번째 인자에 data: {} atrribute를 넣어주면 된다.

axios.delete(url[, config])

axios.delete(`/post/like`, {
    data: { // 서버에서 req.body.{} 로 확인할 수 있다.
      postId: ...
      commentId: ...
    },
  });

 

axios.delete does support a request body. It accepts two parameters: url and optional config. You can use config.data to set the response body as follows:

axios.delete(url, { data: { foo: "bar" } });

post, put, and patch accept 3 parameters: url, data, and config so you can you can use the second parameter to set the response body like this:

axios.put(url, { foo: "bar" });

 

참고)

https://velog.io/@bigbrothershin/Axios-delete-%EC%9A%94%EC%B2%AD-%EC%8B%9C-body%EC%97%90-%EB%8D%B0%EC%9D%B4%ED%84%B0-%EB%84%A3%EB%8A%94-%EB%B2%95

 

Axios - delete 요청 시 body에 데이터 넣는 법

Axios delete 요청 시 요청의 본문(body)에 데이터를 함께 전송하는 방법

velog.io

 

 

로그인 & 마이페이지

  1. 사용자가 사이트(클라이언트)에 접속 후 깃허브로 로그인하기를 클릭한다.
    window.location.assign()으로 깃허브 로그인 url로 이동 (process.env의 CLIENT_ID가 url parameter에 담겨서 이동)된다.
  2. 깃허브 로그인 후, 깃허브 Authorization 서버에서 클라이언트로 Redirect된다.
    받아온 Authorization code로 Access token을 받아오기 위해 /callback으로 POST 요청을 한다.
    로컬 서버가 깃허브 Authorization 서버로 Access Token을 요청한다.
    Authorization 서버로부터 Access Token 을 받고 그것을 클라이언트로 전달한다.
    Access Token 전달을 잘 받으면 로그인이 완료된다.

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

  3. 로그인 상태가 true가 되어 Mypage 컴포넌트가 뜬다.
    사용자 정보를 가져오기 위해 로컬 서버Access token과 함께 POST 요청을 보낸다.
    로컬 서버가 깃허브 Resource 서버 Access token과 함께 유저 정보 GET 요청을 보낸다.
    깃허브 Resource 서버에서 응답받은 깃허브 유저 정보와 로컬 서버의 리소스로 가진 유저 정보를 클라이언트로 보낸다.
    화면에 회원 정보를 뿌려준다.

로그아웃

  1. 로컬 서버로 Access Token을 보내서 DELETE 요청을 보낸다.
    로컬 서버에서 깃허브 Authorization 서버에 CLIENT_ID와 CLIENT_SECRET, Access Token을 가지고 Authorization 헤더를 설정한 후 DELETE 요청을 보낸다.
    요청 성공하면 응답코드 205와 성공 문구를 클라이언트에 전달하고, 클라이언트에서 로그인 상태 false로 변경하여 로그아웃이 완료된다.

반응형