Vueでログイン管理(OAuth2)#
Webアプリケーションでは、限られた人のみ、アプリケーションを使わせたいときがあります。
ここでは、特定のGoogle Accountを持っている人のみがアプリケーションを使えるようにしていきます。
前提#
Googleアカウントを持っていること。 こちらの手順でアプリを用意してください。
設定#
Google Accountを持っているかどうかを確認するためにはOAuth2という仕組みを使うことが出来ます。 OAuth2を使うことで、アプリケーション自体でアカウントを管理しなくても、ログイン機能をアプリケーションに組み込むことができます。 GoogleでOAuth2を作るためには、Googleにアプリケーションを登録する必要があります。
https://console.cloud.google.com/apis/ に移動して
「新しいプロジェクト」を押します。
任意の「プロジェクト名」を入力して「作成」を押します。 「APIとサービス」→「認証情報」を押して 「認証情報を作成」を選択します。 「OAuth クライアント ID」を選択します。 アプリケーションの種類に「ウェブ アプリケーション」を選択します。 下の方にスクロールして、「認証済みのリダイレクトURI」に http://localhost:3000/oauthRedirect/google を追加します。ほかは変更しなくて良いです。 そして、作成ボタンを押します。 「クライアント ID」 と 「クライアントシークレット」が生成されるのでメモしておきます。 「クライアント ID」 と 「クライアントシークレット」は他の人にばれないようにしてください。
これでGoogle AccountでOAuthを使用する準備が出来ました。 続きまして、アプリケーションでOAuthを使えるようにしていきます。
OAuth実装#
OAuthを以下の要領で実装します。 1. ブラウザでログインリンクを押すと Vueの処理でサーバのControllerからGoogleのURLを取得し、Googleに画面遷移する。 2. Googleでログインすると、こちら側のブラウザアプリに遷移する。ブラウザアプリがサーバアプリに対してログインが正しいことを確認するように依頼する。 5. サーバアプリがGoogleにログインが正しいことを確認する。 6. Vueの処理でログイン後のページへ遷移する。
まずは、Vueで画面を作成し、Googleに遷移できるようにする。 src/front/components/Login.vue というファイルを以下の内容で作成します。
<template>
<div>
<a href="" @click="moveToGoogleOAuth()" >Google でログイン</a>
</div>
</template>
<script lang="ts">
import Vue from "vue"
import axios from "axios"
export default Vue.extend({
methods: {
async moveToGoogleOAuth(){
const response = await axios.get("/api/login/google/loginPath");
window.location.href = response.data.url
}
}
});
</script>
サーバでGoogleのリンクを作成するようにします。
src/backend/controllers/login.ts を作成します。GoogleのURLを生成し、ブラウザに渡します。
import express, { Router } from 'express'
const querystring = require('querystring');
var router: Router = express.Router();
const client_id = process.env.GOOGLE_OAUTH_CLIENT_ID || '{Google からコピーしたクライアントID}'
const client_secret = process.env.GOOGLE_OAUTH_CLIENT_SECRET || '{Google からコピーしたクライアントシークレット}'
const redirect_uri_postfix = '/oauthRedirect/google'
const response_type = 'code'
const scope = 'email'
const GOOGLE_URL_AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'
const GOOGLE_URL_TOKEN = 'https://www.googleapis.com/oauth2/v4/token'
const GOOGLE_URL_USERINFO = 'https://www.googleapis.com/oauth2/v3/userinfo'
router.get('/google/loginPath', function (req, res) {
var redirect_uri = req.protocol + "://" + req.get("Host") + redirect_uri_postfix;
const params = querystring.stringify({
client_id,
redirect_uri,
response_type,
scope,
})
res.send({ url: `${GOOGLE_URL_AUTH}?${params}` })
});
export default router;
作ったcontrollerを公開します。 src/backend/index.tsを以下の内容で編集します。
import express from 'express'
import messageController from './controllers/message'
import enqueteController from './controllers/enquete'
import loginController from './controllers/login'//この行を追加
import { createConnection } from 'typeorm'
import Enquete from './models/Enquete'
const app: express.Express = express()
app.use(express.json())
app.use(express.urlencoded({ extended: true }))
app.use("/messages", messageController)
app.use("/api/enquetes", enqueteController)
app.use("/api/login", loginController)// この行を追加
app.use("/", express.static(__dirname + "/public"))
app.get('*', (req, res) => {
res.status(200).sendFile(__dirname + "/public/index.html");
});
!async function initialize() {
await createConnection({
type: "postgres",
url: process.env.DATABASE_URL || "postgres://postgres:postgres@localhost:5432/postgres",
synchronize: true,
entities: [
Enquete,
],
extra: {
ssl: (!!process.env.DATABASE_SSL) ? {
rejectUnauthorized: false,
} : false,
}
});
const port = process.env.PORT || 3000;
app.listen(port, () => {
console.log("ポート" + port + "番で起動しました!")
})
}()
そして、Google から戻される画面を用意します。 src/front/components/RedirectedFromGoogle.vue というファイルを以下の内容で作成します。
<template>
<div>{{message}}</div>
</template>
<script lang="ts">
import Vue from "vue"
import axios from "axios"
export default Vue.extend({
data(){
return {
message: ""
}
},
async created(){
const code = this.$route.query.code;
const redirect_uri = window.location.href.split("?")[0];
try {
const response = await axios.post("/api/login/google/loginPath", { code, redirect_uri });
const authInfo = response.data;
if(authInfo.access_token){
Vue.prototype.$loginInfo = {
oAhtuProvider: "google",
accessToken: authInfo.access_token
}
this.$router.push("/enquete/list")
return;
}
} catch(throughtToNext){
}
this.message = "Googleでのログイン検証に失敗しました。再度やり直してください。"
setTimeout(()=>{
this.$router.push("/login")
}, 5000);
}
});
</script>
src/backend/controllers/login.ts を編集し、Googleからauth_tokenを取得できるようにします。
import express, { Router } from 'express'
const querystring = require('querystring');
import axios from 'axios';
var router: Router = express.Router();
const client_id = process.env.GOOGLE_OAUTH_CLIENT_ID || '{Google からコピーしたクライアントID}'
const client_secret = process.env.GOOGLE_OAUTH_CLIENT_SECRET || '{Google からコピーしたクライアントシークレット}'
const redirect_uri_postfix = '/oauthRedirect/google'
const response_type = 'code'
const scope = 'email'
const GOOGLE_URL_AUTH = 'https://accounts.google.com/o/oauth2/v2/auth'
const GOOGLE_URL_TOKEN = 'https://www.googleapis.com/oauth2/v4/token'
const GOOGLE_URL_USERINFO = 'https://www.googleapis.com/oauth2/v3/userinfo'
router.get('/google/loginPath', function (req, res) {
var redirect_uri = req.protocol + "://" + req.get("Host") + redirect_uri_postfix;
const params = querystring.stringify({
client_id,
redirect_uri,
response_type,
scope,
})
res.send({ url: `${GOOGLE_URL_AUTH}?${params}` })
});
// ここから追加
router.post('/google/loginPath', async function (req, res) {
try {
var params = new URLSearchParams();
params.append('client_id', client_id);
params.append('client_secret', client_secret);
params.append('code', req.body.code);
params.append('grant_type', 'authorization_code');
params.append('redirect_uri', req.body.redirect_uri);
const response = await axios.post(GOOGLE_URL_TOKEN, params)
const access_token = response.data.access_token;
const userInfoResponse = await axios.get(GOOGLE_URL_USERINFO, {
headers: {
'Authorization': `Bearer ${access_token}`,
}
});
const userInfo = userInfoResponse.data;
res.send({
access_token,
userInfo
});
} catch (e) {
res.status(400).send();
}
});
// ここまで追加
export default router;
作成したコンポーネントをルーティングに加えます。 src/front/components/App.vueを編集します。
<template>
<div>
<router-view />
</div>
</template>
<script lang="ts">
import Vue from "vue";
import VueRouter from "vue-router";
import Enquete from "./PostEnquete.vue";
import EnqueteResultList from "./EnqueteResultList.vue";
import Login from "./Login.vue";// この行を追加
import RedirectedFromGoogle from "./RedirectedFromGoogle.vue";// この行を追加
const router = new VueRouter({
mode: 'history',
base: __dirname,
routes: [
{ path: '/', component: Enquete }
,{ path: '/enquete/list', component: EnqueteResultList }
,{ path: '/login', component: Login }// この行を追加
,{ path: '/oauthRedirect/google', component: RedirectedFromGoogle }// この行を追加
]
})
Vue.use(VueRouter);
export default Vue.extend({
router
});
</script>
次に、ログインしていて、かつ特定のメールアドレスを持つ人のみがアンケート一覧を見られるようにします。
src/front/components/EnqueteResultList.vue を編集し、アンケート一覧取得時にauth_tokenを渡すようにします。
<template>
<div>
<h1>アンケート回答一覧</h1>
<table>
<tr>
<th>点数</th>
<th>ご意見・ご要望</th>
<th>メールアドレス</th>
</tr>
<tr v-for="enquete in enquetes" :key="enquete.id">
<td>{{enquete.point}}</td>
<td>{{enquete.message}}</td>
<td>{{enquete.mail}}</td>
</tr>
</table>
</div>
</template>
<script lang="ts">
import Vue from "vue";
import axios from "axios"; //この行を追加
export default Vue.extend({
data() {
return {
enquetes: [],
};
},
async created() {
//const response = await axios.get("/api/enquetes"); // この行をコメントアウト(先頭に "//" を入れる。)
const response = await axios.get("/api/enquetes", {headers:{Authorization:Vue.prototype.$loginInfo.accessToken}});//この行を追加
this.enquetes = response.data;
},
});
</script>
最後に、サーバからアンケート情報を渡すときは、auth_tokenと、その持ち主をチェックするようにします。 src/backend/controllers/enquete.tsを編集します。
import express, { Router } from 'express'
import { getRepository } from 'typeorm'
import Enquete from '../models/Enquete'
import axios from 'axios'
const GOOGLE_URL_USERINFO = 'https://www.googleapis.com/oauth2/v3/userinfo' // この行を追加
const ADMIN_MAIL = "あなたのGoogle Accountのメールアドレス" // この行を追加
var router: Router = express.Router();
router.post('/', async function (req, res) {
const enquete = req.body;
const EnqueteRepository = getRepository(Enquete);
await EnqueteRepository.save(enquete);
res.send(enquete);
});
router.get('/', async function (req, res) {
// ここから追加
const userInfoResponse = await axios.get(GOOGLE_URL_USERINFO, {
headers: {
'Authorization': `Bearer ${req.header("Authorization")}`,
}
});
if (ADMIN_MAIL != userInfoResponse.data.email) {
res.status(401).send()
return;
}
// ここまで追加
const EnqueteRepository = getRepository(Enquete);
const enquetes = await EnqueteRepository.find();
res.send(enquetes);
});
export default router;
これで作成が完了しました。 動作確認してみましょう。 プロジェクトのトップディレクトリで以下のコマンドを実行します。
npm run watch
http://localhost:3000/enquete/list にアクセスしても、一覧が見れなくなっています。
http://localhost:3000/login から「Google でログイン」を押して、Googleにログインすると、一覧が見れるようになっています。
これで、GoogleのOAuth認証でデータ取得に制限をかけることが出来ました。 これでセキュリティーもバッチリなので、次はアプリケーションをインターネットに公開してみましょう。