Authentification Azure avec Vue 3 et Pinia

mercredi 13 septembre 2023
Image par Mathieu LALLEMAND.
Mathieu LALLEMAND Photo de l'auteur

Introduction

Récemment je suis tombé sur ce post Vue JS User Authentication de Microsoft paru en décembre 2020. Ce post me semblait inutilement complexe, utilisant VueX, et ne rendant pas vraiment homage à la simplicité de VueJS & Pinia.

Je me suis dit que ça pourrait être intéressant de revisiter cette problématique en Vue 3 avec Pinia.

Ensemble, partons de 0 et voyons comment mettre en place l'authentification au travers de Microsoft Azure depuis Vue 3.

Installation de Vue

Comme indiqué dans la documentation de Vue nous allons lancer les commandes suivantes pour initialiser un nouveau projet :

$ npm create vue@latest

Pour la demo, nous allons créer le projet vue3-mslogin-demo avec les paramètres suivants

✔ Project name: … vue3-mslogin-demo
✔ Add TypeScript? … No
✔ Add JSX Support? … No
✔ Add Vue Router for Single Page Application development? … No
✔ Add Pinia for state management? … Yes
✔ Add Vitest for Unit testing? … No
✔ Add an End-to-End Testing Solution? … No
✔ Add ESLint for code quality? … Yes
✔ Add Prettier for code formatting? … Yes

Reste enfin à lancer le projet :

$ cd vue3-mslogin-demo
$ npm install
$ npm run format # Optionnel
$ npm run dev

Pour la suite, veillez à bien faire pointer votre navigateur vers http://localhost:5173/ afin d'éviter les erreurs de redirection Microsoft qui peuvent arriver si vous pointez sur http://127.0.0.1:5173

Par défaut, l'assistant de création de projet va générer des classes, des composants et un store Pinia pour vous aider à prendre vueJS en main. Nous ne nous en servirons pas pour la demo, mais vous pourrez toujours aller y jeter un coup d'oeil. Pour le moment, afin de simplifier les choses aux maximum, nous allons désactiver les styles.

Pour cela, allez dans le fichier /src/main.js et mettez simplement la 1ère ligne import './assets/main.css en commentaire

Nous allons aussi en profiter pour simplifier le composant principal /src/App.vue au maximum :

<script setup>
import MsLogin from './components/MsLogin.vue';
</script>

<template>
<MsLogin></MsLogin>
</template>

Enfin, nous allons réaliser un composant vide dans le fichier ./components/MsLogin.vue

<template>
This is an empty component...
</template>

Après avoir lancer un npm run dev et en vous rendant sur http://localhost:5173/ vous devriez avoir l'affichage suivant :

EmptyApp

Création des variables d'environnement pour l’authentification

Vite permet de définir des variables d'environnement qui seront injectées dans le code au moment du run dev ou du run build. C'est très pratique pour permettre la modification rapide de certaines données sans aller fouiller dans le code. C'est aussi une bonne pratique de sortir les données sensibles du code pour éviter qu'elles ne se retrouvent dans l'historique du Git.

Créez un fichier .env à la racine du projet avec le contenu suivant :

#.env
VITE_MS_CLIENT_ID='YOUR_APPLICATION_CLIENT_ID'
VITE_MS_TENANT_ID='YOUR_APPLICAITON_TENANT_ID'

Par convention, chez NCI excluons toujours les fichier .env de notre GIT. Pour ce faire, il suffit d'ajouter la ligne suivante à la fin du fichier .gitignore

(...)
.env

Pour permettre aux autres développeurs de connaître ces variables et de les redéfinir, nous réalisons toujours une copie du fichier .env, sans aucune clé afin de rapidement remonter le projet :

cp .env .env.sample

RAPPEL : Pensez à bien vérifier qu'aucune information sensible ne reste dans le fichier .env.sample avant de le commit sur le projet !

Dans le code, le contenu de ces variables pourra être récupéré comme ceci :

// Exemple d'utilisation d'une variable d'environnement avec Vite
const clientId = import.meta.env.VITE_MS_CLIENT_ID
const tenantId = import.meta.env.VITE_MS_TENANT_ID

Enregistrement d'une application dans Microsoft Azure

Maintenant que nous avons créé notre application en Vue, nous allons l'enregistrer dans Azure. Pour cela rendez-vous sur le portail d'Azure et authentifiez vous, puis dans le menu en haut à gauche choisissez Microsoft Entra ID

Entra ID Icon

Dans la vue d'ensemble, cliquez sur le bouton Ajouter puis Inscription d'application

Register App Link

Dans le masque d'enregistrement de l'application spécifiez :

  • Le nom de votre application : vue3-mslogin-demo
  • Le type de compte qui pourra s'y connecter : Tous les comptes, 3eme option
  • L'URI de redirection :
    • Type d'application : SPA (Single Page Application)
    • URL : http://localhost:5173

Register App Form

Validez ensuite le formulaire. Azure va enregistrer votre application et vous affichera la page d'information de cette application fraîchement créée.

