[Nuxt.js/Vue.js] JWT를 이용해 구글 서비스 계정 인증하기
초코스프레드 위키를 Nuxt.js로 옮기게 되었습니다.
왜 Next.js가 아니라 Nuxt.js 였냐 하면....별 이유는 없고 그냥 깃허브 액션에 Next가 있다는 것을 늦게 보았습니다 (...)
하여튼, Nuxt.js는 뷰 기반이니만큼 뷰 기준으로 설명하겠습니다.
//index.vue
<template>
<div>{{ wikiBody }}</div>
</template>
템플릿에는 가져온 스프레드시트의 컨텐츠를 담을 수 있게 준비했습니다.
//index.vue
<script>
const jwt = require('jsonwebtoken');
const querystring = require("querystring");
const secretKey = "(private_key)"
index.vue
가 아니어도 상관없지만, nuxt 기준으로 pages
아래에 있는 뷰 파일에다가 작업을 해 주어야 합니다.
jsonwebtoken
이라는 모듈을 설치하고 불러옵시다. 참고로 이 jsonwebtoken
의 경우, 9.0.0이 아니라 8.5.1 버전을 설치해야 합니다. 그렇지 않으면 오류가 납니다.
querystring
모듈은 나중에 POST 요청을 보내기 위해 필요합니다.
secretKey
변수에 받아온 json 파일의 private key를 집어넣습니다. 일단 로컬에서 실행하기 때문에 그대로 넣어주었습니다. 이후 env 파일로 옮길 예정입니다. 이대로 커밋만 안 하면 됩니다(...)
참고로 이 json 파일은 구글 API에서 새 서비스 계정을 생성해서 받아왔습니다.
//index.vue
const token = jwt.sign(
{ "iss": "(client_email)", "scope": "https://www.googleapis.com/auth/spreadsheets", "aud": "https://oauth2.googleapis.com/token" },
secretKey,
{ algorithm: 'RS256', expiresIn: "1h", keyid: "(private_key_id)" }
);
jsonwebtoken
모듈을 사용해서 사인을 해봅니다. 간단합니다.
jwt.sign(payload, privateKey, options)
Google에서 요구하는 JWT는 헤더에 alg
(RS256), kid
가 포함되어야 하고, 페이로드에 iss
, scope
, aud
, iat
, exp
가 포함되어 있어야 합니다.
이걸 일일이 변수로 만들어줘도 되지만, jwt 모듈에서는 그냥 option에 필요한 값만 넣어주면 알아서 사인을 해줍니다.
예를 들어 alg
는 algorithm
, kid
는 keyid
입니다. exp
는 expiresIn
이에요. iat
는 자동으로 넣어줍니다.
만들어진 JWT는 https://jwt.io/ 에서 검증할 수 있습니다만, 구글 JWT의 경우 아마 유효하지 않다고 나올 겁니다. 하지만 이게 맞습니다. 구글에서 예시로 던져준 JWT도 유효하지 않다고 나옵니다.
//index.vue
export default {
async asyncData () {
const googleAuthUrl = 'https://oauth2.googleapis.com/token'
const googleAuthParam = {
method: 'POST',
headers: {
'content-type': "application/x-www-form-urlencoded",
},
body: querystring.stringify({
grant_type: "urn:ietf:params:oauth:grant-type:jwt-bearer",
assertion: token
})
}
var authData = await fetch(googleAuthUrl, googleAuthParam)
var authRes = await authData.json()
그 다음에 fetch
를 하는 단계 입니다. nuxt이기 때문에 asyncData()
훅을 사용해서 데이터를 받아온 다음 템플릿에 붙일 수 있게 해 보았습니다.
fetch
는 다른 사이트로의 fetch
들과 똑같이 진행하면 되는데요(그야, 그냥 평범한 POST니까요...), body
에서 grant_type
이 자주 오류가 나더라구요.
이유는 잘 모르겠지만 JSON.stringify
를 querystring.stringify
로 바꾸었더니 잘 인증이 되었습니다.
//index.vue
const googleSheetUrl = 'https://sheets.googleapis.com/v4/spreadsheets/(구글 시트 주소)/values/(시트 및 범위)'
const googleSheetParam = {
method: 'GET',
headers: {
"content-type": "application/json",
Authorization: "Bearer " + authRes.access_token,
},
}
var sheetData = await fetch(googleSheetUrl, googleSheetParam)
var sheetRes = await sheetData.json()
var wikiBody = sheetRes.values[sheetRes.values.length - 1][2]
return { wikiBody }
}
}
</script>
내친김에 받은 액세스 토큰으로 구글 시트까지 가져와 보았습니다.
가장 어려운 인증(...) 부분을 뚫었으니 이제는 다른 사이트와 똑같이 제대로 된 엔드포인트에다가 값만 잘 넘겨 주면 됩니다. 특히 시트 값 가져오는 건 GET이니까요.
초코숲을 만들다가 또 막히는 일이 있으면 포스트를 써 보도록 하겠습니다.