How do you authenticate a VueJS app with Azure AD?
To solve this very problem, I leaned on ADAL JS. I've made a Vue + Vue-Router sample application available here - but I'll include the important pieces below.
In your package.json:
"dependencies": { "adal-angular": "^1.0.15", "vue": "^2.5.2", "vue-router": "^3.0.1"},
A basic wrapper module for the ADAL JS library:
import AuthenticationContext from 'adal-angular/lib/adal.js'const config = { tenant: 'your aad tenant', clientId: 'your aad application client id', redirectUri: 'base uri for this application', cacheLocation: 'localStorage'};export default { authenticationContext: null, /** * @return {Promise} */ initialize() { this.authenticationContext = new AuthenticationContext(config); return new Promise((resolve, reject) => { if (this.authenticationContext.isCallback(window.location.hash) || window.self !== window.top) { // redirect to the location specified in the url params. this.authenticationContext.handleWindowCallback(); } else { // try pull the user out of local storage let user = this.authenticationContext.getCachedUser(); if (user) { resolve(); } else { // no user at all - go sign in. this.signIn(); } } }); }, /** * @return {Promise.<String>} A promise that resolves to an ADAL token for resource access */ acquireToken() { return new Promise((resolve, reject) => { this.authenticationContext.acquireToken('<azure active directory resource id>', (error, token) => { if (error || !token) { return reject(error); } else { return resolve(token); } }); }); }, /** * Issue an interactive authentication request for the current user and the api resource. */ acquireTokenRedirect() { this.authenticationContext.acquireTokenRedirect('<azure active directory resource id>'); }, /** * @return {Boolean} Indicates if there is a valid, non-expired access token present in localStorage. */ isAuthenticated() { // getCachedToken will only return a valid, non-expired token. if (this.authenticationContext.getCachedToken(config.clientId)) { return true; } return false; }, /** * @return An ADAL user profile object. */ getUserProfile() { return this.authenticationContext.getCachedUser().profile; }, signIn() { this.authenticationContext.login(); }, signOut() { this.authenticationContext.logOut(); }}
In the app's entry point (main.js if you used vue-cli):
import Vue from 'vue'import App from './App'import router from './router'import authentication from './authentication'// Init adal authentication - then create Vue app.authentication.initialize().then(_ => { /* eslint-disable no-new */ new Vue({ el: '#app', router, template: '<App/>', components: { App } });});
For your Vue router configuration:
import Vue from 'vue'import Router from 'vue-router'import HelloWorld from '@/components/HelloWorld'import authentication from '../authentication'Vue.use(Router)const router = new Router({ mode: 'history', routes: [ { path: '/', name: 'HelloWorld', component: HelloWorld, meta: { requiresAuthentication: true } } ]})// Global route guardrouter.beforeEach((to, from, next) => { if (to.matched.some(record => record.meta.requiresAuthentication)) { // this route requires auth, check if logged in if (authentication.isAuthenticated()) { // only proceed if authenticated. next(); } else { authentication.signIn(); } } else { next(); }});export default router;
In your Vue components:
import authentication from './authentication'...computed: { isAuthenticated() { return authentication.isAuthenticated(); }},methods: { logOut() { authentication.signOut(); }}
Add access token to request headers
The below is an example of a vue-resource http interceptor, but any method will do.
Vue.http.interceptors.push(function (request, next) { auth.acquireToken().then(token => { // Set default request headers for every request request.headers.set('Content-Type', 'application/json'); request.headers.set('Ocp-Apim-Subscription-Key', 'api key'); request.headers.set('Authorization', 'Bearer ' + token) // continue to next interceptor next(); });});
Hopefully this saves somebody a little time :)
Disclaimer: I am the author of this plugin.
Use vue-adal via npm:
npm install vue-adal
Basic Usage
import Adal from 'vue-adal'Vue.use(Adal, {// This config gets passed along to Adal, so all settings available to adal can be used here. config: { // 'common' (multi-tenant gateway) or Azure AD Tenant ID tenant: '<guid>', // Application ID clientId: '<guid>', // Host URI redirectUri: '<host addr>', cacheLocation: 'localStorage' }, // Set this to true for authentication on startup requireAuthOnInitialize: true, // Pass a vue-router object in to add route hooks with authentication and role checking router: router})```
important: make sure to set the mode on your router to 'history' so that it doesn't use hashes! This will have implications on the serverside.
new Router({ mode: 'history', // Required for Adal library ... // Rest of router init})
There are more instructions for use on npm, and instructions + a sample on github
I'm not sure that there is a library to help with security for Vue apps. However, we can easily leverage Adal.js for authentication.
I wrote a simple demo for your reference:
Index.html:
<html><head> <script src="https://unpkg.com/vue"></script> <script src="node_modules\adal-angular\lib\adal.js"></script> <script src="config.js"></script> <script> var authContext = new AuthenticationContext(config); function login() { authContext.login(); } function init(configOptions) { if (configOptions) { // redirect and logout_redirect are set to current location by default var existingHash = window.location.hash; var pathDefault = window.location.href; if (existingHash) { pathDefault = pathDefault.replace(existingHash, ""); } configOptions.redirectUri = configOptions.redirectUri || pathDefault; configOptions.postLogoutRedirectUri = configOptions.postLogoutRedirectUri || pathDefault; // create instance with given config } else { throw new Error("You must set configOptions, when calling init"); } // loginresource is used to set authenticated status updateDataFromCache(authContext.config.loginResource); } var _oauthData = { isAuthenticated: false, userName: "", loginError: "", profile: "" }; var updateDataFromCache = function(resource) { // only cache lookup here to not interrupt with events var token = authContext.getCachedToken(resource); _oauthData.isAuthenticated = token !== null && token.length > 0; var user = authContext.getCachedUser() || { userName: "" }; _oauthData.userName = user.userName; _oauthData.profile = user.profile; _oauthData.loginError = authContext.getLoginError(); }; function saveTokenFromHash() { var hash = window.location.hash; var requestInfo = authContext.getRequestInfo(hash); if (authContext.isCallback(hash)) { // callback can come from login or iframe request var requestInfo = authContext.getRequestInfo(hash); authContext.saveTokenFromHash(requestInfo); window.location.hash = ""; if (requestInfo.requestType !== authContext.REQUEST_TYPE.LOGIN) { authContext.callback = window.parent.AuthenticationContext().callback; } } } function isAuthenticate() { return _oauthData.isAuthenticated; } saveTokenFromHash(); init(config); </script></head><body><div id="app"> <p v-if="_oauthData.isAuthenticated">Hello {{ oauthData.userName }}</p> <button onclick="login()" v-else>Login</button></div><script> var app = new Vue({ el: "#app", data: { oauthData: _oauthData } });</script></body></html>
config.js:
var config = { tenant: 'xxx.onmicrosoft.com', clientId: '', redirectUri: '', cacheLocation: 'localStorage'};