プログラミング

React、Express + PassportでGoogle認証を行う方法

hebishima.shogo

はい、hebiです。
React、Express + PassportでGoogle認証を行ってみたので記事にしたいと思います。

スポンサーリンク

前提条件

Google認証を行う前提として以下の作業が完了していることとします。

  • Google Cloud Platformの登録
    Google Cloud PlatformにてOAuth同意画面の設定と認証情報の設定を行い、「クライアントID」、「クライアントシークレット」、「承認済みのリダイレクト URI」の取得を行ってください。取得方法は割愛します。
  • 以下の記事で作成したプロジェクトを利用します。
あわせて読みたい
TypeScriptでWebAPIを作ってみた(Node.js + routing-controllers)
TypeScriptでWebAPIを作ってみた(Node.js + routing-controllers)
あわせて読みたい
【React】とっても簡単!!Webサービスの作り方
【React】とっても簡単!!Webサービスの作り方

バックエンドの実装

バックエンド(サーバ側)でGoogle認証を行いますので、認証を行うための実装を行います!

passportのインストール

Google認証を行うために、以下をインストールします。

npm install --save @types/passport
npm install --save @types/passport-google-oauth2

passportの初期設定処理

Passportは認証のためにストラテジーと呼ばれるものを認証に使用します。
そのための初期設定を行います。

Google Cloud Platformで登録した際に生成された以下の値が必要です。

  • clientID
  • clientSecret
  • callbackURL
import passport from "passport";
const GoogleStrategy = require('passport-google-oauth20').Strategy;

passport.serializeUser(function(user:any, done:any) {
    done(null, user);
});

passport.deserializeUser(function(user:any, done:any) {
    done(null, user);
});

passport.use(new GoogleStrategy({
    clientID: 'xxxx',
    clientSecret: 'yyyy',
    callbackURL: 'https://localhost:3001/auth/google/callback',
    passReqToCallback: true,
},function(req:any, accessToken:any, refreshToken:any, profile:any, done:any) {
    process.nextTick(function(){
        return done(null, profile);
    });
}
));

export default passport;

googleControllerクラスの作成

Google認証を行うためのコントローラクラスを作成します。

/loginにアクセスするとGoogle認証画面に遷移します。

認証に成功すると/auth/google/callbackが実行され、successRedirectに指定した「http://localhost:3000/home」へリダイレクトされます。

import {
    JsonController,
    UseBefore,
    Get
} from 'routing-controllers';

import passport from '../Lib/passport';

@JsonController()
export class GoogleController {
    // ログイン処理(呼び出されるとGoogle認証画面に遷移)
    @UseBefore(passport.authenticate('google' , {scope: ['email', 'profile']}))
    @Get('/login')
    login() {
        // 使用しないがエラーになるため定義
    }

    // Googleで認証成功時に呼び出されるコールバック
    @UseBefore(passport.authenticate('google', {failureRedirect: '/', successRedirect: 'http://localhost:3000/home'}))
    @Get('/auth/google/callback')
    auth() {
        // 使用しないがエラーになるため定義
    }
}

認証チェック処理の作成

全てのリクエスト実行時に実行されるクラスを作成します。
認証済みの場合はnext()を実行します。
未認証の場合は401を返却します。
泥臭い方法ですが、/loginと/auth/googleは認証チェックを行わないようにしてます。

import { Middleware, ExpressMiddlewareInterface } from 'routing-controllers';
import { NextFunction, Request, Response } from 'express';

@Middleware({ type: 'before' })
export class AuthCheck implements ExpressMiddlewareInterface {
  public use(req: Request, res: Response, next: NextFunction): void {
    if (req.originalUrl == '/login' || req.originalUrl.startsWith('/auth/google'))
    {
      next();
      return;
    }
    if(req.isAuthenticated()) {
        next();
    } else {
        res.status(401).send();        
    }
  }
}

passportの適用

app.use(session())、app.use(passport.session())にてセッションを利用できるようにします。
useExpressServerにGoogleControllerを追加します。
アクセスする前に認証チェックを行うために、middlewaresにAuthCheckを追加します。

import 'reflect-metadata';
import express from 'express';
import bodyParser from 'body-parser';
import {
    useExpressServer
} from 'routing-controllers';
import { UserController, GoogleController } from './controllers';

import passport from './Lib/passport';
import {AuthCheck} from './Lib/authCheck';

const session = require('express-session');
// const cors = require('cors')

