

현재 나는 Next.js 14를 사용한 플레이리스트 프로젝트를 진행 중이다.
여기서 Google과 Kakao 소셜 로그인을 구현하기로 결정을 했고, 이와 관련된 여러 문서들을 찾아보았다.
구글 로그인 API의 흐름은 이러하다.
구글 로그인 창을 클라이언트에게 보여준다. => client는 로그인을 시도한다. => 동의 항목을 체크하여 네이버 API 서버에 넘긴다. => 전달 받은 정보를 가지고 구글 API 서버는 인가 코드를 발급해준다. => 인가 코드를 통해 로그인을 유지할 수 있는 토큰을 발급해준다.
나는 여기서 구글 access token이 아닌 구글 id token을 발급 받아 그거로 회원가입을 진행할 예정이다.
소셜 로그인의 경우, 각 플랫폼으로 부터 발급받은 id token을 프로젝트의 access token으로 교환하는 방식으로 이루어진다.
내 프로젝트의 경우에는 회원가입과 로그인시 같은 버튼을 사용한다.
회원가입시 바로 로그인이 되어 메인 페이지로 이동하며, 이미 회원가입 되어 있는 계정으로 로그인 시에도 메인 페이지로 이동한다.
이미 다 구현을 해둔 상태이지만, 한 번 정리를 하고 지나가도록 하겠다. (다음에 또 찾아볼 일 없도록)
구글 개발자 센터 API 문서를 찾아보고 해도 좋고, 이 글을 보고 따라 해도 좋다.
- 구글 개발자 센터 API 문서 :
https://cloud.google.com/identity-platform/docs/use-rest-api?hl=ko#section-verify-custom-token
구글 로그인 API 연동은 크게 두 가지로 나뉜다.
1. Google Developers 설정
2. 초기화 및 로그인 구현하기
----------------------------------------------------------------------------------------------------------------------------------------------------
1. Google Developers 설정
먼저, https://console.cloud.google.com로 접속한다.
접속을 했다면, 검색란에 "Oauth"를 검색한 후 "OAuth 동의 화면"을 클릭한다.

그 다음에는 좌측 상단에서 프로젝트를 선택한다.
내가 진행하려고 하는 프로젝트가 생성되어 있지 않다면, 우측 상단의 "새 프로젝트"를 클릭하면 된다.


정보 입력 후 새 프로젝트를 만들어준다.
프로젝트를 만들었다면, 좌측 사이드 메뉴바에서 "OAuth 동의 화면"을 클릭한다.
User Type을 "외부"로 선택 후 만들기 버튼 클릭.

앱 정보를 입력한 후 저장하면 된다.
필수 데이터들만 일단 입력해두면 된다.(도메인 및 홈페이지 정보는 생략해도 됨)


저장 후 계속 버튼을 누르면, "범위" 구간으로 넘어가게 된다.
범위 추가 또는 삭제 버튼을 누르고, 최상단 3개 항목을 선택 후 업데이트 버튼을 누른다.
그리고 저장 후 버튼을 누르면, "테스트 사용자" 구간으로 넘어가게 된다.


"테스트 사용자" 구간에는 구글 로그인 테스트를 위한 계정을 추가해주면 된다.
게시 상태가 '테스트 중'으로 설정된 동안에는 테스트 사용자만 앱에 액세스할 수 있다고 한다.
(앱 인증 전에 허용되는 사용자 한도는 100명이며 앱의 전체 수명 주기에서 계산)
+ ADD USERS 버튼을 클릭하고 내 구글 계정 3개를 집어넣었다.
(이럴 때는 구글 계정이 많아서 참 좋다...)
저장 후 계속 버튼을 누르면 "요약" 구간으로 넘어가게 된다.
여기서는 따로 할 게 없다. 다시 사이드 메뉴바에서 "OAuth 동의 화면"을 누른 후 앱 게시를 눌러준다.
이러면 테스트 모드로 앱이 게시된 것이다.



이 다음에는 사이드 메뉴바에서 "사용자 인증 정보"로 넘어간다.
"사용자 인증 정보 만들기" 버튼을 클릭 - "OAuth 클라이언트 ID"를 클릭한다.
여기서 OAuth 클라이언트 ID를 생성할 수 있다.


