React、Express + PassportでGoogle認証を行う方法
はい、hebiです。
React、Express + PassportでGoogle認証を行ってみたので記事にしたいと思います。
前提条件
Google認証を行う前提として以下の作業が完了していることとします。
- Google Cloud Platformの登録
Google Cloud PlatformにてOAuth同意画面の設定と認証情報の設定を行い、「クライアントID」、「クライアントシークレット」、「承認済みのリダイレクト URI」の取得を行ってください。取得方法は割愛します。 - 以下の記事で作成したプロジェクトを利用します。
バックエンドの実装
バックエンド(サーバ側)で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();
以上でバックエンドの実装は完了です。
実際にフロントエンドから実行してみましょう。
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画面にブラウザでアクセスできてしまいます。フロント側の制御も必要になりますので後日記事にしたいと思います。
最後までお読みいただきありがとうございました。