2022.08.24 - [Backend] - [oAuth2] React와 node.js로 google oAuth2 사용하기[1. 설정]
1. Google oAuth 인증 순서
이전 글에서 oAuth를 사용하기 위한 설정을 했다면 이번 시간에는 Google Server에 토큰을 요청해서 Access Token과 Refresh Token (갱신 토큰)을 받아보려 한다.
코드를 구현하기에 앞서 Google oAuth의 인증 순서를 먼저 알아보자.
아래 이미지는 웹서버 어플리케이션의 인증 순서이다.
클라이언트 측 인증 플로우도 있지만 클라이언트에서 다 구현하게 될 경우 Access Token만 사용할 수 있다. Refresh Token을 사용하기 위해서는 중요한 정보인 CLIENT SECRET 키가 필요하기 때문이다..
출처: https://developers.google.com/identity/protocols/oauth2#webserver
이미지를 간략하게 순서대로 설명하자면
1. 사용자가 google 로그인 버튼을 클릭한다.
2. Token 정보를 얻기 위해 애플리케이션이 브라우저를 Google URL로 리다이렉션한다. URL에는 Client ID, Response Type, Scope 등의 여러 매개변수를 같이 보낸다.
3. Google Server는 Access Token과 Refresh Token을 요청할 수 있는 승인 코드(code)를 준다.
4. 애플리케이션에서 받은 승인 코드와 Client Secret 등을 매개변수로 Google Server에 요청을 보낸다.
5. Google Server는 애플리케이션으로 Access Token과 Refresh Token을 보내준다.
6. 응답 받은 Access Token으로 Google API를 요청할 수 있게 되었다.
2. 코드 작성
본인은 googleapis 라이브러리를 사용하지 않고 REST 방식으로 구현함.
쿠키나 세션 등을 사용하지 않고 학습용으로 굉장히 간단하게 구현했음.
1. 사용자가 google 로그인 버튼을 클릭한다.
index.html
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta http-equiv="X-UA-Compatible" content="IE=edge" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>Document</title>
</head>
<body>
<form method="GET" action="/auth/google">
<button type="submit">google login</button>
</form>
</body>
</html>
사용자가 구글 로그인 버튼을 클릭하면 /auth/google API에 요청을 한다.
2. Token 정보를 얻기 위해 애플리케이션이 브라우저를 Google URL로 리다이렉션한다. URL에는 Client ID, Response Type, Scope 등의 여러 매개변수를 같이 보낸다.
const OAUTH_URL = `${AUTHORIZE_URI}?client_id=${CLIENT_ID}&response_type=${RESPONSE_TYPE}&redirect_uri=${REDIRECT_URL}&scope=${SCOPE}&access_type=${ACCESS_TYPE}`;
// 버튼 클릭시 구글 로그인 화면으로 이동
app.get("/auth/google", (req, res) => {
res.redirect(OAUTH_URL);
});
3. Google Server는 url query로 Access Token과 Refresh Token을 요청할 수 있는 승인 코드(code)를 준다.
4. 애플리케이션에서 받은 승인 코드와 Client Secret 등을 매개변수로 Google Server에 요청을 보낸다.
const getToken = async (code) => {
try {
const tokenApi = await axios.post(`https://oauth2.googleapis.com/token?code=${code}&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&redirect_uri=${REDIRECT_URL}&grant_type=authorization_code`);
const accessToken = tokenApi.data.access_token;
console.log(accessToken);
return accessToken;
} catch (err) {
return err;
}
};
// 설정한 리다이렉트 페이지로 이동시 처리할 로직
app.get("/oauth2/redirect", (req, res) => {
const query = url.parse(req.url, true).query;
if (query && query.code) {
getToken(query.code);
}
res.send("");
});
Refresh Token을 받기 위해서는 2번 과정의 AccessType을 offline으로 주어야 한다.
Refresh Token은 첫번째 승인 시에만 반환되기 때문에 DB 등에 저장해두어야 한다.
https://developers.google.com/identity/protocols/oauth2/openid-connect#refresh-tokens
5. Google Server는 애플리케이션으로 Access Token과 Refresh Token을 보내준다.
// Response 샘플
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3920,
"token_type": "Bearer",
"scope": "openid%20profile%20email",
"refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}
6. 응답 받은 Access Token으로 Google API를 요청할 수 있게 되었다.
사용자 정보를 받아오는 함수를 작성해보도록 하겠다.
const getUserInfo = async (accessToken) => {
try {
const userInfoApi = await axios.get(
`https://www.googleapis.com/oauth2/v2/userinfo?alt=json`,
{
headers: {
authorization: `Bearer ${accessToken}`,
},
}
);
return userInfoApi;
} catch (err) {
return err;
}
};
// Response (개인 정보는 지웠음)
{
id: '',
email: '',
verified_email: true,
name: '',
given_name: '',
family_name: '',
picture: '',
locale: 'ko'
}
액세스 토큰 새로고침
Aceess Token이 만료 되었다면 저장되어 있는 Refresh Token을 통해 새로운 Aceess Token을 받을 수 있음.
https://developers.google.com/identity/protocols/oauth2/web-server#offline
전체 코드
const { default: axios } = require("axios");
const express = require("express");
const url = require("url");
require("dotenv").config();
const app = express();
const PORT = 3000;
// express에서 정적 파일 사용하기
// https://expressjs.com/ko/starter/static-files.html
app.use(express.static("public"));
// NOTE process.env는 dotenv라이브러리 사용
const CLIENT_ID = process.env.CLIENT_ID;
const CLIENT_SECRET = process.env.CLIENT_SECRET;
const AUTHORIZE_URI = "https://accounts.google.com/o/oauth2/v2/auth";
const REDIRECT_URL = "http://localhost:3000/oauth2/redirect";
const RESPONSE_TYPE = "code";
const SCOPE = "openid%20profile%20email";
const ACCESS_TYPE = "offline";
const OAUTH_URL = `${AUTHORIZE_URI}?client_id=${CLIENT_ID}&response_type=${RESPONSE_TYPE}&redirect_uri=${REDIRECT_URL}&scope=${SCOPE}&access_type=${ACCESS_TYPE}`;
const getToken = async (code) => {
try {
const tokenApi = await axios.post(
`https://oauth2.googleapis.com/token?code=${code}&client_id=${CLIENT_ID}&client_secret=${CLIENT_SECRET}&redirect_uri=${REDIRECT_URL}&grant_type=authorization_code`
);
const accessToken = tokenApi.data.access_token;
return accessToken;
} catch (err) {
return err;
}
};
const getUserInfo = async (accessToken) => {
try {
const userInfoApi = await axios.get(
`https://www.googleapis.com/oauth2/v2/userinfo?alt=json`,
{
headers: {
authorization: `Bearer ${accessToken}`,
},
}
);
return userInfoApi;
} catch (err) {
return err;
}
};
const oauth2Api = async (code) => {
const accessToken = await getToken(code);
// NOTE 사용자 정보를 콘솔로 확인
console.log(await getUserInfo(accessToken));
};
// NOTE 버튼 클릭시 구글 로그인 화면으로 이동
app.get("/auth/google", (req, res) => {
res.redirect(OAUTH_URL);
});
// NOTE 설정한 리다이렉트 페이지로 이동시 처리할 로직
app.get("/oauth2/redirect", (req, res) => {
const query = url.parse(req.url, true).query;
if (query && query.code) {
oauth2Api(query.code);
}
res.send("");
});
app.listen(PORT, () => {
console.log("server on! http://localhost:" + PORT);
});
참고 자료
웹 서버 애플리케이션용 OAuth 2.0 사용
https://developers.google.com/identity/protocols/oauth2/web-server#httprest_2
범위 (SCOPE)
https://developers.google.com/identity/protocols/oauth2/scopes
'Backend' 카테고리의 다른 글
[oAuth2] Node Express로 google oAuth2 사용하기[1. 설정] (0) | 2022.08.24 |
---|---|
[Firebase] Firebase, FireStore 설정 및 사용 [2] + React (0) | 2022.05.21 |
[Firebase] Firebase, FireStore 설정 및 사용 [1] (0) | 2022.05.07 |
[Backend] GRPC ? NATS? (0) | 2022.05.04 |
[Jira] Jira REST API (0) | 2022.02.18 |