1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?

More than 3 years have passed since last update.

【OAuth2.0】GoogleのアクセストークンをAuthorizationCodeGrantフローで取得する

Posted at

概要

OAuth2.0のAuthorizationCodeGrantという認可方式でGoogleの認可サーバからトークンを取得してみた

今回作るアプリの構成

構成図

oauth.png
ExpressとHTMLでクライアントアプリを作成しGoogleCloudPlatformからアクセストークンを取得してGmailAPIを叩けるようにする

シーケンス図

↓はアクセストークン取得~GmailAPI叩くまでの処理の概要をシーケンス図に書き起こしたもの
シーケンス図.png

GoogleAPIの設定

クライアントIDの発行

GoogleAPIにログインして適当なプロジェクトを作成する

メニューのAPIとサービス > 認証情報をクリックして↓のようなページへ
1.jfif

認証情報を作成 > OAuthクライアントIDの作成をクリックし、クライアントIDを発行する
2.png
アプリケーションの種類: ウェブアプリケーション
承認済みのリダイレクトURI: http://localhost:3000/auth/login/callback

作成したらOAuthクライアントのJSONファイルをダウンロードしておく

テストユーザーの発行

外部に公開していないクライアントの場合テストユーザーとして登録したGoogleアカウントじゃないとアクセスできないみたいなので追加する
OAuth同意画面をクリックしてテストユーザーステップまで適当に進めて追加する
1.png

クライアントアプリの実装

今回はフロントエンドをHTMLで、バックエンドをexpressで実装する
フロントエンドについてはバックエンドの各種エンドポイントにアクセスできればなんでもOK
バックエンドの実装については↓の項目を参照

バックエンドの実装

アプリの初期化

適当なディレクトリで↓を実行

npm init -y
npm i express cookie-parser uuid node-fetch
npm i -D webpack webpack-cli typescript ts-loader @types/express @types/cookie-parser @types/uuid @types/node-fetch

インストールが完了したら各種設定ファイルを作成/編集

tsconfig.json
{
  "compilerOptions": {
    "sourceMap": true,
    "target": "ES2021",
    "module": "ES2020",
    "moduleResolution": "node"
  }
}
webpack.config.js
const path = require("path");

module.exports = {
  mode: "development",
  entry: {
    handler: path.resolve(__dirname, "src/index.ts"),
  },
  module: {
    rules: [
      {
        test: /\.ts$/,
        use: "ts-loader",
      },
    ],
  },
  target: "node",
  output: {
    path: path.resolve(__dirname, "dist"),
    filename: "index.js",
    libraryTarget: "commonjs",
  },
  resolve: {
    extensions: [".ts", ".js"],
  },
};
package.json
// 省略
"scripts": {
  "build": "webpack"
},
// 省略

静的ファイル配信の実装

最初にHTMLを返す処理を実装する
src/index.tsを作成して編集

src/index.ts
import * as express from 'express';
import * as cookieParser from 'cookie-parser';
import { rootRouter } from './router/root-router';

const app = express();
app.use(cookieParser());
app.use(express.json());
app.use(express.urlencoded({ extended: true }));
app.use(express.static('static')); // ※静的ファイル等があるディレクトリを指定

app.use('/', rootRouter);

app.listen(3000, () => {
  console.log('Listening on port 3000');
});

src/router/root-router.tsを作成して編集

src/router/root-router.ts
import * as express from 'express';

export const rootRouter = express.Router();

rootRouter.get('/', (req, res) => {
  res.sendFile('static/index.html'); // ※HTMLファイルのパスを指定
});

動作確認

以下のコマンドでビルド&アプリ実行し、ブラウザでhttp://localhost:3000 にアクセスしてHTMLで書いたページが表示されたら成功👍

npm run build
node dist/index.js

アクセストークン取得処理の実装

src/router/auth-router.ts
import * as express from 'express';
import fetch from 'node-fetch';
import { v4 } from 'uuid';
import { credentials } from '../../credentials'; // OAuthクライアントのJSONファイル

