概要
Quasar で OpenIDConnect 認証する方法を紹介します
バックエンドに Flask アプリケーションを使って連携します
今回連携する OpenIDConnect 先は Onelogin になります
環境
- macOS 11.7.4
- nodejs 19.6.0
- yarn 1.22.19
- quasar 2.0.0
- Python 3.10.9
- Flask 2.2.3
- Flask-CORS 3.0.10
- authlib 1.2.0
Quasarプロジェクトの作成
こちらを参考に作成しましょう
error @achrinza/node-ipc@9.2.5: The engine "node" is incompatible with this module.
が発生する場合は
- yarn config set ignore-engines true
を設定しましょう
Quasar 側のログインテストページの作成
- vim src/pages/LoginPage.vue
<template>
<div>
<h1>Login Test Page</h1>
<button @click="login">Login</button>
<button @click="logout">Logout</button>
<div>{{ data }}</div>
</div>
</template>
<script setup>
import { ref, onMounted } from 'vue'
import axios from 'axios'
const data = ref('nodata')
const api = axios.create({
baseURL: 'http://localhost:5000',
withCredentials: true
})
const fetchUser = async(value) => {
api.get('/user')
.then((response) => {
data.value = response.data
})
.catch((error) => {
console.log(error)
})
}
const login = async(value) => {
window.location.href = 'http://localhost:5000/login';
}
const logout = async(value) => {
api.get('/logout')
.then((response) => {
data.value = response.data
})
.catch((error) => {
console.log(error)
})
}
onMounted(() => {
fetchUser()
})
</script>
またルーティングを追加しましょう
- vim src/router/routes.js
const routes = [
{
path: '/',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/IndexPage.vue') }
]
},
{
path: '/login',
component: () => import('layouts/MainLayout.vue'),
children: [
{ path: '', component: () => import('pages/LoginPage.vue') }
]
},
// Always leave this as last one,
// but you can also remove it
{
path: '/:catchAll(.*)*',
component: () => import('pages/ErrorNotFound.vue')
}
]
export default routes
起動は
- quasar dev
で行います
localhost:8080/#/login にアクセスすると OpenIDConnect のログインテスト用のページが表示されます
バックエンドの Flask アプリをまだ作成していないので動作はしません
Flask でバックエンドアプリの作成
flask-cors, authlib を使うのでインストールしましょう
- pipenv install flask-cors authlib
今回 OpenIDConnect との連携はすべて Flask 側で行います
認証が成功して Flask 側で ID トークンを受け取ったらそれを session (cookies) に保存して SPA 側と共有します
from flask import (Flask,
url_for,
session,
redirect)
from flask_cors import CORS
from authlib.integrations.flask_client import OAuth
app = Flask(__name__)
app.secret_key = "xxx"
app.config["ONELOGIN_CLIENT_ID"] = "xxxx"
app.config["ONELOGIN_CLIENT_SECRET"] = "xxxx"
app.config["SESSION_COOKIE_SECURE"] = True
app.config["SESSION_COOKIE_HTTPONLY"] = True
CORS(app,
supports_credentials=True,
max_age=300)
oauth = OAuth(app)
oauth.register(
name="onelogin",
server_metadata_url="https://xxxxxx.onelogin.com/oidc/2/.well-known/openid-configuration",
client_kwargs={"scope": "openid email profile"}
)
@app.route("/login")
def login():
redirect_uri = url_for("auth", _external=True)
return oauth.onelogin.authorize_redirect(redirect_uri)
@app.route("/user")
def user():
user = session.get("user")
if user is None:
return "Not logged in yet."
return f"Login successful. => {user}"
@app.route("/callback")
def auth():
token = oauth.onelogin.authorize_access_token()
session["user"] = token["userinfo"]
return redirect("http://localhost:8080/#/login")
@app.route("/logout")
def logout():
session.pop("user", None)
return "Logout successful."
起動しましょう
- pipenv run flask run
これでバックエンドは localhost:5000 で起動します
当然ですが Quasar は localhost:8080 で起動しバックエンドは localhost:5000 で起動しているので CORS の設定が必要です
ポイント
ログインはSPA側からバックエンドにリダイレクトする
OpenIDConnet のログインページへのリダイレクトは Flask 側で行います
なので SPA 側からログインページを表示したい場合はまずバックエンドの /login URI へリダイレクトする必要があります
SPA からリダイレクトをせずに Flask から直接ログインページへリダイレクトしようとすると CORS エラーになるので注意が必要です
SPA側とバックエンド側の認証情報の共有はセッション(cookies)を使う
クッキー情報はドメインごとに管理されます
しかし今回はドメインが異なるので異なるドメイン間でクッキーを共有する必要があります
Quasar 側では withCredentials: true
を設定することで axios リクエスト時にバックエンドにクッキー情報を送信できます
Flask 側では CORS(app, supports_credentials=True, max_age=300)
という感じで supports_credentials=True
にすることで送信された異なるドメインから送信されたクッキーを受け入れることができます
セキュリティ的な面を考慮する
今回はクッキーの Secure 属性と HTTP-Only 属性のみ有効にしています
あとは CORS 側の設定で max_age を設定して共有する有効期限を定めています
そもそもクッキーを使わない方法もあるようなのでプロダクションではセキュリティ麺を考慮した設定が実装の追加が必要になるかなと思います
最後に
Quasar に OpenIDConnect を使った認証機能を追加してみました
基本はバックエンド側のアプリが必要になると思います (調べてみると SPA オンリーでもできるっぽいですが)
SPA はモダンなのですが考えることが多いのが厄介です
そういうのも含めると普通に Web アプリケーションとして作成するのがいいのかもしれません
0 件のコメント:
コメントを投稿