dev.toにも投稿したのですが、こちらにも。
Microsoft Authentication Library (MSAL)は、簡単に言えばEntra ID(Azure AD)やAzure AD B2Cへ接続して認証認可をするためのライブラリーです。いろんな言語環境に対応していますが、その中の一つである msal-browser はSPAで認証認可してIDトークンやアクセストークンを得るためのもの。
MSのサンプルコードにはReactやAngular、Vanilla.jsなどで使う例があるのですが、Vueはなかったので、MSAL.jsを使ってウェブフロントエンドだけでAzureAD認証する | フューチャー技術ブログ を参考にして書いてみた。
ソース
src/plugins/msal.ts
import { ref, computed, ComputedRef, inject } from "vue";
import { type Plugin, type Ref } from 'vue';
import {
Configuration,
PublicClientApplication,
LogLevel,
AuthenticationResult,
} from "@azure/msal-browser";
export type Msal = {
isAuthenticated: ComputedRef<boolean>,
result: Ref<AuthenticationResult | null>,
login: () => Promise<void>,
logout: () => Promise<void>,
};
export const createMsal = async () => {
const config: Configuration = {
auth: {
clientId: import.meta.env.VITE_AUTH_CLIENTID,
authority: import.meta.env.VITE_AUTH_AUTHORITY,
knownAuthorities: [import.meta.env.VITE_AUTH_TENANT_DOMAIN],
redirectUri: import.meta.env.VITE_AUTH_REDIRECT_URI,
},
cache: {
cacheLocation: "localStorage",
storeAuthStateInCookie: false,
},
system: {
loggerOptions: {
loggerCallback: (level, message, containsPii) => {
if (containsPii) {
return;
}
switch (level) {
case LogLevel.Error:
console.error(message);
return;
case LogLevel.Warning:
console.warn(message);
return;
case LogLevel.Info:
console.info(message);
return;
case LogLevel.Verbose:
console.debug(message);
return;
}
},
},
},
};
const scopes = (import.meta.env.VITE_AUTH_SCOPES as string).split(' ').filter(Boolean);
const client = new PublicClientApplication(config);
await client.initialize();
const result = ref(await client.handleRedirectPromise());
const isAuthenticated = computed(() => result.value != null);
const login = async () => {
if (client.getAllAccounts().length > 0) {
client.setActiveAccount(client.getAllAccounts()[0]);
result.value = await client.acquireTokenSilent({
redirectUri: config.auth.redirectUri,
scopes: scopes
});
} else {
await client.acquireTokenRedirect({
redirectStartPage: location.href,
redirectUri: config.auth.redirectUri,
scopes: scopes
});
}
};
const logout = async () => {
await client.logoutRedirect();
}
const msalPlugin: Plugin = {
install(app) {
app.provide<Msal>("msal", { isAuthenticated, result, login, logout });
}
}
return msalPlugin;
};
export const useMsal = () => {
return inject<Msal>("msal")!;
}
使い方
await
しなくても大丈夫な気もするけど、いちおう。
src/main.ts
import { createApp } from "vue";
import { createMsal } from "@/plugins/msal";
import App from "@/App.vue";
const app = createApp(App);
app.use(pinia);
(async () => {
const msal = await createMsal();
app.use(msal);
app.mount("#app");
})();
src/component/HelloWorld.vue
<script setup lang="ts">
import { useMsal } from '@/plugins/msal';
defineProps<{ msg: string }>()
const { isAuthenticated, result, login, logout } = useMsal();
console.log(result.value?.idToken);
console.log(result.value?.accessToken);
</script>
<template>
<h1>{{ msg }}</h1>
<div class="card">
<template v-if="isAuthenticated">
<p>
Hello, {{ result?.account.name }} !
</p>
<button type="button" @click="logout">Logout</button>
</template>
<template v-else>
<button type="button" @click="login">Login</button>
</template>
</div>
</template>
<style scoped>
</style>