├── .eslintrc.cjs ├── .gitignore ├── .npmrc ├── README.md ├── assets ├── Microsoft_logo.svg.png └── css │ └── main.css ├── components ├── LoginScreen.vue └── UserProfile.vue ├── composables ├── useAppUser.ts └── useMSAuth.ts ├── layouts └── default.vue ├── middleware └── auth.global.ts ├── nuxt.config.ts ├── package-lock.json ├── package.json ├── pages ├── index.vue └── login.vue ├── plugins └── msal.ts ├── public └── favicon.ico ├── tailwind.config.js └── tsconfig.json /.eslintrc.cjs: -------------------------------------------------------------------------------- 1 | module.exports = { 2 | root: true, 3 | extends: ["@nuxt/eslint-config"], 4 | }; 5 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | # Nuxt dev/build outputs 2 | .output 3 | .nuxt 4 | .nitro 5 | .cache 6 | dist 7 | 8 | # Node dependencies 9 | node_modules 10 | 11 | # Logs 12 | logs 13 | *.log 14 | 15 | # Misc 16 | .DS_Store 17 | .fleet 18 | .idea 19 | 20 | # Local env files 21 | .env 22 | .env.* 23 | !.env.example 24 | -------------------------------------------------------------------------------- /.npmrc: -------------------------------------------------------------------------------- 1 | shamefully-hoist=true 2 | strict-peer-dependencies=false 3 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Microsoft Authentication Library (MSAL) with Nuxt 3 🚀 2 | 3 | ## Demo 🔍 4 | 5 | 6 | 7 | https://github.com/Akash52/msal-with-nuxt3/assets/31063892/77e1351c-df21-4a91-bdd1-7cc53c387d81 8 | 9 | 10 | 11 | ## Features ✨ 12 | 13 | - **🔁 Handle Redirect Promise**: The plugin handles the redirect promise after login or redirect and obtains the response. 14 | - **🎉 Event Callback on Login Success**: An event callback is added to detect login success events, triggering the setup of a token expiration timer. 15 | - **⏰ Token Expiration Timer**: A timer is set up to automatically refresh the access token upon expiration. 16 | - **🔄 Refresh Access Token**: The plugin can refresh the access token silently using the `acquireTokenSilent` method. 17 | - **🙌 Handle Response**: The plugin handles the response after login or redirect and sets up the token expiration timer if the response includes an account. 18 | - **🔑 Acquire Token Silently**: The plugin provides a method to acquire the access token silently. 19 | - **🚀 Sign In with Redirect**: Users can sign in with redirect using the `signIn` method. 20 | - **🔍 Get All MSAL Accounts**: The plugin provides a method to retrieve all MSAL accounts. 21 | - **🔒 Check Authentication Status**: The `isAuthenticated` method allows checking if the user is authenticated. 22 | - **👋 Sign Out**: Users can sign out using the `signOut` method. 23 | 24 | ## Installation 🛠️ 25 | 26 | ```bash 27 | # Using npm 28 | $ npm install --save @azure/msal-browser 29 | 30 | # Using yarn 31 | $ yarn add @azure/msal-browser 32 | ``` 33 | 34 | ## Usage 🌟 35 | 36 | ### Register the app on the Azure portal 37 | 38 | 1. Go to https://portal.azure.com/ 39 | 2. Search for Azure Active Directory in your organization and select `App Registrations` from the left panel and click on `New registration`. 40 | 41 |  42 | 43 | 44 | 45 | 3. Register the application by giving your name, selecting supported account types (who can use this application) based on requirements, and clicking register. 46 | 47 |  48 | 49 | 4. Register redirect URL 50 | https://yourapplication.com/ or for development http://localhost:3000/ 51 | 52 | Step 1 : 53 |  54 | 55 | Step 2 : 56 | 57 |  58 | 59 | Step 3 : 60 | 61 |  62 | 63 | 5. Copy the app's essential info, create an `.env` file at the root of your project and set the value of the below .env variables. 64 | 65 | 66 |  67 | 68 | 69 | ##### Refer to the image above to set the values for CLIENT and Tenet ID in the following ENV. 70 | 71 | ``` 72 | CLIENTID= 73 | AUTHORITY=https://login.microsoftonline.com 74 | REDIRECT_URI=http://localhost:3000 75 | POSTLOGOUT_REDIRECT_URI=http://localhost:3000 76 | ``` 77 | 78 | ### After Configure ENV 79 | 80 | #### Our Final step 81 | 82 | Run development server 83 | ``` 84 | npm run dev 85 | ```` 86 | 87 | #### Official documentation guide for Register an application with the Microsoft identity platform 88 | 89 | - [https://learn.microsoft.com/en-gb/azure/active-directory/develop/quickstart-register-app](https://learn.microsoft.com/en-gb/azure/active-directory/develop/quickstart-register-app) 90 | 91 | 92 | ### Repo Tree 93 | 94 | ``` 95 | 📦 96 | ├─ .eslintrc.cjs 97 | ├─ .gitignore 98 | ├─ .npmrc 99 | ├─ README.md 100 | ├─ assets 101 | │ ├─ Microsoft_logo.svg.png 102 | │ └─ css 103 | │ └─ main.css 104 | ├─ components 105 | │ ├─ LoginScreen.vue 106 | │ └─ UserProfile.vue 107 | ├─ layouts 108 | │ └─ default.vue 109 | ├─ middleware 110 | │ └─ auth.global.ts // Authentication middleware 111 | ├─ nuxt.config.ts 112 | ├─ package-lock.json 113 | ├─ package.json 114 | ├─ pages 115 | │ ├─ index.vue 116 | │ └─ login.vue 117 | ├─ plugins 118 | │ └─ msal.ts // MSAL plugin file 119 | ├─ public 120 | │ └─ favicon.ico 121 | ├─ stores 122 | │ └─ auth.global.ts 123 | ├─ tailwind.config.js 124 | └─ tsconfig.json 125 | ``` 126 | 127 | ### Follow the steps below to make your own `MSAL plugin`. Alternatively, you can use a boilerplate that covers all necessary features in the `msal.ts` file. 128 | 129 | 1. Import the `PublicClientApplication` and other necessary modules from `@azure/msal-browser`. 130 | 131 | 2. Configure the `msalConfig` object with your authentication settings. 132 | 133 | 3. Create an instance of `PublicClientApplication` using the `msalConfig`. 134 | 135 | 4. Implement the various methods and functionalities described in the code snippet. 136 | 137 | 5. Optionally, provide the `msal` object to the Nuxt.js application using the `provide` option. 138 | 139 | 6. Use the provided functionality methods for authentication and token management in your Nuxt.js application. 140 | 141 | ## License 142 | 143 | This project is licensed under the [MIT License](LICENSE). 144 | -------------------------------------------------------------------------------- /assets/Microsoft_logo.svg.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akash52/msal-with-nuxt3/c7b1c31537565996a190bab001fc101e82d3c252/assets/Microsoft_logo.svg.png -------------------------------------------------------------------------------- /assets/css/main.css: -------------------------------------------------------------------------------- 1 | @tailwind base; 2 | @tailwind components; 3 | @tailwind utilities; 4 | -------------------------------------------------------------------------------- /components/LoginScreen.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | Microsoft Authentication Library (MSAL) with Nuxt 3 6 | 7 | 8 | 9 | 11 | 13 | 14 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 30 | 31 | 32 | 33 | 36 | Login with Microsoft 365 37 | 38 | 39 | 40 | 41 | 42 | 43 | 45 | 47 | 49 | 50 | 52 | 53 | 54 | 55 | 56 | 57 | 58 | 59 | 60 | 61 | 62 | 71 | -------------------------------------------------------------------------------- /components/UserProfile.vue: -------------------------------------------------------------------------------- 1 | 2 | 4 | 5 | 6 | 7 | 8 | 9 | 11 | {{ userStore.user.name?.match(/[A-Z]/g).join("") }} 12 | 13 | 14 | {{ userStore.user.name }} 15 | 16 | Name 17 | {{ userStore.user.username }} 18 | Email 19 | 20 | 22 | 24 | 25 | 26 | 27 | 28 | 29 | 31 | 33 | 34 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 91 | -------------------------------------------------------------------------------- /composables/useAppUser.ts: -------------------------------------------------------------------------------- 1 | 2 | 3 | export const useAppUser = () => { 4 | return useState("user", () => ({ 5 | user: {} as any, 6 | userImage: null, 7 | })) 8 | } -------------------------------------------------------------------------------- /composables/useMSAuth.ts: -------------------------------------------------------------------------------- 1 | import {BrowserCacheLocation, EventType, PublicClientApplication} from "@azure/msal-browser"; 2 | 3 | 4 | let tokenExpirationTimer: any; 5 | 6 | export const useMSAuth = () => { 7 | 8 | const config = useRuntimeConfig(); 9 | 10 | const msalConfig = { 11 | auth: { 12 | clientId: config.public.clientId, 13 | authority: config.public.authority, 14 | redirectUri: config.public.redirectUri, 15 | postLogoutRedirectUri: config.public.postLogoutRedirectUri, 16 | navigateToLoginRequestUrl: true, 17 | }, 18 | cache: { 19 | cacheLocation: BrowserCacheLocation.LocalStorage, 20 | storeAuthStateInCookie: true, 21 | }, 22 | system: { 23 | tokenRenewalOffsetSeconds: 300, 24 | }, 25 | }; 26 | 27 | let msalInstance = useState('msalInstance', 28 | () => new PublicClientApplication(msalConfig) 29 | ); 30 | 31 | async function initialize() { 32 | await msalInstance.value.initialize(); 33 | 34 | // Handle redirect promise after login or redirect 35 | await msalInstance.value 36 | .handleRedirectPromise() // Handles the redirect promise and obtains the response 37 | .then(handleResponse) 38 | .catch((err) => { 39 | throw new Error(err); 40 | }); 41 | 42 | // Add event callback for login success 43 | msalInstance.value.addEventCallback((event) => { 44 | if (event.eventType === EventType.LOGIN_SUCCESS) { 45 | setupTokenExpirationTimer(); 46 | } 47 | }); 48 | 49 | } 50 | 51 | // Handle the response after login or redirect 52 | function handleResponse(resp: any) { 53 | if (resp?.account) { 54 | setupTokenExpirationTimer(); 55 | } else { 56 | console.log("LOGIN"); 57 | } 58 | } 59 | 60 | // Set up timer for refreshing access token upon expiration 61 | function setupTokenExpirationTimer() { 62 | const accounts = msalInstance.value.getAllAccounts(); 63 | if (accounts.length > 0) { 64 | const account = accounts[0]; 65 | if (account.idTokenClaims && account.idTokenClaims.exp) { 66 | const tokenExpirationTime = account.idTokenClaims.exp * 1000; 67 | const currentTime = Date.now(); 68 | const timeUntilExpiration = tokenExpirationTime - currentTime; 69 | 70 | clearTimeout(tokenExpirationTimer); 71 | 72 | tokenExpirationTimer = setTimeout(() => { 73 | refreshAccessToken(account); 74 | }, timeUntilExpiration); 75 | } 76 | } 77 | } 78 | 79 | // Refresh access token 80 | async function refreshAccessToken(account: any) { 81 | try { 82 | const response = await msalInstance.value.acquireTokenSilent({ 83 | account, 84 | scopes: ["User.Read"], 85 | }); 86 | console.log("Refreshed Access Token:", response.accessToken); 87 | setupTokenExpirationTimer(); 88 | } catch (err) { 89 | console.error("Token refresh error:", err); 90 | //signOut(account.homeAccountId); 91 | } 92 | } 93 | 94 | const loginRequest = { 95 | scopes: ["User.Read"], 96 | }; 97 | 98 | // Sign in with redirect 99 | async function signIn() { 100 | try { 101 | await msalInstance.value.loginRedirect(loginRequest); 102 | } catch (err) { 103 | console.log("Login error:", err); 104 | } 105 | } 106 | 107 | // Acquire access token silently 108 | async function acquireTokenSilent() { 109 | const accounts = msalInstance.value.getAllAccounts(); 110 | if (accounts.length > 0) { 111 | const account = accounts[0]; 112 | msalInstance.value.setActiveAccount(account); 113 | try { 114 | const response = await msalInstance.value.acquireTokenSilent({ 115 | account, 116 | scopes: ["User.Read"], 117 | }); 118 | return response.accessToken; 119 | } catch (err) { 120 | return null; 121 | } 122 | } else { 123 | console.error("No accounts found"); 124 | return null; 125 | } 126 | } 127 | 128 | // Get all MSAL accounts 129 | function getAccounts() { 130 | return msalInstance.value.getAllAccounts(); 131 | } 132 | 133 | // Check if user is authenticated 134 | function isAuthenticated() { 135 | return getAccounts().length > 0; 136 | } 137 | 138 | // Sign out user 139 | function signOut(accountId: string) { 140 | const account = accountId 141 | ? msalInstance.value.getAccountByHomeId(accountId) 142 | : null; 143 | if (account) { 144 | msalInstance.value.logoutRedirect({ 145 | account, 146 | }); 147 | localStorage.clear(); 148 | } else { 149 | console.error("Account not found"); 150 | } 151 | } 152 | 153 | 154 | return { 155 | initialize, 156 | msalInstance, 157 | signIn, 158 | getAccounts, 159 | acquireTokenSilent, 160 | isAuthenticated, 161 | signOut, 162 | } 163 | 164 | } -------------------------------------------------------------------------------- /layouts/default.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | -------------------------------------------------------------------------------- /middleware/auth.global.ts: -------------------------------------------------------------------------------- 1 | import {useMSAuth} from "~/composables/useMSAuth"; 2 | import {useAppUser} from "#imports"; 3 | 4 | export default defineNuxtRouteMiddleware(async (to, from) => { 5 | if (process.server) return; 6 | if (to.name === "/login") return; 7 | 8 | const msAuth = useMSAuth(); 9 | const accounts = msAuth.getAccounts(); 10 | const userStore = useAppUser(); 11 | const accessToken = await msAuth.acquireTokenSilent(); 12 | let isAuthenticated = msAuth.isAuthenticated() && accessToken; 13 | 14 | if (isAuthenticated) { 15 | const user = { 16 | ...accounts[0], 17 | bearerToken: accessToken, 18 | }; 19 | 20 | localStorage.setItem("user", JSON.stringify(user)); 21 | userStore.value.user = user; 22 | } 23 | if (to.name !== "login" && !isAuthenticated) { 24 | return navigateTo("/login", { replace: true }); 25 | } else if (to.name === "login" && isAuthenticated) { 26 | return navigateTo("/", { replace: true }); 27 | } else { 28 | return; 29 | } 30 | }); 31 | -------------------------------------------------------------------------------- /nuxt.config.ts: -------------------------------------------------------------------------------- 1 | export default defineNuxtConfig({ 2 | ssr: false, 3 | devtools: { enabled: true }, 4 | 5 | plugins: [{ src: "~/plugins/msal.ts", mode: "client" }], 6 | 7 | modules: ["nuxt-headlessui"], 8 | css: ["~/assets/css/main.css"], 9 | 10 | runtimeConfig: { 11 | public: { 12 | clientId: process.env.CLIENTID, 13 | authority: process.env.AUTHORITY, 14 | redirectUri: process.env.REDIRECT_URI, 15 | postLogoutRedirectUri: process.env.POSTLOGOUT_REDIRECT_URI, 16 | }, 17 | }, 18 | 19 | postcss: { 20 | plugins: { 21 | tailwindcss: {}, 22 | autoprefixer: {}, 23 | }, 24 | }, 25 | }); 26 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "nuxt-3-msal", 3 | "private": true, 4 | "scripts": { 5 | "build": "nuxt build", 6 | "dev": "nuxt dev", 7 | "generate": "nuxt generate", 8 | "preview": "nuxt preview", 9 | "postinstall": "nuxt prepare", 10 | "lint": "eslint .", 11 | "lint-fix": "eslint . --fix" 12 | }, 13 | "devDependencies": { 14 | "@nuxt/devtools": "latest", 15 | "@types/node": "^18", 16 | "autoprefixer": "^10.4.14", 17 | "nuxt": "^3.5.2", 18 | "nuxt-headlessui": "^1.1.4", 19 | "postcss": "^8.4.24", 20 | "tailwindcss": "^3.3.2" 21 | }, 22 | "dependencies": { 23 | "@azure/msal-browser": "^3.5.0", 24 | "@editorjs/editorjs": "^2.27.0", 25 | "@editorjs/header": "^2.7.0", 26 | "@heroicons/vue": "^2.0.18", 27 | "@nuxt/eslint-config": "^0.1.1", 28 | "eslint": "^8.43.0" 29 | } 30 | } 31 | -------------------------------------------------------------------------------- /pages/index.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 5 | Welcome to your 365 profile! 🌟 6 | 7 | 8 | 9 | 10 | 12 | -------------------------------------------------------------------------------- /pages/login.vue: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 6 | -------------------------------------------------------------------------------- /plugins/msal.ts: -------------------------------------------------------------------------------- 1 | import {useMSAuth} from "~/composables/useMSAuth"; 2 | 3 | export default defineNuxtPlugin(async ({ $config }) => { 4 | const msAuth = useMSAuth(); 5 | await msAuth.initialize(); 6 | 7 | return {}; 8 | }); 9 | -------------------------------------------------------------------------------- /public/favicon.ico: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/Akash52/msal-with-nuxt3/c7b1c31537565996a190bab001fc101e82d3c252/public/favicon.ico -------------------------------------------------------------------------------- /tailwind.config.js: -------------------------------------------------------------------------------- 1 | /** @type {import('tailwindcss').Config} */ 2 | module.exports = { 3 | darkMode: "class", 4 | content: [ 5 | "./components/**/*.{js,vue,ts}", 6 | "./layouts/**/*.vue", 7 | "./pages/**/*.vue", 8 | "./plugins/**/*.{js,ts}", 9 | "./nuxt.config.{js,ts}", 10 | "./app.vue", 11 | ], 12 | theme: { 13 | extend: { 14 | colors: { 15 | "cs-main": "#EF5366", 16 | "cs-second": "#f87171", 17 | }, 18 | }, 19 | }, 20 | }; 21 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | // https://nuxt.com/docs/guide/concepts/typescript 3 | "extends": "./.nuxt/tsconfig.json" 4 | } 5 | --------------------------------------------------------------------------------