프론트엔드 프로젝트에 카카오 API와 Firebase를 연동하는 방법입니다.
REST API를 이용한 방식입니다.
카카오 developers console에서 애플리케이션을 추가해주었습니다.
앱키에서 REST API 키를 .env
파일에 추가했습니다.
NextJS에서는 NEXT_PUBLIC_KAKAO_REST_API=
방식으로 저장해주어야 합니다.
아직 배포 자동화를 연결하지 않은 상태이기 때문에 3000번 포트를 사용했습니다. 저는 리다이렉트 받을 URI를 네이버도 사용해야하기 때문에 명시적으로 /oauth/kakao
로 기재해주었습니다.
Redirect URI 가 필요한 이유 ?
- 카카오 document에 올라온 동작 방식이다. /oauth/kakao 페이지에서 인가코드와 access_token 을 받고 사용자 정보를 받을 수 있다. (email, 닉네임 등) 그 때 user가 있으면 jwt 토큰을 발급해주어야하고, 없으면 root page로 리다이렉트 시켜주어야한다. 그런 작업을 /oauth/kakao에서 해주는 것이다.
위의 UI에서 '카카오로 로그인하기'를 a태그로 마크업해준다.
<Link href="https://kauth.kakao.com/oauth/authorize?response_type=code&client_id=${process.env.NEXT_PUBLIC_KAKAO_REST_API}&redirect_uri=${REDIRECT_URI}">
<a>카카오로 로그인하기
<Link/>
.env
폴더의 카카오 REST_API 값이다.https://localhost:3000/oauth/kakao
가 될 것이다.이렇게 마크업을 해주고, 회원이 동의하고 로그인한다면 리다이렉트 페이지로 이동하게 된다.
리다이렉트 페이지의 routing 처리는 이렇게 해주었다.
│ ├── oauth
│ │ └── kakao
│ │ └── index.tsx
useEffect 훅을 이용하여 컴포넌트가 올라오기 전, 인가 코드를 받아올 수 있도록 한다.
/* oauth/kakao/index.tsx */
useEffect(() => {
const code = new URL(window.location.href).searchParams.get('code');
}, []);
인가 코드를 통해 카카오 측에 user에 대한 정보를 받아올 수 있다.
카카오에서 user 정보를 받아올 수 있는 access token이다. access token 그대로 client 사용자에게 주어도 할 수 있는게 없다. 우리는 access token을 카카오 측에서 받고 그것을 이용해서 user 정보를 받아온다.
/* oauth/kakao/index.tsx */
useEffect(() => {
const code = new URL(window.location.href).searchParams.get('code');
getToken(code)
}, []);
getToken 이라는 함수를 만든다. parameter로 위에서 받았던 인가 코드를 입력해준다.
axios를 이용하여 post 요청을 보냈다. url은 고정이다.
const getToken = async (code: string | null) => {
try {
const {
data: { access_token },
} = await axios({
url: `https://kauth.kakao.com/oauth/token`,
method: 'post',
params: {
grant_type: 'authorization_code',
client_id: process.env.NEXT_PUBLIC_KAKAO_REST_API,
REDIRECT_URI: REDIRECT_URI,
code: code,
},
});
return access_token;
} catch (e) {
console.log(e);
}
};
`
grant_type : 'authorization_code'
client_id : 발급 받은 REST API 키
redirect_uri : 설정해둔 redirect uri
code : 위에서 받아온 인증 코드
REST API를 이용하여 클라이언트 측에서 유저 정보를 가져오는 것은 불가능하다.
프론트엔드 측에서 access token 을 갖고 유저 정보를 가져오는 방법은 자바스크립트 SDK 를 이용하는 방식이다. (그러나 자바스크립트 SDK 는 모바일 환경에서 로그인이 되지 않는 문제가 발생한다. 모바일에서도 로그인을 실행시키고 싶다면 백엔드가 필요하다.)
REF | 카카오의 CORS 정책
즉, 프론트엔드 스크립트에서 kapi.kakao.comAPI호출은 허용되지 않습니다.
CORS가 열려있는 kauth.kakao.comAPI의 경우에도 리다이렉트 URI로 되돌아가야하는 로그인(인가요청, 추가 항목 동의받기), 로그아웃은 ajax 방식으로 호출 할 수 없습니다. 즉, kauth.kakao.comAPI는 토큰 요청만 가능합니다.
결론적으로 Express를 이용하여 백엔드를 구축했다.
Poiemaweb - express 를 참고했고 module 방식이 편해서 package.json에서 type을 module로 지정해주었다. 추가적으로 nodemon
을 설치하여 백엔드 서버가 업데이트 되어 보여줄 수 있도록 했다.
const corsOption = {
origin: "http://localhost:3000",
credentials: true,
};
app.use(cors(corsOption));
cors 라이브러리를 설치하여 프론트엔드 Port(3000번)을 허용해주었다.credentials 속성이 없다면 추후에 프론트엔드측에 cookie를 백엔드가 세팅할 수 없기 때문에 credential 을 true로 허용해주었다.
백엔드 (8000번 포트 사용했습니다.)에서 /oAuth/kakao 로 요청받기로 했다.
위에서도 언급했지만 kapi
는 javascript 환경에서 돌아가지 않기 때문에 node.js 환경을 구축해주어야합니다.
위의 방식은 동일 Organization 에서 다른 Repository 로 Express를 구축하는 방법입니다. (당연히 두 개의 Port 번호가 다르기 때문에 프론트 - 백엔드 간의 CORS 에러를 핸들해주어야합니다.) 하지만 수정 후에는 Next JS의 Custom Server 기능을 사용해보려 합니다. 사용 방법은 Express와 동일합니다. 그러나 프론트와 백엔드가 동일 Port에서 돌아가기 때문에 그 간의 CORS 에러를 고민하지 않으셔도 됩니다.
Next JS의 Custom Server 에서 어떻게 홈페이지를 구축했는지 작성해두었습니다.
/* 프론트의 oauth/kakao/index.tsx */
const { data } = await axios({
url: 'http://localhost:8000/oAuth/kakao',
/* custom 서버 이용시 : /oAuth/kakao 로 설정 */
method: 'post',
data: { access_token: access_token },
withCredentials: true,
});
프론트측에 access_token을 data로 전달해준다.
백엔드측에서는 access_token 을 받고 kapi에 정보를 요청한다.
/* 백엔드의 index.js */
app.post("/oAuth/kakao", async (req, res) => {
try {
const { access_token } = req.body;
const {
data: { kakao_account },
} = await axios({
url: "https://kapi.kakao.com/v2/user/me",
method: "get",
params: {
secure_resource: true,
property_key: ["kakao_account.email"],
},
headers: {
Authorization: `Bearer ${access_token}`,
},
});
});
사용자 측의 Option(필수 아님)
- secure_resource : https 를 사용할 것인지, http 를 사용할 것인지 적는다.
- property_key는 요청할 데이터를 적어두면 된다.
위에서 사용자의 email 주소를 찾고 Firebase에서 email에 해당하는 user가 있는지 없는지 찾는다.
fireStore의 Collection 은 이런 구조이고, 임시 목데이터를 넣어둔 상태다.
백엔드의 firebase.js 파일 내에서 파이어베이스 document를 보고 초기 설정은 해둔 상태이다.
collection 을 이용하여 db에 존재하는 "user"이름의 collection을 userRef로 잡는다.
인자로 전달받은 email과 일치하는 정보가 있는지 확인하고 있다면 true를 리턴해주고, 있다면 return false를 리턴해준다.
const userRef = collection(db, "user");
export const findUserInfo = async (email) => {
try {
let userCollectionID = null;
const q = query(userRef, where("email", "==", email));
const querySnapshot = await getDocs(q);
querySnapshot.forEach((doc) => {
userCollectionID = doc.id;
});
if (userCollectionID) {
return true;
}
return false;
} catch (e) {
console.log(e);
}
};
firebase에서 유저가 있다면 jwt를 이용하여 토큰을 발행해주고, 로그인을 성공시켜주어야 한다.
jsonwebtoken
라이브러리를 사용했다. .env
파일에 JWT_SECRET_KEY=
를 설정해준다. (아무 단어나 설정해주어도 된다.)
jwt.sign 함수를 이용하여 jwt 토큰을 생성해준다. id는 사용자의 email로 설정해주었고 유효기간은 60m (1시간)으로 설정해주었다. .env
파일에서 설정해준 jwt 키를 token에 담아준다.
/* 백엔드의 jwt.js 파일 */
export const makeToken = async (email) => {
const token = jwt.sign(
{
id: email,
},
process.env.JWT_SECRET_KEY,
{
subject: "bodyBuddy jwtToken",
expiresIn: "60m",
issuer: "bodyBuddy",
}
);
return token;
};
result가 true 인 경우(user가 존재해서 로그인을 성공시켜야하는 경우)에 res.cookie를 이용하여 생성한 jwt 토큰을 담아주고 데이터는 result :boolean 값으로 보내준다.
/* 백엔드의 index.js*/
const result = await findUserInfo(email);
const token = await makeToken(email);
// 회원이 있는 경우
if (result) {
res.cookie("jwt_Token", token);
res.send({
result: true,
});
}
그러면 프론트에 로그인이 성공하고, 쿠키가 담긴 것을 확인 할 수 있다.
많은 도움이 되었습니다. 감사합니다.