🤠

Next.js - Auth.jsでGoogleカレンダーAPIを使う方法

2023/06/19に公開

Next.jsAuth.jsを使用して、GoogleのカレンダーAPIにアクセスする方法です。App Routerを使用しています。

Auth.js自体の説明はこちらをご覧ください。ここでは説明しません。

https://zenn.dev/tfutada/articles/5557b780050574

仕組み

技術的に言えば、OAuth 2.0になります。Googleから一時的なアクセストークンを払い出してもらい、それを使ってGoogleのAPIを叩きます。巷のサービスを使うとよくお目にかかりますが、Webブラウザで同意画面を提示し、ユーザに承諾ボタンを押してもらいます。承諾されると302リダイレクトで認可コード(authorization code)がGETパラメータで渡されます。最後にその認可コードを使用して、アクセストークンを払い出すGoogle APIを叩きます。

この仕組みはnext-authライブラリを使用するといい感じにやってくれるので、自分でコーディングする必要はありません。

手順

おおまかに言って、次の2ステップになります。

  1. GCPコンソール画面での設定
  2. Next.jsでのコーディング

GCPプロジェクトでの作業

GCPのWebコンソールのAPIs & Services画面から次の2つを設定します。私のブログではコンセプト的なことにフォーカスします。細かな操作手順は他の人のブログを見てください。

ここでは次のCredentialsOAuth consent screenを設定します。

Credentials

Client IDSecret Keyの払い出しをします。ここで重要なのはredirect URIを設定することです。Google側の画面でユーザがログインに成功すると302リダイレクトをブラウザに返します。

Authorized redirect URIs
http://localhost:3000/api/auth/callback/google

Next.js側の設定です。環境変数にClient IDSecret Keyを設定します。

.env.development.local
GOOGLE_CLIENT_ID=96032...4tbslf.apps.googleusercontent.com
GOOGLE_CLIENT_SECRET=GOC...J_3SN

こちらの同意画面の設定をします。

ここでGoogleカレンダーAPIを(あなたのNext.jsサービスが)使いたいので許可をくださいとユーザにお願いしています。

ユーザがcontinueボタンを押すと、GoogleカレンダーAPIにアクセスするためのトークンを払い出すための認可コードをグーグルから払い出してもらえます。この認可コードは302リダイレクトを経由してnext-authのAPI(先ほど設定したredirect URI)に渡されます。この認可コードを使用して、Googleのアクセストークンを払い出すAPIを叩くとめでたくアクセストークンがゲットできます。このあたりの処理がnext-authに実装されています。

GCPコンソールのscopeの設定のところで許可をもらうロールを選択します。ここではGoogleカレンダーのリードオンリー権限に対してユーザに同意もらう設定をします。どんなスコープがあるかの一覧はこちらで確認できます。

Next.js側の作業です。このスコープをnext-authoptions.tsscopeで指定する必要があります。

options.ts
            GoogleProvider({
                clientId: process.env.GOOGLE_CLIENT_ID!,
                clientSecret: process.env.GOOGLE_CLIENT_SECRET!,
                authorization: {
                    params: {
                        prompt: "consent",
                        access_type: "offline",
                        response_type: "code",
                        scope: "https://www.googleapis.com/auth/userinfo.profile https://www.googleapis.com/auth/userinfo.email https://www.googleapis.com/auth/calendar.readonly",
                    }
                }
            }),

optionsのcallbacksの設定です。Googleから払い出してもらったトークンをバケツリレー(jwt->session)でセットします。セキュリティの観点からデフォでは闇雲にコピーしないようになってます。

options
        callbacks: {
            jwt: async ({token, user, account, profile, isNewUser}) => {
                if (user) {
                    token.user = user;
                    const u = user as any
                    token.role = u.role;
                }
                if (account) {
                    token.accessToken = account.access_token
                    token.refreshToken = account.refresh_token
                }
                return token;
            },
            session: ({session, token}) => {
                token.accessToken
                return {
                    ...session,
                    user: {
                        ...session.user,
                        role: token.role,
                        accessToken: token.accessToken,
                        refreshToken: token.refreshToken,
                    },

                };
            },
        }

サーバ・コンポーネントに実装

さて、いよいよ準備が整ったところでNext.jsのコーディングをしましょう。

GoogleのカレンダーAPIを呼び出すためには、Google提供のSDKを使用します。
例えば、カレンダー一覧を取得するには、await calendar.calendarList.list()のように呼び出します。非同期対応しています。公式サンプルもあります。

Node.jsのライブラリをインストールします。

インストール
npm install googleapis

getServerSession(options)を呼び出すと、先ほど設定したcallback.sessionの戻り値を受け取ります。user.accessTokenにGoogleのトークンを仕込んだことを思い出してください。
あとは、コード内のコメントを参考にしてください。ソースコードはこちらにあります。

app/google-calendar/page.tsx
import {getServerSession} from "next-auth/next";
import {options} from "@/app/options";
import {google, calendar_v3} from 'googleapis'
import Calendar = calendar_v3.Calendar


export default async function Page() {
    // サーバ・コンポーネントでセッションを取得する。
    const session = await getServerSession(options)
    const user = session?.user

    // Google OAuthへの接続
    const oauth2Client = new google.auth.OAuth2({
        clientId: process.env.GOOGLE_CLIENT_ID,
        clientSecret: process.env.GOOGLE_CLIENT_SECRET,
	// GCPコンソールで設定したredirect URI
        redirectUri: 'http://localhost:3000/google-calendar'
    })
    
    const accessToken = user?.accessToken // Googleが払い出したアクセストークン
    if (!accessToken) {
        return (
            <div>accessToken is null</div>
        )
    }

    // トークンを設定。refresh_tokenも渡せます。
    oauth2Client.setCredentials({access_token: accessToken})
    
    // カレンダーオブジェクト作成
    const calendar: Calendar = google.calendar({version: 'v3', auth: oauth2Client})
    
    // カレンダー一覧を取得
    const calendarResponse = await calendar.calendarList.list()

    console.log(calendarResponse.data)

    return (
        <main
            style={{
                display: "flex",
                justifyContent: "center",
                alignItems: "center",
                height: "70vh",
            }}
        >
            <div>
                <div>よしなにレンダリング。calendarResponse.data</div>
            </div>
        </main>
    );
}

Chrome Dev Tool

yarn devで実行し、Chrome Dev Toolでネットワークを見てみましょう。

下の図は最初にGoogleからのログイン画面を表示させているところです。
Client ID、scope、redirect_uriを飛ばしているのがわかりますね。

次は、ユーザが同意画面でOKしたときのコールバックです。ここで302でリダイレクトさせていることに注目してください。GCPコンソールで設定したredirect_uriをキックさせます。その際に承認コードをGETパラメータで飛ばしています。next-auth側でこのGETパラメータを受け取り、Googleのアクセストークンを払い出すAPIを叩く(Next.js側から)わけです。

Vercelへのデプロイ

先ほどの、Credentialsredirect URIを変更するか、新規にClient IDを作成します。新規に作った方が良いでしょう。

参考

https://blog.logrocket.com/how-to-authenticate-access-google-apis-using-oauth-2-0/

https://plswiderski.medium.com/google-api-authentication-with-oauth-2-on-the-example-of-gmail-a103c897fd98

Discussion