Vous pouvez maintenant copier le tokens du champs ID d'application (client) dans votre .env sur VITE_MS_CLIENT_ID et l'ID d'annuaire (locataire) sur VITE_MS_TENANT_ID situés sur la page d'accueil de votre application dans Azure.

Get App Ids

Création du store msAuthStore

Pour permettre à Vue d'authentifier les utilisateurs Microsoft, nous allons réaliser un store dédié. Ce store va permettre aux composants qui l'importent de réaliser une authentification via Microsoft Azure au moyen d'un package créé par Microsoft.

Quand l'authentification aura réussie, la variable account du store contiendra les informations de l'utilisateur. Chaque composants intégrant ce store pourra alors avoir accès à ces informations.

Pour commencer, installons la librairie Microsoft qui va nous aider à connecter les utilisateurs :

$ npm install @azure/msal-browser -D

Pour ce store, j'ai choisi d'utiliser l'Option API afin d'en simplifier la compréhension et la lecture.

Créez un fichier ./stores/msAuthStore.js avec le contenu suivant :

import { defineStore } from 'pinia'
import { PublicClientApplication } from '@azure/msal-browser';

export const useMsAuthStore = defineStore('msAuthStore', {

// ---------------------------------------------------------------------
state: () => ({
clientId: import.meta.env.VITE_MS_CLIENT_ID, // Application Client ID
tenantId: import.meta.env.VITE_MS_TENANT_ID, // Application Directory / Tenant ID
cacheLocation: 'localStorage', // Cache location used by MSAL
instance : null, // Public Client Application instance
account : null, // User account
}),

// ---------------------------------------------------------------------
getters: {
// Is the user signed in?
isSignedIn: (state) => !!state.account
},

// ---------------------------------------------------------------------
actions: {

// Configure the Public Client Application instance
config() {
this.instance = new PublicClientApplication({
auth: {
clientId: this.clientId,
authority: `https://login.microsoftonline.com/${this.tenantId}`,
},
cache: {
cacheLocation: this.cacheLocation,
},
})
},

async loadCache() {
if (!this.instance) this.config();
const accounts = await this.instance?.getAllAccounts();
if (!accounts || accounts.length == 0) return;
this.account = accounts[0];
},

// Sign in the user
async signIn() {
if (!this.instance) this.config();
await this.instance.initialize();
await this.instance.handleRedirectPromise();
await this.instance.loginPopup({})
const myAccounts = this.instance.getAllAccounts();
this.account = myAccounts[0];
},

// Sign out the user
signOut() {
this.account = null;
}
},
})

Ce store va utiliser les variables d'environnement et la librairie Microsoft pour authentifier l'utilisateur.

Outre l'accès a la variable account contenant les informations de l'utilisateur, il dispose d'un getter isSignedIn qui reverra true ou false selon que l'utilisateur est connecté ou non.

Il dispose enfin de quatres méthodes :

  • la première permet de configurer l'instance d'authentification,
  • la seconde va permettre de vérifier au chargement du composant si l'utilisateur a déjà été authentifié via le cache (ici le localStorage est utilisé).
  • La troisième permet de connecter l'utilisateur (signIn)
  • La dernière permet de déconnecter (signOut) l'utilisateur de l'application (sans pour autant le déconnecter de son compte Microsoft).

Vous noterez sans doute l'absence de Try/Catch : je préfère les réaliser dans les pages ou les composants ce qui est rendu possible par l'utilisation des fonctions asynchrones. Pour la démo, je ne les inclus pas, toujours dans un objectif de lisibilité.

Creation du composant d'authentification

Enfin viens le moment de réaliser le composant qui va réaliser l'authentification via le Store Pinia.

<script setup>
import { onMounted } from 'vue';
import { useMsAuthStore } from "@/stores/msAuthStore";
const msAuthStore = useMsAuthStore();

onMounted(() => {
msAuthStore.loadCache()
});
</script>

<template>
<button @click="msAuthStore.signIn()" v-show="!msAuthStore.isSignedIn"> SignIn </button>
<button @click="msAuthStore.signOut()" v-show="msAuthStore.isSignedIn"> SignOut </button>

<div v-if="msAuthStore.isSignedIn">
Congratulations!<br />
You are logged as <b>{{ msAuthStore.account?.name }}</b> {{ msAuthStore.account?.email }}
</div>
</template>

Dans la partie <script setup>, on charge le store. C'est réalisé via ces deux lignes :

import { useMsAuthStore } from "@/stores/msAuthStore";
const msAuthStore = useMsAuthStore();

Il est alors possible d'utiliser directement le store via la variable msAuthStore dans le template.

Notez le onMounted, qui au chargement du composant, va demander à pinia de regarder si l'utilisateur est déjà connecté.

Vous devriez avoir cet affichage :

SignIn

En cliquant sur le bouton, une fenêtre de connexion Microsoft devrait s'ouvrir. Un fois connecté vous devriez avoir un affichage de ce style :

Logged & SignOut

Et voilà !

Conclusion

J'espère que ce petit bout de code vous aura permis de voir à quel point il est trivial de réaliser une authentification Azure avec Vue JS.

Au plaisir de se recroiser au détours de nouveaux tutoriels.