인증 방식만 이해하면 쉽다.
OAuth 사용으로 각각의 서비스별로 정보를 일일이 기입하면서 회원가입 할 필요가 없어짐
Authorization code grant type
=> Access Token을 발급받는 유형
인증 코드를 받아서 그걸로 Access Token을 발급받음
refresh token grant type
=> Access Token 만료됐을 경우 refresh token으로 Access Token 발급받음
깃허브 OAuth는 리프레시 토큰 지원 X
위 두가지만 잘 활용하면 된다.
https://github.com/settings/developers
위 링크에서 OAuth 앱을 등록한다. 참고문서
client id와 client secret은 환경변수로 설정하여 적재적소에 활용
authorization callback url => authorization code를 받을 주소
OAuth 매커니즘이 인증 과정이 끝난 후 리디렉션을 통해 다시 내 앱으로 이동하는 원리이므로 callback url이 필요하다.
resource owner = 사용자
resource server = 사용자의 정보를 가지고 있는 서버 = 깃허브
authorization server = 인증 담당 서버
.env 파일을 생성하여 위에서 생성한 client id를 넣어준다.
이때, 클라이언트가 리액트로 만들어졌기 때문에 변수명에 REACT_APP_
을 넣어줘야 한다.
REACT_APP_CLIENT_ID='yourClientID'
CLIENT_ID
와 CLIENT_SECRET
모두 사용한다.CLIENT_SECRET
은 항상 비밀로 지켜야 한다.이제 클라이언트와 서버를 다시 시작하면 적용된다.
먼저 "깃허브로 로그인" 버튼을 누르면, 사용자 인증을 위해 깃허브로 이동해야 한다.
=> 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}`
)}
이제 로그인 버튼을 누르면 깃허브 계정 인증 요청 창이 뜬다.
깃허브 앱에 요청(로그인)을 보내 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을 받아와야 한다.
인자로 들어오는 Authorization Code로 다시 OAuth App(깃허브)에 요청해서 Access Token을 받는다.
Access Token은 보안 유지가 필요하기 때문에 클라이언트에서 직접 요청하지 않고 서버로 보내서 서버에서 Access Token을 요청한다.
서버의 /callback 엔드포인트로 Authorization Code를 보내주고 응답 body에서 Access Token을 받아온다.
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));
};
setIsLogin
(로그인 상태 변경 함수)과 accessToken
은 Mypage의 props로 넘겨준다.이때 서버는 클라이언트의 요청을 받고
CLIENT_ID
,CLIENT_SECRET
,요청 바디의 인증코드
를 data로 묶어서 깃허브에 Access Token 요청을 보낸다.
정상적인 요청이라면res.data.access_token
에서 깃허브에서 보내온 Access Token을 확인할 수 있다.
로컬 서버는 Access Token을 클라이언트의 응답으로 보낸다.
- 받아온 Access Token으로 Mypage에서 리소스에 대한 API 요청을 한다.
= 사용자 정보를 엔드포인트 /userinfo에 요청한다.- 응답으로 받은 로컬 서버의 리소스와 깃허브 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 서버로 요청을 보낸다.
응답으로 받은 깃허브 유저 정보와 함께 로컬 서버의 리소스를 클라이언트에 응답으로 보낸다.
- props로 받은 Access token을 이용해 로컬 서버의 /logout 엔드포인트에 요청을 보낸다.
(=> 서버에서 깃허브로 요청을 보내 사용자의 token을 삭제한다.)- 요청이 성공하면 로그인 상태를 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'sclient_id
andclient_secret
as the username and password.
검색해보니 auth:{}
로 묶어서 보내는 이유는 깃허브 인증방식이 octokit을 사용해서 그런 것 같은데 확실하지 않아서 더 알아봐야 할 것 같다.
사용자가 사이트(클라이언트)에 접속 ➞ 깃허브로 로그인하기를 클릭한다.
window.location.assign()
으로 깃허브 로그인 url로 이동 (process.env의 CLIENT_ID
가 url parameter에 담겨서 이동)
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
Mypage
컴포넌트가 마운트 된다.CLIENT_ID
와 CLIENT_SECRET
, Access Token을 가지고 Authorization 헤더 설정한 후 DELETE 요청을 보낸다.