필수 정보들을 입력해주면 된다.
1. 애플리케이션 유형 항목은 "웹 애플리케이션"을 선택해주면 된다.
2. 이름 항목에 애플리케이션 이름을 입력해주면 된다.
3. 승인된 자바스크립트 원본 항에 URI 추가 버튼을 눌러 구글 로그인을 사용할 홈페이지 주소를 입력해주면 된다.
=> 홈페이지가 배포되지 않은 상황이니까 http://localhost:3000를 입력했다.
4. 승인된 리디렉션 URI 항목에 URI 추가 버튼을 눌러 구글 로그인 후 리디렉션할 주소를 입력해주면 된다.
=> 이 역시 홈페이지가 배포되지 않은 상황이니까 http://localhost:3000/google-auth 이런 식으로.
=> 나는 google-auth 페이지로 리디렉션 시킬 것이다.
다 입력했다면 만들기 버튼을 클릭한다.


그러면 최종적으로 구글 로그인 연동 API 정보를 확인할 수 있다.
client ID와 client 보안 비밀번호, 리디렉션할 주소는 인가 코드를 발급 받는 과정에서 꼭 필요하므로 기억해두자.
(사실 나중에 다시 찾아볼 수 있다. 그냥 중요하다는 제일 중요한 핵심이라는 얘기다.)

