1. Oauth2.0란?
2. Oauth2.0의 플로우
(1) Client
: 운영되는 사이트
(2) Authorization Server
: google, naver 등 인증에 필요한 데이터를 제공해주는 서버
(3)Resource Server
: google, naver 등 사용자의 개인정보를 가지고 있는 서버
(4) Server
: 자신이 운영하는 사이트의 서버
(5) DB Server
: 자신의 사이트의 DB서버
3. 구현 시작
단계별로 구현을 해보겠습니다.
제 프로젝트 환경은 (클라이언트 : NextJS / 백엔드 : NodeJs / DB: MYSQL)입니다.
Step 1 :Naver Developers에서 Naver api 요청 등록하기
Step2 : 클라이언트에서 Naver 로그인 창 요청하기
페이지 요청 시에는 Step 1에서 발급 받은 CLIENT_ID , Redirect_URL,state정보가 필요합니다..
https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=뒤에 client_ID를 넣주고,
"&redirect_uri="뒤에 CALLBACK_URL을 넣어 주고, "&state=" 뒤에
임의의 문자들을 넣어주면 됩니다.
//NextJS 클라이언트
const goNaverLogin = () => {
window.location.href =
"https://nid.naver.com/oauth2.0/authorize?response_type=code&client_id=" +
CLIENT_ID +
"&redirect_uri=" +
CALLBACK_URL +
"&state=" +
"asd2222222";
};
//밑에 버튼 코드 jsx코드 생략
Step 3 Node에 전달된 코드로 Naver 서버에 accesstoken 및 회원정보 요청
//node app.js
app.get("/naver/callback/oauth", async (req, res) => {
// console.log(req.query.code); // 1. 일단 authorization code 받기 완료
const code = req.query.code; // callback으로 전달받은 authorization code
const state = req.query.state;//callback으로 전달받은 state
const client_id = process.env.NAVER_CLIENT_ID;// developer에서 받은 id
const client_secret = process.env.NAVER_CLIENT_SECRET;
//developer에서 받은 secret
const redirectURI = process.env.NAVER_REDIRECT_URL;
//이건 여기서는 사용되지 않을 예졍이지만, 등록해줘야하는 부분이라 등록해줬다.
const naver_api_url = `https://nid.naver.com/oauth2.0/token?grant_type=authorization_code&response_type=code&client_id=${client_id}&client_secret=${client_secret}&redirect_uri=${redirectURI}&code=${code}&state=${state}`;
// 네이버 서버에 accesstoken 및 회원 정보 요청 url
const options = {
url: naver_api_url,
headers: {
"X-Naver-Client-Id": client_id,
"X-Naver-Client-Secret": client_secret,
},
json: true,
};// 이렇게 options를 담아서 요청하는 것이 규칙인 것 같다.
const result = await request.get(options);
// request 객체를 이용해서 요청 후 result에 저장 //const request = require("request-promise");
const token = result.access_token;// 받은 accesstoken 저장
const info_options = {
url: "https://openapi.naver.com/v1/nid/me",
headers: { Authorization: "Bearer " + token },
};//토큰을 해제하기위해 구성
const info_result = await request.get(info_options);
// 결과저장
const info_result_json = JSON.parse(info_result).response;
// 여기서 로그인한 회원의 정보가 저장된다.아래와 같이 네이버 개인정보를 받아올 수 있다.
/*
Ex)
{
id: '!@!!!!',
nickname: '별명',
gender: 'M',
email: '이메일',
name: '이동기',
birthday: '02-14',
birthyear: '2000'
}
*/
});
Step 4 전달받은 개인정보로 MYSQL에 회원 중복 or 가입여부 확인 후 클라이언트에 , 토큰을 리다이렉트해준다.
//node app.js
// 위의 정보 가져오는 api에 이어지는 코드들이다.
//우선 비동기 를 이용할 함수를 하나 만들어줬다. 아래에서 확인할 수 있다.
const checkUserQuery = async (SQLdata) => {
return new Promise((resolve, reject) => {
connection.query(confirmIDSQL, SQLdata, (error, result) => {
if (error) {
reject(error);
} else {
resolve(result);
}
});
});
};
// 받아온 유저데이터로 MYSQL에 유저 확인
const SQLdata = [info_result_json.email];//email저장
const confirmIDSQL = "SELECT U_IDX,U_EMAIL FROM USER_TABLE WHERE U_EMAIL=? ";//유저 가입확인을 위한 SQL
let count = 0;
let userData = "";
try {
//위에서 만든 함수에 쿼리문을 넣어 이메일을 통해 SELECT된 갯수를 result에 저장한다.
const result = await checkUserQuery(SQLdata);
count = result.length;// count=0 => 신규유저, count=1 기존회원
userData = result;
} catch (error) {
console.error(error);
res.status(500).send("Internal server error");
return;
}
//count=0이면, 신규회원이므로 , INSERT문으로 가입 후 , email정보만 토큰에 담아서 클라이언트로 리다이렉트해준다.
if (count === 0) {
const birth = info_result_json.birthyear + "-" + info_result_json.birthday;
const insertData = [
info_result_json.email,
"naverpass",
info_result_json.name,
info_result_json.gender,
birth,
];
const insertSQL =
"INSERT INTO USER_TABLE(U_EMAIL,U_PASSWORD,U_NAME,U_GENDER,U_BIRTH) VALUES(?,?,?,?,?)";
// 신규 유저 저장 쿼리문 실행
connection.query(insertSQL, insertData, (error, rows) => {
const email = info_result_json.email;
const booleanLogin = "true";
const message = "로그인 완료";
console.log(rows.insertId);
const result = [{ U_IDX: rows.insertId, U_EMAIL: email }];
//accessToken 생성
const accessToken = jwt.sign(
{
email: email,
result,
},
process.env.JWT_SECRET_KEY,
{
expiresIn: "30m",
issuer: "hong",
}
);
// cookie에 accessToken을 담아 전달
res.cookie("accessToken", accessToken, {
path: "/",
secure: true,
httpOnly: true,
sameSite: "none",
});
// 클라이언트에 로그인 여부를 쿼리스트링에 담고 원하는 페이지로 리다이렉트 요청
res.redirect(
`https://club-front.vercel.app/Login/?login=${booleanLogin}&message=${message}`
);
});
}
// count!==0이므로 이미 회원이다. 그러므로 accesstoken 발급 후, 리다이렉트 해준다. if문의 insert문 제외 동일 과정이다.
else {
console.log("이미 회원임이다");
const result = userData;
const booleanLogin = "true";
const message = "로그인 완료";
// 이미 회원이므로 토큰에 담아서 보내주자.
const email = info_result_json.email;
const accessToken = jwt.sign(
{
email: email,
result,
},
process.env.JWT_SECRET_KEY,
{
expiresIn: "30m",
issuer: "hong",
}
);
res.cookie("accessToken", accessToken, {
path: "/",
secure: true,
httpOnly: true,
sameSite: "none",
});
res.redirect(
`https://club-front.vercel.app/Login/?login=${booleanLogin}&message=${message}`
);
}
});
Step 5 완료
테스트 후 완료됐다.
4. 느낀점
플로우를 먼저 이해한 뒤 구현을 해보았다. 플로우를 이해하는데는 크게 어려움은 없었다.
그러나 Next,node를 이용한 Oauth 구현 정보를 찾는 과정에서 Next-auth,passporte 등의 라이브러리나 next에서 제공해주는 방법으로 구현하는 방법이 나와있었다.
Next-auth로 구현해보려했으나, 나와있는 정보로는 DB까지 저장하고 구분하는 플로우까지 구현하기에는 나와있는 정보가 부족해서 사용하지 못하고 passport라이브러리도 예상치 못한 오류들을 맞딱들여서 성공하지 못했다.
결국 , 위의 플로우대로 단계별로 나눠서 구현을 시도하니 위와같이 구현을 성공했다.
다행히 구현하는데는 성공해서 만족스럽지만, 나중에 Next-auth나 passport로 구현하는 방법도 좀 더 찾아보고 시도해봐야겠다.