export const authRouter = express.Router();

// Googleのサインイン画面へリダイレクト
authRouter.get('/login', (_, res) => {
  const state = v4();
  const params = {
    client_id: credentials.client_id,
    redirect_uri: credentials.redirect_uris[0],
    response_type: 'code',
    scope: 'https://www.googleapis.com/auth/gmail.readonly',
    state: state,
  };
  const encodedParams = new URLSearchParams(params);
  const endpoint =
    'https://accounts.google.com/o/oauth2/v2/auth?' + encodedParams;

  // cookieにstateをセット
  res.cookie('state', state);
  res.redirect(endpoint);
});

// 認可サーバからのリダイレクト先
authRouter.get('/login/callback', async (req, res) => {
  // cookieのstateとクエリのstateが一致するか検証
  if (req.query.state !== req.cookies.state) {
    res.status(400).send(`state is invalid.
    Received state: ${req.query.state}
    Cookie state: ${req.cookies.state}`);
    return;
  }
  const params = {
    client_id: credentials.client_id,
    client_secret: credentials.client_secret,
    code: req.query.code as string,
    grant_type: 'authorization_code',
    redirect_uri: credentials.redirect_uris[0],
  };

  const encodedParams = new URLSearchParams(params);

  const response = await fetch('https://oauth2.googleapis.com/token', {
    method: 'POST',
    headers: {
      'Content-Type': 'application/x-www-form-urlencoded',
    },
    body: encodedParams,
  });
  const responseBody = (await response.json()) as any;

  res.cookie('access_token', responseBody.access_token);
  res.redirect('http://localhost:3000');
});

動作確認

ビルド&実行→ブラウザでhttp://localhost:3000 にアクセス

npm run build
node dist/index.js

クライアントが表示されるのでイベント発火→http://localhost:3000/auth/login へリダイレクト
スクリーンショット 2021-09-24 184107.png

Googleのログイン画面へ移動するのでメールアドレス、パスワードを入力してログイン
4.png

クライアントへリダイレクトするので開発者ツールでCookieを確認
5.png
access_tokenが入っていれば成功👍

GmailAPI実行処理の実装

Cookieに入れたアクセストークンを使ってGmailAPIを叩く処理を実装する
今回は↓のAPIを叩けるようにしてみる
https://developers.google.com/gmail/api/reference/rest/v1/users/getProfile

src/router/gmailapi-router.ts
import * as express from 'express';
import fetch from 'node-fetch';

export const gmailRouter = express.Router();

gmailRouter.get('/user/profile', async (req, res) => {
  const mailaddress = req.query.mailaddress;

  const params = {
    emailAddress: mailaddress as string,
  };
  const encodedParams = new URLSearchParams(params);

  const response = await fetch(
    `https://gmail.googleapis.com/gmail/v1/users/${mailaddress}/profile?` +
      encodedParams,
    {
      method: 'GET',
      headers: {
        Authorization: `Bearer ${req.cookies.access_token}`, // Authorizationヘッダーにアクセストークンを挿入
      },
    }
  );
  const responseBody = (await response.json()) as any;

  res.send(responseBody);
});

クエリからメールアドレスを取り出してそれに紐づくアカウントの情報を取得するAPI
OAuth2.0のアクセストークンはAuthorizationヘッダーに入れてリソースサーバへ送信することになる
Authorizationヘッダーのフォーマット

Authorization: Bearer ${ACCESS_TOKEN} 
# "Bearer"の後ろに半角スペースが必要
# 地味に引っ掛かりやすいので注意

動作確認

アクセストークンの取得後上のGmailAPIへリクエストを投げる
↓のようにプロフィールを取得できればOK👍
6.png

1
0
1

Register as a new user and use Qiita more conveniently

  1. You get articles that match your needs
  2. You can efficiently read back useful information
  3. You can use dark theme
What you can do with signing up
1
0

Delete article

Deleted articles cannot be recovered.

Draft of this article would be also deleted.

Are you sure you want to delete this article?