인증 정보에 대한 전반적인 관리에 대해서는 password grant 방식과 동일하게 context와 global state를 이용해서 관리하도록 한다.
autorization code grant
는
authorization code
와 함께 돌아온다.autorization code
를 ACCESS_TOKEN
으로 교환의 방식으로 진행된다.
authorization code
를 OAuth Server로부터 받기 위해서는 일단
을 필요로 한다.
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
를 받았다면 REDIRECT_URI
로 넣어 보낸 callback 주소로 돌아오게 된다.
이제 받은 authorization code
를 이용해서 ACCESS_TOKEN
과 REFRESH_TOKEN
등을 받을 수 있다.
password grant 방식에서 했던 것과 동일하게 TOKEN_ENDPOINT
로 post 요청을 보내주면 되는데,
GRANT_TYPE
은 authorization code로 설정하고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 방식도 password grant 방식과 동일하다.
간단하게 login을 진행할 때 local에 저장된 정보들을 제거해주고 이동하려는 화면으로 전환시켜주면 된다.