Google OAuth 구현
- Front: React
- Sever: Nextjs
Google OAuth를 직접 구현하면서 OAuth의 흐름을 이해하기 위해서 Next-auth 라이브러리를 사용하지 않았다.
OAuth는 Client(내 서비스), Resource Owner(내 서비스 이용자), Resource Sever(구글) 간의 통신으로 이루어진다.
Google OAuth를 사용하기 위해서는 Google Cloud Platform에서 사용자 인증정보를 등록해야 한다.
등록을 마치면 client_id, client_secret, redirect_uri 정보를 확인 할 수 있는데 이 값들을 사용해 코드를 구현하면된다.
위의 정보들은 구글 서비스를 이용하는 Client인지 식별하기 위함이라고 이해
가장 중요하다고 생각되는 redirect_uri를 설정하면 User가 내서비스에서 구글 로그인을 하겠다고 시도했을때 구글에서 승인한 redirect_uri로 이동을 시켜준다.
중요한건 Google Cloud Platform에 등록된 client_id, client_secret, redirect_uri가 모두 일치해야 구글은 User(내 서비스 이용자)에게 구글 로그인 페이지로 이동시키고, 사용자가 구글 로그인을 승인하면 구글이 redirect_uri로 승인 코드를 전달한다.
여기서 구글이 승인코드를 보내주는건 내 서비스에서 구글 로그인을 사용하겠다고 사용자가 승인했다는 의미의 코드라고 이해
승인 코드를 프론트에서 확인하고 서버에 request 요청 -> 서버에서는 request 데이터를 분석해 필요한 정보를 담아 구글 서버에 request 요청 -> 승인된 code인지 판별하고 구글에서는 내 서버에 token을 전송
여기서의 token은 구글이 내 서비스를 승인했다고 이해
여기까지 완료하면 구글에게 다시 한번 userInfo를 요청하면 된다.(이떄 구글에서 보내준 token 보냄)
프론트는 React를 사용했고 처음에는 button 태크의 onClick 이벤트를 사용했고 직접 redirect_uri로 window를 이동시켰다.
export default function LoginComponent() {
const handleLogin = () => {
const clientId = process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID;
const redirectUri = process.env.NEXT_PUBLIC_GOOGLE_LOGIN_REDIRECT_URI;
// authorization code 요청
const googleAuthUrl =
`https://accounts.google.com/o/oauth2/v2/auth?` +
`client_id=${clientId}&` +
`redirect_uri=${redirectUri}&` +
`response_type=code&` +
`scope=email profile&` +
`access_type=offline&` + // access_type을 스코프가 아닌 URL 파라미터로 설정
`prompt=consent`; // 권한 승인을 항상 요청하는 옵션
console.log(googleAuthUrl);
window.location.href = googleAuthUrl;
};
return (
<button onClick={handleLogin}>로그인 with Google</button>
)
}
이에 따라 백엔드 nextjs 코드는
import { request } from "http";
export async function GET(req: NextRequest) {
try {
const { searchParams } = new URL(req.url);
const code = searchParams.get("code");
if (!code) {
return NextResponse.json({ message: "인증되지 않은 User 입니다." }, { status: 400 });
}
const requestBody = {
code: code,
client_id: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_ID!,
client_secret: process.env.NEXT_PUBLIC_GOOGLE_CLIENT_SECRET!,
redirect_uri: process.env.NEXT_PUBLIC_GOOGLE_REDIRECT_URI!,
redirect_uri: process.env.NEXT_PUBLIC_GOOGLE_LOGIN_REDIRECT_URI!,
grant_type: "authorization_code",
access_type: "offline",
};
const data = new URLSearchParams(requestBody);
const sendData = new URLSearchParams(requestBody).toString();
const tokenResponse = await axios.post("https://oauth2.googleapis.com/token", data.toString(), {
const tokenResponse = await axios.post("https://oauth2.googleapis.com/token", sendData, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
})
...
return NextResponse.json({})
}
프론트에서 보낸 요청 중, url을 파싱해 'code' 만 변수로 저장했고,
구글에 승인 요청을 위해 token 요청을 보냈다.
이렇게 했을 떄는 tokenResponse를 확인할 수 있었는데 문제는 그다음이었다...........ㅠ
redirect_uri_mismatch....
redirect_uri_mismatch 문제가 발생한건
프론트의 코드를 수정하면서 였다.
기존에 구현한 방식은 팝업으로 구글 로그인이 뜨는게 아닌 페이지를 직접 이동시켰기 때문에 서버와의 통신 중에 로딩 페이지를 구현해 보여줘야 한다.
사실 이걸 어떻게 해야할까 고민하다가 @react-oauth/google을 알게되었,,,,
useGoogleLogin 도입
export default function LoginComponent() {
const googleLogin = useGoogleLogin({
scope: "email profile",
onSuccess: async authCode => {
try {
const response = await axios.post("/api/auth/login/callback", { authCode: authCode.code });
console.log(response);
} catch (err: any) {
setMessage(err.response.data.message);
}
},
onError: err => console.log(err),
flow: "auth-code",
});
return (
<button onClick={googleLogin}>로그인 with Google</button>
)
}
공식 문서에 보면 onSuccess나 onError로 간단하게 구현할 수 있다는걸 확인할 수 있었다.
이걸 바꾸고 서버에서 url을 파싱하는 부분만 수정하면 완료될줄 알았다ㅠㅠㅠㅠㅠㅠㅠ
그땐 몰랐지...4시간이나 삽질하고 GPT랑 싸우고 있을줄은......
확실히 코드는 간결해졌고 따로 로딩 UI를 구현하고 보여주지 않아도 된다고 생각했다.
(client_id, clinet_secert, redriect_uri를 설정하지 않아도 자동으로 처리되는 듯...?)
그리고 백엔드 코드를 변경했다.
export async function POST(req: NextRequest) {
try {
const { authCode } = await req.json();
if (!authCode) {
return NextResponse.json({ message: "인증 코드가 없습니다." }, { status: 400 });
}
const requestBody = {
code: authCode,
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
grant_type: "authorization_code",
};
const tokenResponse = await axios.post("https://oauth2.googleapis.com/token", requestBody, {
headers: {
"Content-Type": "application/x-www-form-urlencoded",
},
});
return NextResponse.json({});
}
url을 파싱할 필요가 없었고 authCode를 콘솔로 찍었을때도 문제가 없었다.....
근데....ㅎ
redirect_uri_mismatch 에러가 발생했고 구글링해본 결과 대부분은 배포 환경에서 발생하는 문제였기 때문에 관련된 정보를 찾기가 어려웠다.
별짓 다해보다가 어떤 글을 봤었는데 구글에서 사용자가 구글 로그인을 승인했을 때 보내주는 승인코드가 백엔드로 들어가는 과정에서 문제가 있었다는 글을 봤다.
그래서 백엔드 콘솔에 찍어봤는데
이렇게 code값이 확인되었다.
바로 클라이언트 콘솔에서 code값을 찍어봤다.
동일한 에러였다
구글에서 보내준 코드는 4/로 시작하는데 백엔드에 들어간 code는 / 대신 "%2F"가 들어가 있었고 이걸 인코딩한다고 GPT랑 싸웠다....
그리고 redirect_uri가 문제였기 때문에 백엔드에서 redirect_uri를 확인해봤더니
이 아이도 %를 포함한 문자열이 들어가있었다.
근데 결과적으로 다 실패했고
알고보니 원래 구글 token 보낼때 저렇게 변형되어서 들어가는거 같더라...? 이건 확실하지 않음
다시 구글링.....
진짜 도움되는 글을 하나 찾아 해결 할 수 있었다.
해결...
redirect_uri에 postmessage를 넣으라는 글이었다. 참조
프론트와 서버에 각각 redirect_uri를 직접 넣지 않고 postmessage를 넣었더니!!!
프론트코드
export default function LoginComponent() {
const googleLogin = useGoogleLogin({
scope: "email profile",
onSuccess: async authCode => {
try {
const response = await axios.post("/api/auth/login/callback", { authCode: authCode.code });
console.log(response);
} catch (err: any) {
setMessage(err.response.data.message);
}
},
onError: err => console.log(err),
flow: "auth-code",
redirect_uri: "postmessage",
});
return (
<button onClick={googleLogin}>로그인 with Google</button>
)
}
flow 다음에 redirect_uri 추가!
백엔드코드
export async function POST(req: NextRequest) {
try {
const { authCode } = await req.json();
if (!authCode) {
return NextResponse.json({ message: "인증 코드가 없습니다." }, { status: 400 });
}
const requestBody = {
code: authCode,
client_id: process.env.GOOGLE_CLIENT_ID!,
client_secret: process.env.GOOGLE_CLIENT_SECRET!,
grant_type: "authorization_code",
redirect_uri: "postmessage",
};
...
return NextResponse.json({});
}
드디어 해결!!!
이렇게 토큰 데이터를 받아왔다*_*
Reference
[리빙포인트] google oauth client에서 계속해서 400 redirect_uri_mismatch 가 나는 경우
아티클을 쓰기에는 너무 짧고, 안쓰기엔 아까운 글들을 공유하는 개발 리빙 포인트 시간입니다.
medium.com
Goolge OAuth2 로그인 구현 with React
Google Cloud Platform에서 OAuth 설정 먼저하자 구글 클라우드 콘솔에서는 OAuth 동의화면과 사용자 인증 정보를 등록해야 한다. OAuth 동의화면 먼저 OAuth 동의화면에서는 앱 이름, 사용자 지원 이메일과
berom.tistory.com
'Dev-log > ERROR' 카테고리의 다른 글
(Sourcetree)Git Clone error- 유효한 소스 경로/URL이 아닙니다. (0) | 2024.04.06 |
---|