Google Calendar APIを利用する代表的な方法は
- OAuth2.0
- サービスアカウント
を使う2種類の方法があります。
2のサービスアカウントは特定のカレンダーを利用する場合、
1のOAuth2.0はどのGoogleカレンダーを利用するか分からない場合、例えばカレンダーアプリを作るような場合に向いています。
サービスアカウントを使ってGoogle Calendar APIを使う方法については以前書いた以下の記事を良ければどうぞ!
サービスアカウントの記事を書いた後、
他APIにも応用がきき、汎用性の高いOAuth2.0を使った方法も把握しておきたいなと思いました。
OAuth2.0を使ってGoogleのAPIを利用するシーンは
- Webサービス
- モバイルアプリ
辺りが多そうなイメージです。
またOAuth2.0を使用する場合
Googleの認証画面 → アプリケーション
にリダイレクトさせるという手順が発生しWebサービスがイメージしやすそう、と思いました。
そのため、この記事ではNext.jsでOAuth2.0を使ってGoogleのAPIを使用する方法を書きます!
ReactでOAuth2.0を使用する事も考えたのですが、
後述する秘匿情報のクライアントシークレットがブラウザで読み込むコードに流れてしまい第三者が把握出来てしまう可能性があります。
React/Vueのみで完結させたい場合SSRがある Next.js/Nuxt.js を使用すると良さそうです。
検証した環境
1 | next | 13.1.1 |
2 | googleapis | 110.0.0 |
1. GCPでの準備
私は今回Googleの提供するAPIの中でGoogle Calendar APIを利用したかったため、
Google Calendar APIを例に進めます。
1-1. プロジェクト作成
GCPのプロジェクト作成ページを開きプロジェクトを作成します
https://console.cloud.google.com/projectcreate
- プロジェクト名・・自分が分かる名前
- 場所・・自分が判別出来るようにする。「組織なし」で始めて問題なし
1-2. Google Calendar APIを有効にする
1-1.で作成したプロジェクトを選択した状態でAPIライブラリよりGoogle Calendar APIを有効にします
https://console.cloud.google.com/apis/library/calendar-json.googleapis.com
1-3. OAuthの同意画面の設定
同じく作成したプロジェクトが選択された状態で
サイドバー > 認証情報 > 認証情報を作成 > OAuthクライアントID
を選択
最初にOAuthの同意画面のための設定を行う必要があります。
同意画面とは良くアプリを使っている際、目にする↓のような画面のことです。
UserType
アプリケーションによって変わってきます
- 内部・・組織内のユーザーのみ利用可能にする(Google Workspaceを利用して組織のドメインを持っている場合)
- 外部・・それ以外のケース
今回個人利用だったので私は「外部」を選択
アプリケーション情報を入力
アプリケーションに必要な情報を入力します。
以下の項目が必須
- アプリ名・・アプリケーションの名前(テスト的に作成する場合は「サンプルアプリ」のように適当な名前を付けて問題なし)
- サポートメール・・一覧から選択
- 連絡先メールアドレス・・サポートメールと同じでも問題なし
スコープの設定
「スコープを追加または削除」ボタンを押下
表示されたウィンドウで必要なスコープを選択します
今回カレンダーの一覧を取得するため auth/calendar.readonly
にチェックを入れました。
テストユーザー
開発中は「テストモード」となりテストユーザーのみが選択したAPIにアクセス出来ます。
今回のケースでいくと、Google Calendar APIを利用したいGoogleアカウントを入力します。
例えば複数アカウントを使っていてそれら全てのカレンダーにアクセスしたい場合、その複数のアカウント情報を入力する必要があります。
1-4. OAuthクライアントIDを作成
OAuthの同意画面の設定が完了した後、
再度サイドバー > 認証情報 > 認証情報を作成 > OAuthクライアントID
を選択
すると今度は「OAuthクライアントIDの作成」画面が表示されます
- アプリケーションの種類を選択
- 名前を入力
- 承認済みのJavaScript生成元・承認済みのリダイレクト URIは分かっていれば入力、後ほど追加も可能
-
今回私は
http://localhost:3000/callback
にリダイレクトして戻ってくるためこちらを入力しました
-
今回私は
クライアントID・クライアントシークレットが作成されます!
これらの値は後ほど使用する重要な値になります。
また、これらの情報も含めたjsonファイルがDL出来るためDLしておきます。
クライアントID・クライアントシークレットは後ほどDL・再発行 可能です
Webアプリケーションの準備
GCPでAPIを利用するための準備が出来たので、
それを利用するためのWebアプリケーションを準備します。
前述した通り、Next.jsのアプリケーションを作成します。
本題からそれるので簡単に
# プロジェクトを作成したいフォルダで以下のコマンドを実行
# my-app という名前でプロジェクトが作成される
$ yarn create next-app
・・・
✔ What is your project named? … my-app
✔ Would you like to use TypeScript with this project? … Yes
✔ Would you like to use ESLint with this project? … Yes
# プロジェクトフォルダに移動
$ cd my-app
# reactの開発環境を起動
$ yarn run dev
Next.jsの開発環境が起動しました!
2. Google Calendar APIをOAuth2.0で利用する
ここではサーバ部分にNext.jsのAPIを利用しますが、他FWのREST APIなどに読み替えて問題ありません。
以下のような流れを作っていきます!
- フロントで「認証する」ボタンを押下
- サーバでGoogle認証用画面のURLを組み立てフロントに返す
- フロントからGoogle認証のページに遷移
- Google認証のページから認証コードを付けた状態でフロントにリダイレクト
- 認証コードを合わせてサーバに送る
- 該当ユーザーのアクセストークン・リフレッシュトークンなどを取得しフロントに返す
- アクセストークンを用いてGoogle Calendar APIにアクセスする
道のりは長いですが、
6.
のアクセストークン・リフレッシュトークンが取得出来れば、後はよくある認証方式です。
期限が切れてないアクセストークンを使ってGoogleのAPIにアクセスすればOKです!
2-1. 認証ボタンの用意
トップページに認証ボタンを表示します
export default function Home() {
const handleOnClick = async () => {
// TODO: 認証ボタン押下時の処理
}
return (
<div>
<button onClick={handleOnClick}>認証する</button>
</div>
)
}
2-2. サーバでGoogle認証用画面のURLを組み立てフロントに返す
Google APIを利用するためのライブラリを追加
$ yarn add googleapis
API側(Next.jsのAPI)にGoogle認証画面のURLを生成し返せるようにします
import type { NextApiRequest, NextApiResponse } from 'next'
import { google } from 'googleapis'
type Response = {
authorizeUrl: string
}
// Google認証画面のURLを生成しレスポンスに含める
export default function handler(req: NextApiRequest, res: NextApiResponse<Response>) {
const oauth2Client = new google.auth.OAuth2({
clientId: '<クライアントID>',
clientSecret: '<クライアントシークレット>',
redirectUri: 'http://localhost:3000/callback',
})
// Google認証画面のURLを生成する
const authorizeUrl = oauth2Client.generateAuthUrl({
access_type: 'offline',
scope: 'https://www.googleapis.com/auth/calendar.readonly',
})
res.status(200).json({ authorizeUrl })
}
フロント側ではNext.jsのAPIをコールする際にaxios
を使いたいので追加
$ yarn add axios
「認証する」ボタン押下時に
先ほどのGoogle認証画面生成APIを呼び出し、受け取ったページのURLを別タブで開きます。
import axios from 'axios'
export default function Home() {
const handleOnClick = async () => {
const response = await axios.get('http://localhost:3000/api/generate-google-oauth-url')
const { authorizeUrl } = response.data
// Google認証ページを別タブで開く
window.open(authorizeUrl, '_blank')
}
return (
<div>
<button onClick={handleOnClick}>認証する</button>
</div>
)
}
すると、良く目にする↓のようなGoogle認証画面が表示出来ます!
2-3. Google認証のページから認証コードを付けた状態でフロントにリダイレクト
Google認証画面を進めていくと最終的にredirectUri
で設定したページに 「認証コード」 と呼ばれる値がgetパラメータで付与された状態でリダイレクトして戻ってきます。
以下のようなURLで戻ってくる
http://localhost:3000/callback?code=<認証コード>&scope=<許可したGoogleのAPI>
認証コードはこの後のトークンを取得する際に使用するため、getパラメータから取り出せるようにしておきます。
import { useRouter } from 'next/router'
import { useEffect } from 'react'
export default function Callback() {
const router = useRouter()
useEffect(() => {
const fn = async () => {
// getパラメータの認証コードを取得する
const code = router.query.code
if (!code) return
console.log('code: ', code)
}
fn().then()
}, [router.query.code])
return <div></div>
}
2-4. 認証コードをサーバに送りトークンを取得する
認証コードを元にアクセストークンなどが取得出来ます。
まず、API側に認証コードからトークンを取得するエンドポイントを作成
import type { NextApiRequest, NextApiResponse } from 'next'
import { google } from 'googleapis'
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { authorizationCode } = req.body
const oauth2Client = new google.auth.OAuth2({
clientId: '<クライアントID>',
clientSecret: '<クライアントシークレット>',
redirectUri: 'http://localhost:3000/callback',
})
const response = await oauth2Client.getToken(authorizationCode)
res.status(200).json({ tokens: response.tokens })
}
フロント側で↑のAPIを呼び出してトークン関連の値を取得
import axios from 'axios'
import { useRouter } from 'next/router'
import { useEffect } from 'react'
export default function Callback() {
const router = useRouter()
useEffect(() => {
const fn = async () => {
const code = router.query.code
if (!code) return
const response = await axios.post('http://localhost:3000/api/get-google-auth-token', {
authorizationCode: code,
})
// tokens = {
// access_token: "ya29.abcdefghijk...",
// expiry_date: 1673545804055,
// refresh_token: "1//0e6-abcdefghijk...",
// scope: "https://www.googleapis.com/auth/calendar.readonly",
// token_type: "Bearer",
// }
const { tokens } = response.data
console.log('tokens: ', tokens)
}
fn().then()
}, [router.query.code])
return <div></div>
}
トークンが無事取得出来ました!
ここまで出来ればあとは、Webアプリケーションの場合
- LocalStorage
- cookie
などにこれらトークンの値を保存。
必要に応じて使用、「リフレッシュトークンがあればログイン出来ている」といった判断も出来るようになりますね!
2-5. カレンダーの情報を取得
最後にGoogleのAPIを試す上でGoogleカレンダーの情報を取得してみます。
API側にアクセストークンを受け取ってカレンダーの一覧を取得し返すエンドポイントを追加
import type { NextApiRequest, NextApiResponse } from 'next'
import { google, calendar_v3 } from 'googleapis'
import Calendar = calendar_v3.Calendar
export default async function handler(req: NextApiRequest, res: NextApiResponse) {
const { accessToken } = req.body
const oauth2Client = new google.auth.OAuth2({
clientId: '<クライアントID>',
clientSecret: '<クライアントシークレット>',
redirectUri: 'http://localhost:3000/callback',
})
// 認証情報を設定
// tokens として受け取った access_token・refresh_token・expiry_date などが設定可能
// access_token が最低限必須
oauth2Client.setCredentials({ access_token: accessToken })
// Googleカレンダーの一覧を取得する
const calendar: Calendar = google.calendar({ version: 'v3', auth: oauth2Client })
const calendarResponse = await calendar.calendarList.list()
// カレンダーの一覧をレスポンスに含める
res.status(200).json({ items: calendarResponse.data.items })
}
重要なのが2-4.で取得したアクセストークンを設定している以下の部分
// 認証情報を設定
// access_token が最低限必須
oauth2Client.setCredentials({ access_token: accessToken })
期限が切れていないアクセストークンさせ設定すれば、scopeで設定したGoogleのAPIへアクセス出来るようになります!
フロント側では
受け取ったレスポンスの値をstateに保存して値があればmap
で回してidを表示する、としています。
any[]
を使って無理矢理感がありますね 😂
import axios from 'axios'
import { useRouter } from 'next/router'
import { useEffect, useState } from 'react'
export default function Callback() {
const [calendars, setCalendars] = useState<any[]>([])
const router = useRouter()
useEffect(() => {
const fn = async () => {
const code = router.query.code
if (!code) return
const response = await axios.post('http://localhost:3000/api/get-google-auth-token', {
authorizationCode: code,
})
// tokens = {
// access_token: "",
// expiry_date: 1673545804055,
// refresh_token: "",
// scope: "https://www.googleapis.com/auth/calendar.readonly",
// token_type: "Bearer",
// }
const { tokens } = response.data
// カレンダーの一覧を取得しstateに保存する
const res = await axios.post('http://localhost:3000/api/get-google-calendar', {
accessToken: tokens.access_token,
})
setCalendars(res.data.items)
}
fn().then()
}, [router.query.code])
return (
<div>
<ul>
{calendars.map((calendar: any) => {
return <li key={calendar.id}>{calendar.id}</li>
})}
</ul>
</div>
)
}
無事Google Calendar APIと接続が出来て、カレンダーの一覧が取得・表示出来るようになりました!
今回はGoogle Calendar APIとの接続でしたが、
Oauth2.0の仕組みさえ理解出来れば他のGoogle APIにも応用が効きます。
またアクセストークン、リフレッシュトークンを用いたOAuthの仕組みの理解、
というところでもGoogleのAPIを使うのはとっつきやすいな、というのが所感でした!