토큰 발급 및 연동
발급 방식
토큰을 만들 때 발급 방식을 고릅니다. 만든 뒤에는 바꿀 수 없습니다.
Google Apps Script: 스크립트에서 승인 주소를 열어 승인하면 토큰을 받습니다. 토큰이 사람 손을 거치지 않고 스크립트 저장소에만 남습니다. 스크립트 ID 를 넣어 만들고, redirect URI 는 그 ID 로 정해집니다.
콘솔 발급: 승인 화면을 띄울 수 없는 서버리스용입니다. 만들 때 refresh token 을 한 번만 보여 줍니다. 잃어버리면 콘솔에서 다시 받아야 합니다.
토큰 만들기
콘솔 개발 > OAuth2 에서 새로운 토큰 을 누릅니다.
발급 방식을 고릅니다.
권한을 고릅니다. 내 역할로 고를 수 있는 것만 고를 수 있습니다.
Apps Script 면 스크립트 ID 와 메모를 넣습니다. 메모는 목록과 알림에서 토큰을 구분하는 이름입니다.
2차 인증을 마치면 토큰이 만들어집니다.
계정마다 토큰은 20개까지 만들 수 있습니다(구성원 전체 합계). 권한과 발급 방식은 만든 뒤 바꿀 수 없습니다. 다른 권한이 필요하면 새로 만듭니다. 메모는 목록의 정보 · 메모 수정 에서 바꿀 수 있습니다.
Google Apps Script 에서 쓰기
토큰을 만들면 연동 설정 창이 4단계로 안내합니다. 목록의 설정 보기 에서 다시 열 수 있습니다.
1. OAuth2 라이브러리 추가
Google 이 공개한 Apps Script OAuth2 라이브러리를 씁니다. 편집기 왼쪽 라이브러리 옆 + 를 누르고 아래 스크립트 ID 로 찾아 가장 큰 버전을 추가합니다. 식별자는 OAuth2 그대로 둡니다. PKCE(generateCodeVerifier)를 지원하는 판이어야 합니다.
1B7FSrk5Zi6L1rSxxTDgDEUsPzlukDsi4KGuTMorsTQHhGBzBkMun4iDF
2. 코드 붙여 넣기
설정 창의 코드는 client_id 와 주소가 이미 들어 있어 그대로 붙여 넣으면 됩니다. 모양은 아래와 같습니다. authorize 를 맨 위에 둔 것은 편집기가 첫 함수를 실행 대상으로 고르기 때문입니다.
function authorize() {
const service = getSolapiService()
if (service.hasAccess()) return console.log('이미 승인했습니다.')
console.log(service.getAuthorizationUrl())
}
function callSolapi() {
const service = getSolapiService()
if (!service.hasAccess()) return console.log('먼저 authorize 를 실행해 주세요.')
const res = UrlFetchApp.fetch('https://api.solapi.com/cash/v1/balance', {
headers: { Authorization: 'Bearer ' + service.getAccessToken() },
muteHttpExceptions: true
})
console.log(res.getResponseCode() + ' ' + res.getContentText())
}
function resetSolapi() {
getSolapiService().reset()
}
function getSolapiService() {
return OAuth2.createService('solapi')
.setAuthorizationBaseUrl('https://api.solapi.com/oauth2/v1/authorize')
.setTokenUrl('https://api.solapi.com/oauth2/v1/access_token')
.setClientId('CID…')
// client secret 이 없어 라이브러리가 싣는 값을 지웁니다.
.setTokenPayloadHandler(function (payload) {
delete payload.client_secret
return payload
})
.generateCodeVerifier()
.setScope('personal')
.setCallbackFunction('authCallback')
.setPropertyStore(PropertiesService.getUserProperties())
.setLock(LockService.getUserLock())
}
function authCallback(request) {
const ok = getSolapiService().handleCallback(request)
return HtmlService.createHtmlOutput(ok ? '승인했습니다. 이 창을 닫으세요.' : '승인하지 못했습니다.')
}
client_secret을 지우는 줄은 빼면 안 됩니다. 개인 연동은 secret 이 없는 공개 클라이언트라, 라이브러리가 빈 secret 을 실으면 토큰을 받지 못합니다.
3. 승인하기
함수 목록에서
authorize를 골라 실행합니다.처음이면 Google 의 권한 검토 창이 뜹니다. 허용합니다. "확인되지 않은 앱" 이 보이면 고급 → (안전하지 않음)으로 이동 을 누릅니다. 직접 만든 스크립트라서 나오는 안내입니다.
실행 로그의
https://api.solapi.com/oauth2/v1/authorize?…주소를 브라우저에서 엽니다.토큰을 만든 회원으로 SOLAPI 에 로그인돼 있으면 승인 화면 없이 바로 "승인했습니다" 가 뜹니다.
authCallback 은 직접 실행하지 않습니다. 4번에서 돌아올 때 Google 이 부르는 함수라, 편집기에서 실행하면 Cannot read properties of undefined (reading 'parameter') 오류가 납니다.
4. 확인하기
callSolapi 를 실행해 실행 로그에 200 이 나오면 끝입니다. 예시 호출은 토큰의 조회 권한에 맞춰 잔액 조회, 메시지 목록 조회, 발신번호 조회 가운데 하나로 들어갑니다.
서버리스에서 쓰기 (콘솔 발급)
콘솔에서 받은 refresh token 을 서버 비밀 저장소에 넣고, 필요할 때 access token 으로 바꿔 씁니다.
토큰 갱신 요청
POST https://api.solapi.com/oauth2/v1/access_token
Content-Type: application/x-www-form-urlencoded
grant_type=refresh_token&refresh_token=solapi_prt_…
client_id 와 client_secret 은 보내지 않습니다. 응답은 아래 모양입니다.
{
"access_token": "…",
"token_type": "Bearer",
"expires_in": 3600,
"refresh_token": "solapi_prt_…",
"scope": "message:send cash:read"
}
API 를 부를 때는 Authorization: Bearer <access_token> 을 싣습니다.
새 refresh token 을 반드시 저장합니다
refresh token 은 쓸 때마다 새 값으로 바뀝니다(회전). 응답의 refresh_token 을 저장소에 바로 덮어써야 합니다. 이전 값을 다시 쓰면 탈취로 보고 그 토큰을 모두 폐기합니다. 그러면 콘솔에서 토큰을 다시 받아야 합니다.
// Node 18 이상. loadRefreshToken · saveRefreshToken 은 쓰는 비밀 저장소에 맞게 만듭니다.
async function solapiAccessToken() {
const res = await fetch('https://api.solapi.com/oauth2/v1/access_token', {
method: 'POST',
headers: { 'Content-Type': 'application/x-www-form-urlencoded' },
body: new URLSearchParams({
grant_type: 'refresh_token',
refresh_token: await loadRefreshToken()
})
})
const body = await res.json()
if (!res.ok) throw new Error(`${body.errorCode}: ${body.errorMessage}`)
await saveRefreshToken(body.refresh_token)
return body.access_token
}
access token 은 1시간 동안 쓸 수 있습니다. 매 요청마다 갱신하지 말고, 받은 access token 을 만료 전까지 다시 쓰는 편이 좋습니다.
토큰 수명과 재승인
access token: 1시간
refresh token: 쓸 때마다 바뀝니다.
미사용 만료: 마지막으로 쓴 날부터 30일 동안 쓰지 않으면 만료됩니다.
승인 만료: 처음 승인(콘솔 발급은 발급)한 날부터 90일이 지나면 만료됩니다.
만료되면 사람이 다시 승인해야 합니다. 스크립트가 대신할 수 없습니다.
Apps Script:
authorize를 다시 실행해 주소를 열고 승인합니다. 새 refresh token 은 라이브러리가 저장소에 알아서 넣습니다. 라이브러리의hasAccess()는 갱신에 실패하면false를 돌려주므로, 예시 코드에서는callSolapi가 "먼저 authorize 를 실행해 주세요." 를 남깁니다.콘솔 발급: 콘솔 목록에서 토큰 다시 받기 를 누르고, 쓰던 곳의 값을 새 refresh token 으로 바꿉니다. 받는 순간 이전 토큰은 폐기됩니다.
목록의 상태 칸은 만료 7일 전부터 "곧 만료" 로 바뀝니다.
알림
계정 OWNER 전원과 토큰을 만든 회원에게 메일이 발송됩니다. 중요 알림이라 수신거부할 수 없습니다.
상황 | 제목 | 문자 |
|---|---|---|
토큰을 만들었을 때 | 토큰이 만들어졌습니다 | 없음 |
Apps Script 에서 승인했을 때 | 연동이 승인되었습니다 | 없음 |
콘솔에서 다시 받았을 때 | 토큰이 다시 발급되었습니다 | 없음 |
폐기했을 때 | 토큰이 폐기되었습니다 | 없음 |
탈취가 감지됐을 때 | 토큰 탈취가 감지되어 폐기되었습니다 | 함께 보냄 |
Apps Script 승인 만료 7일 · 1일 전 | 연동을 N일 안에 다시 승인해야 합니다 | 1일 전만 |
콘솔 발급 만료 7일 · 1일 전 | 토큰을 N일 안에 다시 받아야 합니다 | 1일 전만 |
미사용 만료 7일 · 1일 전 | 30일 동안 쓰지 않은 토큰이 곧 만료됩니다 | 1일 전만 |
만료 예고는 한국 시각 10시에서 18시 사이에 발송됩니다.
오류 코드
토큰 갱신 · 승인에서 나는 오류입니다. 응답 본문의 errorCode 로 구분합니다.
errorCode | 상태 | 뜻 | 할 일 |
|---|---|---|---|
| 400 | 30일 동안 쓰지 않아 만료 | 다시 승인 또는 다시 받기 |
| 400 | 승인 90일 지남 | 다시 승인 또는 다시 받기 |
| 400 | 바뀐 refresh token 을 다시 씀, 모두 폐기 | 다시 승인 또는 다시 받기, 새는 곳 확인 |
| 400 | 폐기된 토큰 | 새로 만들기 |
| 400 | 모르는 refresh token | 저장한 값 확인 |
| 400 | 승인 요청에 code_challenge(S256) 없음 | 라이브러리 판 확인, |
| 403 | 내 역할로 고를 수 없는 권한 | 만들 때 권한 다시 고르기 |
| 400 | 계정당 20개를 넘음 | 쓰지 않는 토큰 폐기 |
API 를 부를 때 401 이면 access token 이 만료됐거나 승인이 끝난 것이고, 403 이면 토큰에 없는 권한입니다.
주의할 점
refresh token 은 비밀번호처럼 다룹니다. 코드 · 저장소 · 로그에 남기지 않고 비밀 저장소에 둡니다. 새어 나갔다면 콘솔에서 바로 폐기합니다.
콘솔 발급 refresh token 은 만들 때와 다시 받을 때 한 번만 볼 수 있습니다.
승인은 토큰을 만든 회원만 할 수 있습니다. 다른 회원으로 로그인한 브라우저에서 승인 주소를 열면 승인되지 않습니다.