[React] OAuth 2.0 사용하기 (w/ authorization code grant)

강버섯·2022년 2월 9일
5

AUTHORIZATION

목록 보기
3/9
post-custom-banner

👉 시작하기 전에

인증 정보에 대한 전반적인 관리에 대해서는 password grant 방식과 동일하게 context와 global state를 이용해서 관리하도록 한다.

👉 authorization code grant 방식으로 진행하기

autorization code grant

  1. 사용자가 로그인을 시도하면,
  2. 3rd party(OAuth server)로 보내져 login을 완료한 뒤 callback 주소로 authorization code와 함께 돌아온다.
  3. 사용자는 받은 autorization codeACCESS_TOKEN으로 교환

의 방식으로 진행된다.

👉 login

✏️ authorization code 받아오기

authorization code를 OAuth Server로부터 받기 위해서는 일단

  1. authorization endpoint
  2. client id
  3. redirect uri(callback)
  4. response type
  5. scope

을 필요로 한다.

AUTHORIZATION_ENDPOINT는 사용자가 authorization code를 받아오기 위한 OAuth Server의 주소이다.

REDIRECT_URI는 사용자가 정상적으로 authorization code를 받은 후 돌아오는 callback 주소인데, OAuth Server에 등록되지 않은 uri일 경우에는 OAuth Server가 authorization code를 반환해주지 않는다.

authorization code grant 방식을 사용한 인증이기 때문에 RESPONSE_TYPE"code" 로 설정해주면 된다.

SCOPE에는 사용자 "권한" 에 대한 정보를 담아준다.

사용자가 authorization code를 받기 위한 위와 같은 값들이 준비되었다면, AUTHORIZATION ENDPOINT에 해당 값들을 form 방식으로 붙여 이동시켜준다.
next.js의 내부 페이지가 아닌 외부의 페이지로 이동하는 것이기 때문에 window.location.href의 값을 이동시킬 uri의 주소로 설정하여 사용자를 이동시켜준다.

외부페이지(OAuth Server)로 이동 후 authorization code를 받으면 OAuth Server가 callback 주소로 사용자를 이동시켜 주기 때문에, status code를 확인하면 302 Redirect인 것을 확인할 수 있다.

login.js 👇

import React, { useEffect } from "react";
import qs from "querystring";

const AuthCodeLoginPage = () => {
    // authorization code를 받아오는 auth server로 보내는 역할을 수행함
    useEffect(() => {
        
        const OATUH_HOST = process.env.NEXT_PUBLIC_OAUTH_AUTHORIZATION_ENDPOINT;
        const client_id = process.env.NEXT_PUBLIC_OAUTH_AUTH_CLIENT_ID;
        // api의 callback 주소로 code를 받을 시 돌아옴
        const redirect_uri = process.env.NEXT_PUBLIC_OAUTH_REDIRECT_URI;
        const response_type = "code";
        const scope = process.env.NEXT_PUBLIC_OAUTH_AUTH_SCOPE;

        const AUTHORIZE_URI = `${OATUH_HOST}?${qs.stringify({
            client_id,
            redirect_uri,
            response_type,
            scope
        })}`; 
        
        // next.js api가 아닌 다른 page로 이동을 시킴 >> code를 받기 위한 auth server로의 이동
        // window -> client side에서만 실행되는 object = server side에는 window object가 존재하지 않음
        // window의 위치를 해당 URI로 보낸다~의 의미
        window.location.href = AUTHORIZE_URI;

    },[]);
    
    return <div>loading...</div>
}

export default AuthCodeLoginPage;

✏️ authorization code로 token 받기

정상적으로 authorization code를 받았다면 REDIRECT_URI로 넣어 보낸 callback 주소로 돌아오게 된다.
이제 받은 authorization code를 이용해서 ACCESS_TOKENREFRESH_TOKEN등을 받을 수 있다.

password grant 방식에서 했던 것과 동일하게 TOKEN_ENDPOINT로 post 요청을 보내주면 되는데,

  1. GRANT_TYPE은 authorization code로 설정하고
  2. username/password 대신에 받아온 authorization code를 code에 넣어서

요청을 보내는 점이 다르다.

서버가 사용자에 대한 정보를 보내주지 않기 때문에 authorization code로 받은 ACCESS_TOKEN을 이용해서 사용자 정보를 재요청한다.
ACCESS_TOKEN을 이용한 인증을 통해 API에 접근하게 되는데, 이때 Bearer token 방식으로 인증을 진행한다.
이 값은 Authorization header로 담아서 보내주면 된다.
만일 사용자 정보를 한 번에 받아오고 싶다면, 사용자 정보가 담긴 id token을 보내주는 openid connect라는 OAuth의 특별한 버전을 사용해주면 된다.

callback.js 👇

import axios from "axios";
import { useRouter } from "next/router"
import { useEffect } from "react";
import { useState } from "react/cjs/react.development";
import { useAuth } from "../../shared/context/auth";

const CallBackPage = () => {
    // auth server로부터 code를 받아온 이후의 처리 진행
    const router = useRouter();
    const {setIsSignedIn, setProfile} = useAuth();
    const [error, setError] = useState(null);

    useEffect( () => {
        (async () => {
            const {code} = router.query;
            console.log(code)
            if (code) {
                //code를 정상적으로 받아올 경우
                try {
                    // code를 이용해서 token들을 받아옴
                    const resp = await axios.post("/api/oauth/token",
                    {
                        grant_type: "authorization_code",
                        code,
                    });
              
                    console.log({resp})
              
                    const {access_token, refresh_token} = resp.data;
                    localStorage.setItem("access_token", access_token);
                    localStorage.setItem("refresh_token", refresh_token);
              
                    // 사용자에 대한 정보를 보내주지 않기 때문에 한 번더 요청을 해서 받아와야 함
                    const profileResp = await axios.get(
                        process.env.NEXT_PUBLIC_OAUTH_USER_INFO_ENDPOINT,
                        {
                            headers: {
                                authorization: `Bearer ${access_token}`
                            },
                        });
                    console.log({profileResp});
                    const profile = {"email": profileResp.data.email};
              
                    localStorage.setItem("profile", profile);
                    setIsSignedIn(true);
                    setProfile(profile);
              
                    console.log({localStorage})
                    router.push("/");
                } catch (err) {
                    setError("server error")
                }
            }
        })();
    }, [router])

    if (error) {
        return <div>{error}</div>;
    }

    return <div>Loading...</div>;
}

export default CallBackPage;

👉 logout

logout 방식도 password grant 방식과 동일하다.
간단하게 login을 진행할 때 local에 저장된 정보들을 제거해주고 이동하려는 화면으로 전환시켜주면 된다.

profile
무럭무럭 버섯농장
post-custom-banner

0개의 댓글