1. bun이란?
bun
은 node.js
와 같은 자바스크립트 런타임입니다. 자세한 내용과 설치는 bun.sh 에서 확인 하실 수 있습니다. node.js
의 express
와 같은 elysia
라는 프레임워크를 사용해서 google gmail OAuth2토큰을 발급 받는 과정까지 포스팅 하도록 하겠습니다.
2. elysia 앱 생성
elysia
앱을 생성해보겠습니다 생성은
bun create elysia ${app name}
명령어를 통해 생성 할 수 있습니다.
src/index.ts
파일을 보면
import { Elysia } from "elysia";
const app = new Elysia().get("/", () => "Hello Elysia").listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
3000번포트로 리스닝 중인것을 확인 할 수 있습니다.
bun run dev
명령어로 실행하고 localhost:3000
을 접속해보면
이와 같이 작동중인것을 확인 할 수 있습니다.
3. Google 프로젝트 생성
google developer console에 접속해서 기존 프로젝트에서 api를 추가해주거나 새로운 프로젝트를 생성합니다.
왼쪽탭에서 Library를 클릭하고 gmail API를 검색후 클릭 => ENABLE 선택합니다.
OAuth consent screen 을 선택후 External타입으로 CREATE 해줍니다.
다음 화면에서 App name, user support email, Developer contanct information을 채워준후 save and continue 버튼을 누릅니다.
scope를 추가합니다
https://www.googleapis.com/auth/gmail.readonly
=> ADD TO TABLE => UPDATE => Save and continue이후 나오는 화면들은 특별히 건드릴 것은 없습니다.
다음 Credentials 탭에서 CREATE CREDENTIALS => OAuth client ID를 누릅니다
내용을 채워줍니다
현재 elysia
앱이 3000번포트를 사용하고있기때문에 localhost:3000
이고 redirect URI는 아무거나 상관없지만 저는 /auth
로 했습니다. 이후 CREATE를 눌러줍니다.
- 누른뒤 나오는 Client ID와 Client Secret을 메모해둡니다.
4. 코드 작성
이제 준비가 완료되었습니다. 코드를 작성하러 가보겠습니다.
1. 시크릿 준비
Client ID
와 Client Secret
는 랜덤한 문자이기 때문에 외워서 칠 수도 없고, 복잡하기 때문에 시크릿 파일을 만들어두고 필요할때 import해서 사용하도록 하겠습니다.
export const CLIENT_ID = "your client id"
export const CLIENT_SECRET = "your client secret"
export const SCOPES = ['https://www.googleapis.com/auth/gmail.readonly']
export const REDIRECT_URI = 'http://localhost:3000/auth'
export const AUTH_URI = 'https://accounts.google.com/o/oauth2/auth'
export const TOKEN_URI = 'https://oauth2.googleapis.com/token'
export const MESSAGES_END_POINT = 'https://gmail.googleapis.com/gmail/v1/users/me/messages'
export const GET_MESSAGE_END_POIND = '/gmail/v1/users/me/messages/'
필요한 변수들을 준비했습니다.
2. code 발급
OAuth2에서는 리디렉션 페이지에서 쿼리스트링으로 제공되는 Code를 발급받고 그 코드로 AccessToken을 발급받습니다. 그 Code를 발급받기위한 코드를 작성하겠습니다.
import { Elysia } from "elysia";
import {
CLIENT_ID,
AUTH_URI,
REDIRECT_URI,
SCOPES } from '../secrets';
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/test", ({ redirect }) => {
return redirect(`${AUTH_URI}?scope=${SCOPES}&access_type=offline&include_granted_scopes=true&response_type=code&prompt=consent&redirect_uri=${REDIRECT_URI}&client_id=${CLIENT_ID}`)
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
이후 localhost:3000/auth
에 접속하면
이렇게 계정을 선택하는 화면이 뜹니다.
계정을 선택하고 주소창을 보면 쿼리스트링에 /auth?code=코드값
의 형태로 나와있습니다. 이 코드값을 가져오면됩니다.
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/test", ({ redirect }) => {
return redirect(`${AUTH_URI}?scope=${SCOPES}&access_type=offline&include_granted_scopes=true&response_type=code&prompt=consent&redirect_uri=${REDIRECT_URI}&client_id=${CLIENT_ID}`)
})
.get("/auth", async ({ query }) => {
const code: string | undefined = query.code
if (code) {
try {
// code를 이용해 token을 발급받는 함수를 불러옴
} catch (err) {
console.error(err);
}
}
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
이제 맨 위에 token의 type과 tokenResponse라는 변수를 선언합니다
type tokenType = {
accessToken: string
expiresIn: number
scope: string
token_type: string
}
let tokenResponse: tokenType | null = null;
다시 밑으로 내려와서
import { Elysia } from "elysia";
import {
CLIENT_ID,
AUTH_URI,
REDIRECT_URI,
SCOPES } from '../secrets';
import { issueToken } from 'gmail' ;
type tokenType = {
accessToken: string
expiresIn: number
scope: string
token_type: string
}
let tokenResponse: tokenType | null = null;
const app = new Elysia()
.get("/", () => "Hello Elysia")
.get("/test", ({ redirect }) => {
return redirect(`${AUTH_URI}?scope=${SCOPES}&access_type=offline&include_granted_scopes=true&response_type=code&prompt=consent&redirect_uri=${REDIRECT_URI}&client_id=${CLIENT_ID}`)
})
.get("/auth", async ({ query, set}) => {
const code: string | undefined = query.code
if (code) {
try {
tokenResponse = await issueToken(code);
if (!tokenResponse) {
set.status = 401;
return "No toke available";
}
} catch (err) {
console.error(err);
}
}
})
.listen(3000);
console.log(
`🦊 Elysia is running at ${app.server?.hostname}:${app.server?.port}`
);
issueToken
import axios from 'axios';
import {
TOKEN_URI,
CLIENT_ID,
CLIENT_SECRET,
REDIRECT_URI,
} from '../../secrets';
async function issueToken(code: string) {
const searchParams = new URLSearchParams()
searchParams.append('code', code)
searchParams.append('client_id', CLIENT_ID)
searchParams.append('client_secret', CLIENT_SECRET)
searchParams.append('redirect_uri', REDIRECT_URI)
searchParams.append('grant_type', 'authorization_code')
try {
const response = await axios.post(TOKEN_URI, searchParams, {
headers: {
'Content-Type': 'application/x-www-form-urlencoded'
}
})
console.log(response.data)
return response.data
} catch (err) {
console.error(err)
}
return null
}
export { issueToken }
여기까지 하면 Token을 발급받을 수 있습니다.
5. 마무리
elysia
에서는 라우팅을
import { Elysia } from 'elysia'
// ❌ don't
const app1 = new Elysia()
app1.get('/', () => 'hello')
app1.post('/', () => 'world')
// ✅ do
const app = new Elysia()
.get('/', () => 'hello')
.post('/', () => 'world')
와 같이 하기를 권장하고 있습니다. 1번처럼 해도 동작은 하지만 elysia
의 장점인 간단한 문법을 지키기 위해서 2번을 사용하는 것을 권장합니다. elysia 공식문서
토큰 받아오는 것까지 했는데 다음엔 토큰을 이용해 gmail 메일 리스트를 보여주고 화면에 보여주는걸 목표로 포스팅 해보겠습니다 감사합니다.
>> Home