Snappi 서비스를 개발하면서 OAuth로 인증코드와 액세스 토큰을 각각 받아오는 작업을 담당했다.
Google 개발자 페이지에서는 OAuth를 통해 사용자인증을 하고 API에 접근하는 과정을 아래와 같이 안내하고 있다.
1. 유저가 소셜 로그인을 하면
2. 구글 서버에서 인증 코드를 제공.
3. 해당 인증코드로 토큰을 구글 서버에 요청해
4. 토큰을 가지고 Google API에 토큰을 전송해주면 된다.
<로그인>
<유튜브 채널 연동>
여기까지 완료하면 클라이언트 ID와 클라이언트 보안 비밀번호를 얻는다.
client_id: Google API Console에서 발급받은 클라이언트 ID
redirect_uri: 사용자가 구글에서 인증을 한 후 결과를 받을 수 있는 주소이다.
나의 경우 ‘/signin’ 페이지에 구글 로그인 버튼을 만들어주었고 ’/check-signup’ 페이지로 리다이렉트 시켰다. 이 페이지에서는 인증 절차를 거친 후 db에 해당 유저가 있는지 파악해서 정보가 있다면 로그인, 정보가 없다면 회원가입으로 이동시키는 분기처리를 해준다.
이 redirect_uri는 구글 콘솔 사용자 인증 정보의 승인된 리디렉션 URI와 정확히 일치해야한다.
난 이때 몇가지 삽질(?)을 했는데
http 이슈
로컬로 할 땐 문제 없었지만 http 도메인은 보안상 등록되지 않는다. 개발계 주소는 http 로 되어있어 등록할 수 없었고 이 문제가 해결되기까지 테스트를 하지 못했다. (나중에 OCI에 운영계를 올려서 https 도메인으로 테스트 할 수 있었다.)
후행 슬래시!!
구글 개발자 페이지에 redirect_uri 부분을 보며 아래와 같이 써있다.
"http 또는 https 스키마, 대소문자, 후행 슬래시('/')가 모두 일치해야 합니다." 쿼리스트링에도 리디렉션 URI에도 후행 슬래시를 붙이지 않았느데 이때 계속 redirect_uri_mismatch 오류가 발생했다.
나중에 알고 보니 Next.js에서는 redirect 시 후행 슬래시가 붙어서 이동한다고 한다.
[참고] Next.js 공식문서 - trailingSlash
그러니 Next.js를 사용한다면 후행 슬래시를 꼭 붙일 것!
ex)’https://.../check-signup/google/’
하지만 액세스 토큰 발급 시 후행 슬래시를 제거해야만 가져와졌다...
ex)‘https://.../content-upload/get-channel-id’
예시 (인증 코드)
// getAuthCode.ts
import qs from 'querystring';
export const getGoogleAuthCode = () => {
const OAUTH_HOST = "https://accounts.google.com/o/oauth2/v2/auth";
const client_id = // 클라이언트 아이디;
const redirect_uri = 'https://{domain}/check-signup/google/';
const response_type = 'code';
const scope = 'https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/userinfo.profile';
const prompt = 'select_account';
const AUTHORIZE_URI = `${OAUTH_HOST}?${qs.stringify({
client_id,
redirect_uri,
response_type,
scope,
prompt
})}`
window.location.hreft = ATHORIZE_URI;
};
// check-signup.ts
...
const searchParams = useSearchParams();
// 인증 코드는 redirect_uri의 쿼리스트링으로 받을 수 있다.
const authCode = searchParams.get('code');
예시 (액세스 토큰)
// channel-upload.ts
const getGoogleToken = () => {
const OAUTH_HOST = "https://accounts.google.com/o/oauth2/v2/auth";
const client_id = // 클라이언트 아이디;
const redirect_uri = 'https://{도메인}/content-upload/get-channel-id';
const response_type = 'token';
const scope = "https://www.googleapis.com/auth/youtube https://www.googleapis.com/auth/youtube.readonly https://www.googleapis.com/auth/youtubepartner";
const prompt = 'select_account';
const AUTHORIZE_URI = `${OAUTH_HOST}?${qs.stringify({
client_id,
redirect_uri,
response_type,
scope,
propmt
})}`;
window.location.href = AUTHORIZE_URI;
}
export defualt function GetChannelId() {
const router = useRouter();
// access_token은 redirect_uri의 해시프래그먼트로 받아볼 수 있다.
//https://oauth2.example.com/callback#access_token=4/P7q7W91&token_type=Bearer&expires_in=3600
useEffect(() => {
window?.localStorage.setItem(
'googleToken',
window?.location.href?.split('access_token=')[1]?.split('&')[0]
);
router.push('/content-upload/channel-upload');
}, [router]);
return <></>
}
이후 나머지 처리를 하면 된다!