----------------------------------------------------------------------------------------------------------------------------------------------------
2. 초기화 및 로그인 구현하기
이제 발급 받은 구글 로그인 연동 API 정보로 로그인을 구현해보도록 하겠다.
내 프로젝트에서 로그인 기능 구현을 위한 과정은 이러하다.(구글 로그인 연동 API 정보를 받은 후부터)
=> 인가 코드 받기 - id token 받기 - id token으로 회원가입 하기 - id token으로 로그인 하기
로그인 : 구글 api로부터 발급 받은 id token을 프로젝트 access token으로 교환하는 방식으로 로그인이 진행된다.
인가 코드 받기는 클라이언트에서, 나머지 과정은 서버에서 처리할 예정이다.
서버는 이미 구현되어 있다. 난 이걸 가져다 사용할 예정이다.
- 소셜 로그인 회원가입
Muwith에 가입한 이력이 없는 소셜 계정의 경우에는 아래와 같은 요청을 통해 가입이 가능하다.
- Method : POST
- Path : /auth/signup-by-sso
- Request body :
{
platform: 'google' | 'kakao';
token: string;
name: string;
}
- 소셜 로그인
소셜 로그인은 각 플랫폼으로부터 발급받은 access token을
프로젝트의 access token으로 교환하는 방식으로 로그인이 이루어진다.
- Method : POST
- Path : /auth/transfer-sso-token
- Request body :
{
platform: 'google' | 'kakao';
token: string;
}
- Response :
{
accessToken: string;
}
그럼 이제 로그인 기능 구현을 시작해보겠다.
먼저, 인가 코드 받기부터 시작하겠다.
1. 인가 코드 받기
구글 로그인 창을 화면에 노출시키는 방법은
구글 개발자 센터에서 발급 받은 client ID와 Redirect URL 등을 파라미터에 추가하여 호출하면 된다.
반드시 포함해야 하는 파라미터는 response_type, client_id, redirect_uri, state 이렇게 총 4개가 있다.
function doGoogleLogin() {
const url = 'https://accounts.google.com/o/oauth2/v2/auth?client_id=' + process.env.VUE_APP_GOOGLE_CLIENT_ID + '&redirect_uri=' + process.env.VUE_APP_GOOGLE_REDIRECT_URL + '&response_type=code' + '&scope=email profile';
window.location.href = url;
}
- client_id : 구글 개발자 센터에서 발급 받은 JavaScript Key
- redirect_uri : 구글 개발자 센터에서 설정한 Callback URI
http://localhost:3000/google-auth는 구글 개발자 센터에서 설정한 Callback URL이다.
정해둔 uri로 redirect할 수 있도록 프로젝트 내에서 google-auth 페이지를 하나 생성해둬야 한다.
- response_type : 'code'로 고정 (인가코드를 통한 로그인 방식)
- scope : 토큰 발급 이후 user 정보에서 어떤 항목을 조회할 것인가를 띄어쓰기를 구분으로 하여 ' ' 입력하면 된다.
전부 입력해서 url을 채웠다면 window.location.href를 이용하여 입력한 주소로 redirect시키면 된다.
요청 값을 올바르게 설정했다면, 구글 로그인 페이지가 화면에 뜨게 될 것이다.
이 과정에서 계정 선택을 완료하게 되면, 내가 적어두었던 Redirect URL로 리디렉션하게 된다.
=> 나의 경우에는 http://localhost3000/google-auth 페이지로 리디렉션하게 될 것이다.
(이에 따라 프로젝트 내에도 google-auth 페이지가 생성되어 있어야 한다.)
구글 로그인이 성공하면 어떻게 되는지 URL을 보여주고 싶지만, 이미 구현이 끝난 후에는 확인이 힘들다...
여기서 확실하게 알아야 하는 것은, 구글 로그인 성공 후에 URL의 경우
처음에는 내가 Redirct URL로 설정해뒀던 것이 오고, 그 다음에는 code라는 이름의 파라미터가 오게 된다.
이 code를 이용해 로그인 처리에 필요한 구글 id token을 받을 것이다.
일단 id token을 받기 위해서는 code가 반드시 필요하기 때문에 code를 먼저 얻어보도록 하겠다.
export async function GET(request: NextRequest) {
const { searchParams } = new URL(request.url);
const googleAuthCode = searchParams.get('code');
}
Next.js 14에서 cookies.set을 사용하기 위해서는
Server Action 또는 Route Handler 환경에 있어야 한다고 해서 Route Handler로 만들어보았다.
Route Handler는 경로 처리기로, 만들어둔 google-auth 폴더 내에 route.ts 파일을 생성해준 후,
그 안에 이렇게 코드를 작성하면 된다.
NextRequest는 Web Request API를 확장시킨 것이다.
request는 리소스 요청을 나타낸다. request 객체의 url 속성을 통하여 요청 URL을 받을 수 있다.
그리고 URL 생성자를 통하여 URL 객체를 반환한 다음, 구조 분해 할당을 통하여 searchParams 속성을 추출한다.
URL 객체의 searchParams 속성은 URL의 쿼리 문자열을 파싱하여 URLSearchParams 객체를 반환한다.
URLSearchParams 객체는 쿼리 문자열의 각 항목을 검색하고 조작할 수 있는 편리한 방법을 제공한다.
ex) 만약 다음과 같은 URL이 있다고 가정해보자. => /api/users?name=john&age=30
이 코드를 실행하면 searchParams 객체는 다음과 같은 형태가 된다.
URLSearchParams {
"name" => "john",
"age" => "30"
}
이와 같은 방법으로, const googleAuthCode = searchParams.get('code'); 코드를 통하여 code를 얻을 수 있다.
2. 구글 ID token 얻기
이제 얻은 code를 통하여 구글 ID token을 받아볼 것이다.
인가 코드를 통해 구글에서 로그인 token을 가져오는 API는 이러하다.
=> https://oauth2.googleapis.com/token
로그인 token을 가져오기 위하여 Body에 담아야 할 것들은 이러하다.
- code : client 페이지에서 얻은 인가 코드를 사용 하기 때문에 "code"로 고정.
- client_id : 구글 개발자 센터에서 발급 받은 Client ID
- client_secret : 구글 개발자 센터에서 발급 받은 Client Secret
- redirect_uri : 구글 개발자 센터에서 등록한 redirect_uri
- grant_type : 'authorization_code' 로 고정 (인가코드를 통한 로그인 방식)
이 과정을 통하여 구글 ID token을 받을 수 있다.
const googleIdToken = await fetch('https://oauth2.googleapis.com/token', {
method : 'POST',
headers : {
'Content-Type': 'application/json',
},
body : JSON.stringify({
client_id : GOOGLE_API_CLIENT_ID,
client_secret : GOOGLE_API_CLIENT_SECRET,
code: googleAuthCode,
redirect_uri : 'http://localhost:3000/google-auth',
grant_type : 'authorization_code',
}),
})
.then((res) => res.json())
.then((result) => result.id_token);
정상적으로 응답 받았을시, 응답 데이터는 이러하다.
난 여기서 id_token만 꺼내 쓸 것이다.
{
"access_token" : "---------------------------",
"expires_in" : "-----",
"scope" : "-------------------------------------",
"token_type" : "------------------------------",
"id_token" : "---------------------------------"
}
위에서 난 이미 만들어진 서버의 요청 사항을 적어뒀었는데,
거기에 맞춰서 로그인과 회원가입을 구현해보도록 하겠다.
// 회원가입이 되어 있지 않은 경우에만 회원가입 진행
const signUpInfo = await fetch(BASE_URL2 + '/auth/signup-by-sso', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: 'google',
token: googleIdToken,
name: `userName${Math.random()}`,
}),
})
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res.json();
})
.then((result) => result)
.catch((error) => {
console.error('회원가입 중 오류 발생', error);
if (error.status === 400) {
// 이미 가입한 유저인 경우
// 이미 가입된 회원이므로 넘어감
} else {
console.error('알 수 없는 오류가 발생했습니다.');
}
});
// 로그인
const accessToken = await fetch(BASE_URL2 + '/auth/transfer-sso-token', {
method: 'POST',
headers: {
'Content-Type': 'application/json',
},
body: JSON.stringify({
platform: 'google',
token: googleIdToken,
}),
})
.then((res) => {
if (!res.ok) {
throw new Error(res.statusText);
}
return res.json();
})
.then((result) => result.accessToken)
.catch((error) => {
console.error('로그인 중 오류 발생', error);
if (error.status === 401) {
// Google 로그인에서 잘못된 토큰이 입력된 경우
console.error('잘못된 Google ID token입니다.');
} else if (error.status === 400) {
// 잘못된 소셜 로그인 유형이 입력된 경우
console.error('잘못된 SSO platform입니다.');
} else if (error.status === 403) {
// 회원가입 하지 않은 유저인 경우
console.error('회원가입 하지 않은 유저입니다.');
} else {
console.error('알 수 없는 오류가 발생했습니다.');
}
});
이렇게 구현을 해보았다.
처음에 회원가입을 시도하고, 회원가입이 되어 있지 않은 경우에는 회원가입을,
회원가입이 되어 있는 경우에는 로그인을 통하여 내 프로젝트 내의 accessToken을 최종적으로 얻게 되었다.
이러면 로그인이 된 것인데, accessToken을 어딘가에 담아둬야 하므로 나는 cookie를 사용할 것이다.
cookie를 사용하기 위한 빌드업(Route Handler 사용)은 전부 해뒀으니,
이제 next/headers의 cookies를 import한 후
cookies().set("key name", 값, ,options); 이런 식으로 값을 cookie에 저장할 수 있다.
난 option으로 expires를 넣어줬는데, 이건 cookie의 만료 시간을 결정하는 것이다.
expires는 시간으로 나타내야 한다.
구현되어 있는 서버의 최대 cookie 만료 기간은 하루이기 때문에, Date.now()에 하루를 의미하는 24시간(초)을 더한다.
그런 다음 http://localhost3000으로 리디렉션 한다.
const oneDay = 24 * 60 * 60 * 1000;
cookies().set('accessToken', accessToken, { expires : Date.now() + oneDay });
redirect('/');
'Next.js' 카테고리의 다른 글
Next.js 14) 서버 컴포넌트에서 데이터 패칭 (0) | 2024.02.24 |
---|---|
Next.js 14) Suspense를 사용한 로딩 UI (0) | 2024.02.24 |
Next.js 14) 대충 내가 헷갈려서 적어본 url 받는 방법. (0) | 2024.02.24 |
Next.js 14) 클라이언트 컴포넌트 아래의 서버 컴포넌트? (오늘의 삽질 (0) | 2024.02.24 |
Next.js 14) 현재 URL의 쿼리 문자열 얻어내기 (+Link (0) | 2024.02.23 |
github : https://github.com/dnjfht
포스팅이 좋았다면 "좋아요❤️" 또는 "구독👍🏻" 해주세요!