const PORT = 3001;

async function main() {

    const app = express();
    // app.use(cors())
    app.use(session({
        secret: "secret",
        resave: false ,
        saveUninitialized: true ,
    }));
    app.use(passport.session());
    
    // JSON 形式の HTTP リクエストを受け取る
    app.use(bodyParser.json());

    // コントローラを設定
    useExpressServer(app, {
        controllers: [
            UserController,
            GoogleController
        ],
        middlewares:[AuthCheck]
    });

     // 秘密鍵と証明書読み込み用にfs生成
    const fs = require('fs');

    // // 作成した秘密鍵と証明書を指定しサーバを作成
    const server = require('https').createServer({
        key: fs.readFileSync('./tls/privatekey.pem'),
        cert: fs.readFileSync('./tls/cert.pem'),
    }, app);

    // // サーバ起動
    server.listen(PORT, () => {
        console.log(`Express server listening on port ${PORT}`);
    });
}

main();

以上でバックエンドの実装は完了です。

実際にフロントエンドから実行してみましょう。

※axiosでの実行方法が記事にできていない技術を使用しています。

WebAPI実行確認

未認証時のAPI実行確認

Google認証前には、フロントエンドから作成したWebAPIを実行すると、401が返却されます。(testボタン)

ログイン画面

Google認証

では、loginボタンにて「”https://localhost:3001/login”」を実行し、Google認証画面が表示されることを確認します。
RestAPIで実行したかったのですが、Google側でCORSエラーになってしまうため、window.openで実現しました。

実行すると以下のようにGoogle認証画面が表示されます。

上記画面から認証に成功すると、バックエンドの「/auth/google/callback」がコールバックされ、さらにフロントエンドの「http://localhost:3000/home」へリダイレクトされます。
ホーム画面

認証後のAPI実行確認

サーバのWebAPIを実行すると、正常にデータを取得できました。(testボタン)

以上でフロント側の実装完了です。お疲れ様でした!

フロント側のソースコード

フロント側のソースコード載せておきます。

ログイン画面

import React, { useState } from "react";
import axios from "axios";
import { UserModel } from "../../../../Server/src/models";

const Login = () => {
  const [user, setUser] = useState<UserModel>();

  const googleLogin = () =>
    window.open("https://localhost:3001/login", "_self");

  const loginClick = () => {
    console.log("loginClick");
    axios
      .get("/users/1")
      .then((res) => {
        setUser(res.data);
      })
      .catch((error) => {
        alert(error);
      });
  };
  return (
    <div>
      <button onClick={loginClick}>test</button>
      <button onClick={googleLogin}>login</button>
      <div>{user?.name}</div>
      <div>{user?.mail}</div>
    </div>
  );
};

export default Login;

ホーム画面

import React, { useState } from "react";
import axios from "axios";
import { UserModel } from "../../../../Server/src/models";

const Home = (props: any) => {
  const [user, setUser] = useState<UserModel>();
  const loginClick = () => {
    console.log("loginClick");
    // URL
    axios
      .get("/users/1")
      .then((res) => {
        setUser(res.data);
      })
      .catch((error) => {
        alert(error);
      });
  };

  return (
    <div>
      <button onClick={loginClick}>test</button>
      <h1>Home</h1>
      <div>{user?.name}</div>
      <div>{user?.mail}</div>
    </div>
  );
};

export default Home;

ルータ

import React, { useState } from "react";
import "./App.css";

import { BrowserRouter, Routes, Route } from "react-router-dom";
import Login from "./page/login";
import Home from "./page/home";

const App = () => {
  return (
    <BrowserRouter>
      <Routes>
        <Route path={`/`} element={<Login />} />
        <Route path={`/login/`} element={<Login />} />
        <Route path={`/home/`} element={<Home />} />
      </Routes>
    </BrowserRouter>
  );
};

export default App;

最後に

React、Express+Passportを利用したGoogle認証の方法でした。
APIは未認証時は401エラーになりますが、フロントエンド側は未認証でもHome画面にブラウザでアクセスできてしまいます。フロント側の制御も必要になりますので後日記事にしたいと思います。

最後までお読みいただきありがとうございました。

スポンサーリンク
ABOUT ME
hebi
hebi
エンジニア
フルスタックエンジニアとして活躍中。
HTML5プロフェッショナル認定Level1、Level2所持者です。

未経験の方でも簡単にプログラミングを学べるようにと情報を発信しております。
記事URLをコピーしました