-
Notifications
You must be signed in to change notification settings - Fork 4
Authorization Code Grant 방식으로 Github OAuth 인증 구현
OAuth 2.0은 다양한 플랫폼 환경에서 권한 부여를 위한 산업 표준 프로토콜입니다.
(정의) 제 3의 앱이 자원의 소유자인 서비스 이용자를 대신하여 서비스를 요청할 수 있도록 자원 접근 권한을 위임하는 방법 출처 : 금융보안원 "OAuth 2.0 개요 및 보안 고려사항" 보안연구부-2015-030
-
인증 코드로
access token을 얻는 방식이다.→ 보안에 더 효과적!
-
웹과 모바일 앱에서 가장 많이 사용된다.
-
코드를 교환하는 단계에서 access token은 항상 보호된 back channel에서 응용 프로그램과 OAuth 서버를 이동하기 때문에 공격자가 중간에서 가로챌 수 없다.
[1, 2, 3, 4 단계]
- 프론트에서 깃허브 로그인 버튼 클릭 → 백엔드의 깃허브 로그인 API를 호출.
- 백엔드에서 깃허브 인증 페이지로 리다이렉션 (passport-github2).
- 사용자는 깃허브 계정 로그인을 진행
[5, 6, 7, 8 단계] - passport-github2 에서 내부적으로 진행하는 단계
router.get(
'/github/callback',
passport.authenticate('github', {
session: false,
failureRedirect: clientURL,
}),
githubVerifyCallbackController,
);- 로그인 성공시 GitHub OAuth 인증키를 받을 때 설정한 callbackURL로 Authorization Code가 전송됨.
- Authorization Code로 인증 서버(GitHub)에 Access Token을 요청
- Access Token을 받으면 토큰을 이용해서 유저 profile 정보를 가져오는 API를 호출해서 유저 정보를 받음.
const GitHubVerifyCallback = async (
accessToken,
refreshToken,
profile,
done,
) => {
try {
const [user] = await UserModel.findOrCreate({
where: { uid: profile.id },
defaults: {
uid: profile.id,
name: profile.username,
profileLink: profile.profileUrl,
},
});
return done(null, user);
} catch (error) {
return done(error, null);
}
};[JWT 토큰 발급 단계]
-
로그인 시작점이 fetch API 방식이 아니고 페이지 이동이 일어나는 GET 요청이기 때문에 응답을 URL로 받는다. ⇒ 토큰이 URL에 노출됨
- 노출되어도 상관이 없는 일회용 인증코드를 쿼리로 응답하고 해당 코드로 토큰을 요청하는 단계를 거침 (5, 6, 7, 8 단계를 모방한 방식)
-
인증코드를 생성해서 inmemory 저장소에 <인증코드, 유저정보>를 저장하고 쿼리에 담아서 클라이언트(사용자)에게 전송.
const codeMap = new Map(); // <인증코드, 유저정보> const githubVerifyCallbackController = (req, res) => { const authCode = uuidv4(); codeMap.set(authCode, req.user); res.redirect(`${clientURL}/oauth/callback?code=${authCode}`); };
-
쿼리에서 인증코드를 파싱하고 백엔드 서버에 JWT 토큰을 요청하는 빈 페이지 컴포넌트를 구현하여 콜백 페이지로 이용.
const OAuthCallback = ({ location }) => { const history = useHistory(); const getToken = async () => { const { code } = queryString.parse(location.search); const { token } = await oauthApi.getToken(code); localStorage.setItem('token', token); }; useEffect(() => { getToken().then(() => history.push('/')); }, []); return <></>; };
-
/oauth/token으로 API 요청을 받으면 인증 코드로 유저 정보를 꺼내 토큰을 생성하고 클라이언트에게 응답.router.post('oauth/token', (req, res) => { const { body: { code }, } = req; const user = codeMap.get(code); codeMap.delete(code); const token = generateToken(JwtConfig)(user); return res.json({ token }); });
-
응답받은 토큰을 localStorage에 저장
→ 문제점: localStorage로 토큰에 접근할 수 있으므로 보안에 취약하다.
사용자가 로그인이 되어있는 상태가 아니라면 로그인 페이지로 이동시켜야 한다.
그렇게 하기 위해서는 로그인 요청을 제외하고, 서버에 api 요청을 할 때마다, 이 유저가 로그인이 되어있는 상태인지 확인해야 한다.
-
로컬 스토리지에 토큰 유무 검사
const token = localStorage.getItem('{token_name}') ; if(token){ ... }
-
토큰이 존재하지 않으면, 로그인이 필요하므로 로그인 페이지로 이동시킨다.
Redirect("/login") -
토큰이 존재한다면, 그 토큰을 헤더에 담아서 서버에 api를 요청한다. 토큰을 담아서 사용자 정보 요청
-
유효한 토큰이라면, 서버는 토큰에 담긴 사용자 정보 반환한다.
클라이언트 측에서는 응답받은 사용자 정보를 context에 저장하여 사용하게 된다.
const login = async () => { const flow = {}; try { const userInfo = await userAPI.getMyInfo(); flow.action = userContextActions.setUserInfo(userInfo); flow.redirectURL = LOGIN_SUCCESS_URL; } catch (error) { flow.action = userContextActions.resetUserInfo(); flow.redirectURL = LOGIN_FAILURE_URL; } userFlowAction({ ...flow }); };
-
만약 유효하지 않은 토큰이라면, 401 응답이 오게 된다.
이 유저는 로그인이 필요하므로, 로그인 페이지로 이동시킨다.
Redirect("/login")instance.interceptors.response.use( (response) => { return response.data; }, (error) => { if (error.response.status === 401) { localStorage.removeItem('token'); window.location.href = '/login'; } return Promise.reject(error); }, );
-
-
api 요청시마다 Authorization 요청 헤더에 토큰을 보내주어야 한다.
이 작업은 반복적이므로, axios의 interceptor 로 자동으로 요청 헤더에 토큰을 설정해준다.
- axios 인스턴스 생성.
const instance = axios.create({
baseURL: process.env.API_BASE_URL,
});-
interceptor.request로 Authorization 헤더에 token을 저장한다.
instance.interceptors.request.use((config) => {
const token = localStorage.getItem('token');
const configCopy = config;
if (token) configCopy.headers.common.Authorization = `Bearer ${token}`;
return configCopy;
});1️⃣ Week1
목표
데일리스크럼
회의
회고
2️⃣ Week2
목표
데일리스크럼
회의
회고
2️⃣ Week3
목표
데일리스크럼
회의
회고
