├── LICENSE ├── README.md └── vue-adal-example ├── .babelrc ├── .editorconfig ├── .eslintignore ├── .eslintrc.js ├── .gitignore ├── .postcssrc.js ├── README.md ├── build ├── build.js ├── check-versions.js ├── logo.png ├── utils.js ├── vue-loader.conf.js ├── webpack.base.conf.js ├── webpack.dev.conf.js ├── webpack.prod.conf.js └── webpack.test.conf.js ├── config ├── dev.env.js ├── index.js └── prod.env.js ├── index.html ├── package.json ├── src ├── App.vue ├── assets │ └── logo.png ├── authentication │ └── index.js ├── components │ └── HelloWorld.vue ├── main.js └── router │ └── index.js └── static └── .gitkeep /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2017 Matt Ankerson 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # vue-adal 2 | A sample Vue.js application showcasing usage of [ADAL JS](https://github.com/AzureAD/azure-activedirectory-library-for-js). 3 | 4 | The purpose of this example is to demonstrate usage of ADAL JS from the `adal-angular` module for managing authentication with Azure AD in the context of a typical Vue.js single page application. 5 | 6 | ### Dependencies 7 | ``` JavaScript 8 | "dependencies": { 9 | "adal-angular": "^1.0.15", 10 | "vue": "^2.5.2", 11 | "vue-router": "^3.0.1" 12 | }, 13 | ``` 14 | 15 | ### Build and run this sample: 16 | ``` Bash 17 | cd './vue-adal-example' 18 | 19 | # install dependencies 20 | npm install 21 | 22 | # serve with hot reload at localhost:8080 23 | npm run dev 24 | ``` 25 | 26 | ## ADAL wrapper module: 27 | `vue-adal-example/src/authentication/index.js` 28 | 29 | ADAL requires some configuration to integrate with an Azure Active Directory application: 30 | ``` JavaScript 31 | const config = { 32 | tenant: 'your aad tenant', 33 | clientId: 'your aad application client id', 34 | redirectUri: 'base uri for this application', 35 | cacheLocation: 'localStorage' 36 | }; 37 | ``` 38 | Initialise ADAL at page load - before the app is created. 39 | 40 | Usage: `initialize().then(_ => {/* Create vue app */});` 41 | ``` JavaScript 42 | initialize() { 43 | this.authenticationContext = new AuthenticationContext(config); 44 | 45 | return new Promise((resolve, reject) => { 46 | if (this.authenticationContext.isCallback(window.location.hash) || window.self !== window.top) { 47 | // redirect to the location specified in the url params. 48 | this.authenticationContext.handleWindowCallback(); 49 | } 50 | else { 51 | // try pull the user out of local storage 52 | let user = this.authenticationContext.getCachedUser(); 53 | 54 | if (user) { 55 | resolve(); 56 | } 57 | else { 58 | // no user at all - go sign in. 59 | this.signIn(); 60 | } 61 | } 62 | }); 63 | }, 64 | ``` 65 | You'll usually need to get an access token for some resource (usually an API you want your SPA to consume). The resource identifier should be associated with another Azure Active Directory application which represents the resource you're requesting: 66 | ``` JavaScript 67 | acquireToken() { 68 | return new Promise((resolve, reject) => { 69 | this.authenticationContext.acquireToken('resource id', (error, token) => { 70 | if (error || !token) { 71 | return reject(error); 72 | } else { 73 | return resolve(token); 74 | } 75 | }); 76 | }); 77 | }, 78 | ``` 79 | In the event that something goes wrong (perhaps the user has not accepted the permissions granted to access the requested resource) - an interactive authentication request can be invoked with this function: 80 | ``` JavaScript 81 | acquireTokenRedirect() { 82 | this.authenticationContext.acquireTokenRedirect('resource id'); 83 | }, 84 | ``` 85 | ADAL can be queried to determine whether the user has been properly authenticated: 86 | ``` JavaScript 87 | isAuthenticated() { 88 | // getCachedToken will only return a valid, non-expired token. 89 | if (this.authenticationContext.getCachedToken(config.clientId)) { return true; } 90 | return false; 91 | }, 92 | ``` 93 | It can be useful to get access to the current users JWT token/profile. This will contain user information, assigned groups, app roles and other handy things. 94 | 95 | ``` JavaScript 96 | getUserProfile() { 97 | return this.authenticationContext.getCachedUser().profile; 98 | }, 99 | ``` 100 | Wrapper functions to invoke log in and log out actions: 101 | ``` JavaScript 102 | signIn() { 103 | this.authenticationContext.login(); 104 | }, 105 | signOut() { 106 | this.authenticationContext.logOut(); 107 | } 108 | ``` 109 | 110 | ## In the app's entry point: 111 | `vue-adal-example/src/main.js` 112 | ``` JavaScript 113 | import Vue from 'vue' 114 | import App from './App' 115 | import router from './router' 116 | import authentication from './authentication' 117 | 118 | authentication.initialize().then(_ => { 119 | new Vue({ 120 | el: '#app', 121 | router, 122 | template: '', 123 | components: { App } 124 | }); 125 | }); 126 | 127 | ``` 128 | 129 | Optional - save the user profile in a state store (like Vuex): 130 | ``` JavaScript 131 | let profile = getUserProfile(); 132 | store.commit('SET_USER_PROFILE', { profile }); 133 | ``` 134 | 135 | ## Vue Router 136 | `vue-adal-example/src/router/index.js` 137 | 138 | * Enable history mode (disable usage of hashes (#) in the url). 139 | * Include a meta field to facilitate authentication checking. 140 | 141 | [Vue Router documentation](https://router.vuejs.org/en/) 142 | 143 | ``` JavaScript 144 | const router = new Router({ 145 | mode: 'history', // Use history mode to stop vue-router from using a hash in the url. 146 | // This is necessary to allow adal's redirect to work. 147 | routes: [ 148 | { 149 | path: '/', 150 | name: 'HelloWorld', 151 | component: HelloWorld, 152 | meta: { 153 | requiresAuthentication: true 154 | } 155 | } 156 | ] 157 | }) 158 | ``` 159 | Check for the route's meta field `requiresAuthentication`: 160 | ``` JavaScript 161 | // Global route guard 162 | router.beforeEach((to, from, next) => { 163 | if (to.matched.some(record => record.meta.requiresAuthentication)) { 164 | // this route requires auth, check if logged in 165 | if (authentication.isAuthenticated()) { 166 | // only proceed if authenticated. 167 | next(); 168 | } else { 169 | authentication.signIn(); 170 | } 171 | } else { 172 | next(); 173 | } 174 | }); 175 | ``` 176 | 177 | ## In your Vue components 178 | Use functions from ADAL in your Vue components to drive the view. i.e. - `v-if="isAuthenticated"` 179 | ``` JavaScript 180 | computed: { 181 | isAuthenticated() { 182 | return authentication.isAuthenticated(); 183 | } 184 | }, 185 | methods: { 186 | logOut() { 187 | authentication.signOut(); 188 | } 189 | } 190 | ``` 191 | 192 | ## Requesting an access token for a protected resource 193 | ADAL's `acquireToken` function can be used to get a valid token. The following example uses [Vue Resource](https://github.com/pagekit/vue-resource) to set up a http interceptor (not included in sample code). 194 | ``` JavaScript 195 | Vue.http.interceptors.push(function (request, next) { 196 | auth.acquireToken().then(token => { 197 | // Set default request headers for every request 198 | request.headers.set('Content-Type', 'application/json'); 199 | request.headers.set('Ocp-Apim-Subscription-Key', 'api key'); 200 | request.headers.set('Authorization', 'Bearer ' + token) 201 | // continue to next interceptor 202 | next(); 203 | }); 204 | }); 205 | ``` 206 | -------------------------------------------------------------------------------- /vue-adal-example/.babelrc: -------------------------------------------------------------------------------- 1 | { 2 | "presets": [ 3 | ["env", { 4 | "modules": false 5 | }], 6 | "stage-2" 7 | ], 8 | "plugins": ["transform-runtime"], 9 | "env": { 10 | "test": { 11 | "presets": ["env", "stage-2"] } 12 | } 13 | } 14 | -------------------------------------------------------------------------------- /vue-adal-example/.editorconfig: -------------------------------------------------------------------------------- 1 | root = true 2 | 3 | [*] 4 | charset = utf-8 5 | indent_style = space 6 | indent_size = 2 7 | end_of_line = lf 8 | insert_final_newline = true 9 | trim_trailing_whitespace = true 10 | -------------------------------------------------------------------------------- /vue-adal-example/.eslintignore: -------------------------------------------------------------------------------- 1 | /build/ 2 | /config/ 3 | /dist/ 4 | /*.js 5 | -------------------------------------------------------------------------------- /vue-adal-example/.eslintrc.js: -------------------------------------------------------------------------------- 1 | // https://eslint.org/docs/user-guide/configuring 2 | 3 | module.exports = { 4 | root: true, 5 | parser: 'babel-eslint', 6 | parserOptions: { 7 | sourceType: 'module' 8 | }, 9 | env: { 10 | browser: true, 11 | }, 12 | // https://github.com/standard/standard/blob/master/docs/RULES-en.md 13 | extends: 'standard', 14 | // required to lint *.vue files 15 | plugins: [ 16 | 'html' 17 | ], 18 | // add your custom rules here 19 | 'rules': { 20 | // allow paren-less arrow functions 21 | 'arrow-parens': 0, 22 | // allow async-await 23 | 'generator-star-spacing': 0, 24 | // allow debugger during development 25 | 'no-debugger': process.env.NODE_ENV === 'production' ? 2 : 0, 26 | // semi colons 27 | "semi": 0, 28 | // Function paranthesis spacing 29 | "space-before-function-paren": 0 30 | } 31 | } 32 | -------------------------------------------------------------------------------- /vue-adal-example/.gitignore: -------------------------------------------------------------------------------- 1 | .DS_Store 2 | node_modules/ 3 | /dist/ 4 | npm-debug.log* 5 | yarn-debug.log* 6 | yarn-error.log* 7 | 8 | # Editor directories and files 9 | .idea 10 | .vscode 11 | *.suo 12 | *.ntvs* 13 | *.njsproj 14 | *.sln 15 | -------------------------------------------------------------------------------- /vue-adal-example/.postcssrc.js: -------------------------------------------------------------------------------- 1 | // https://github.com/michael-ciniawsky/postcss-load-config 2 | 3 | module.exports = { 4 | "plugins": { 5 | // to edit target browsers: use "browserslist" field in package.json 6 | "autoprefixer": {} 7 | } 8 | } 9 | -------------------------------------------------------------------------------- /vue-adal-example/README.md: -------------------------------------------------------------------------------- 1 | # vue-adal-example 2 | 3 | > A standard issue Vue.js project - using ADAL js for authentication 4 | 5 | ## Build Setup 6 | 7 | ``` bash 8 | # install dependencies 9 | npm install 10 | 11 | # serve with hot reload at localhost:8080 12 | npm run dev 13 | 14 | # build for production with minification 15 | npm run build 16 | 17 | # build for production and view the bundle analyzer report 18 | npm run build --report 19 | ``` 20 | 21 | For a detailed explanation on how things work, check out the [guide](http://vuejs-templates.github.io/webpack/) and [docs for vue-loader](http://vuejs.github.io/vue-loader). 22 | -------------------------------------------------------------------------------- /vue-adal-example/build/build.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | require('./check-versions')() 3 | 4 | process.env.NODE_ENV = 'production' 5 | 6 | const ora = require('ora') 7 | const rm = require('rimraf') 8 | const path = require('path') 9 | const chalk = require('chalk') 10 | const webpack = require('webpack') 11 | const config = require('../config') 12 | const webpackConfig = require('./webpack.prod.conf') 13 | 14 | const spinner = ora('building for production...') 15 | spinner.start() 16 | 17 | rm(path.join(config.build.assetsRoot, config.build.assetsSubDirectory), err => { 18 | if (err) throw err 19 | webpack(webpackConfig, function (err, stats) { 20 | spinner.stop() 21 | if (err) throw err 22 | process.stdout.write(stats.toString({ 23 | colors: true, 24 | modules: false, 25 | children: false, 26 | chunks: false, 27 | chunkModules: false 28 | }) + '\n\n') 29 | 30 | if (stats.hasErrors()) { 31 | console.log(chalk.red(' Build failed with errors.\n')) 32 | process.exit(1) 33 | } 34 | 35 | console.log(chalk.cyan(' Build complete.\n')) 36 | console.log(chalk.yellow( 37 | ' Tip: built files are meant to be served over an HTTP server.\n' + 38 | ' Opening index.html over file:// won\'t work.\n' 39 | )) 40 | }) 41 | }) 42 | -------------------------------------------------------------------------------- /vue-adal-example/build/check-versions.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const chalk = require('chalk') 3 | const semver = require('semver') 4 | const packageConfig = require('../package.json') 5 | const shell = require('shelljs') 6 | function exec (cmd) { 7 | return require('child_process').execSync(cmd).toString().trim() 8 | } 9 | 10 | const versionRequirements = [ 11 | { 12 | name: 'node', 13 | currentVersion: semver.clean(process.version), 14 | versionRequirement: packageConfig.engines.node 15 | } 16 | ] 17 | 18 | if (shell.which('npm')) { 19 | versionRequirements.push({ 20 | name: 'npm', 21 | currentVersion: exec('npm --version'), 22 | versionRequirement: packageConfig.engines.npm 23 | }) 24 | } 25 | 26 | module.exports = function () { 27 | const warnings = [] 28 | for (let i = 0; i < versionRequirements.length; i++) { 29 | const mod = versionRequirements[i] 30 | if (!semver.satisfies(mod.currentVersion, mod.versionRequirement)) { 31 | warnings.push(mod.name + ': ' + 32 | chalk.red(mod.currentVersion) + ' should be ' + 33 | chalk.green(mod.versionRequirement) 34 | ) 35 | } 36 | } 37 | 38 | if (warnings.length) { 39 | console.log('') 40 | console.log(chalk.yellow('To use this template, you must update following to modules:')) 41 | console.log() 42 | for (let i = 0; i < warnings.length; i++) { 43 | const warning = warnings[i] 44 | console.log(' ' + warning) 45 | } 46 | console.log() 47 | process.exit(1) 48 | } 49 | } 50 | -------------------------------------------------------------------------------- /vue-adal-example/build/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matt-ankerson/vue-adal/0a883ff87a50b3a98ea6bf74ee5aaa7b90ff7a5e/vue-adal-example/build/logo.png -------------------------------------------------------------------------------- /vue-adal-example/build/utils.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const config = require('../config') 4 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 5 | const pkg = require('../package.json') 6 | 7 | exports.assetsPath = function (_path) { 8 | const assetsSubDirectory = process.env.NODE_ENV === 'production' 9 | ? config.build.assetsSubDirectory 10 | : config.dev.assetsSubDirectory 11 | return path.posix.join(assetsSubDirectory, _path) 12 | } 13 | 14 | exports.cssLoaders = function (options) { 15 | options = options || {} 16 | 17 | const cssLoader = { 18 | loader: 'css-loader', 19 | options: { 20 | minimize: process.env.NODE_ENV === 'production', 21 | sourceMap: options.sourceMap 22 | } 23 | } 24 | 25 | var postcssLoader = { 26 | loader: 'postcss-loader', 27 | options: { 28 | sourceMap: options.sourceMap 29 | } 30 | } 31 | 32 | // generate loader string to be used with extract text plugin 33 | function generateLoaders (loader, loaderOptions) { 34 | const loaders = options.usePostCSS ? [cssLoader, postcssLoader] : [cssLoader] 35 | if (loader) { 36 | loaders.push({ 37 | loader: loader + '-loader', 38 | options: Object.assign({}, loaderOptions, { 39 | sourceMap: options.sourceMap 40 | }) 41 | }) 42 | } 43 | 44 | // Extract CSS when that option is specified 45 | // (which is the case during production build) 46 | if (options.extract) { 47 | return ExtractTextPlugin.extract({ 48 | use: loaders, 49 | fallback: 'vue-style-loader' 50 | }) 51 | } else { 52 | return ['vue-style-loader'].concat(loaders) 53 | } 54 | } 55 | 56 | // https://vue-loader.vuejs.org/en/configurations/extract-css.html 57 | return { 58 | css: generateLoaders(), 59 | postcss: generateLoaders(), 60 | less: generateLoaders('less'), 61 | sass: generateLoaders('sass', { indentedSyntax: true }), 62 | scss: generateLoaders('sass'), 63 | stylus: generateLoaders('stylus'), 64 | styl: generateLoaders('stylus') 65 | } 66 | } 67 | 68 | // Generate loaders for standalone style files (outside of .vue) 69 | exports.styleLoaders = function (options) { 70 | const output = [] 71 | const loaders = exports.cssLoaders(options) 72 | for (const extension in loaders) { 73 | const loader = loaders[extension] 74 | output.push({ 75 | test: new RegExp('\\.' + extension + '$'), 76 | use: loader 77 | }) 78 | } 79 | return output 80 | } 81 | 82 | exports.createNotifierCallback = function () { 83 | const notifier = require('node-notifier') 84 | 85 | return (severity, errors) => { 86 | if (severity !== 'error') { 87 | return 88 | } 89 | const error = errors[0] 90 | 91 | const filename = error.file.split('!').pop() 92 | notifier.notify({ 93 | title: pkg.name, 94 | message: severity + ': ' + error.name, 95 | subtitle: filename || '', 96 | icon: path.join(__dirname, 'logo.png') 97 | }) 98 | } 99 | } 100 | -------------------------------------------------------------------------------- /vue-adal-example/build/vue-loader.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const config = require('../config') 4 | const isProduction = process.env.NODE_ENV === 'production' 5 | const sourceMapEnabled = isProduction 6 | ? config.build.productionSourceMap 7 | : config.dev.cssSourceMap 8 | 9 | 10 | module.exports = { 11 | loaders: utils.cssLoaders({ 12 | sourceMap: sourceMapEnabled, 13 | extract: isProduction 14 | }), 15 | cssSourceMap: sourceMapEnabled, 16 | transformToRequire: { 17 | video: 'src', 18 | source: 'src', 19 | img: 'src', 20 | image: 'xlink:href' 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /vue-adal-example/build/webpack.base.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const config = require('../config') 5 | const vueLoaderConfig = require('./vue-loader.conf') 6 | 7 | function resolve (dir) { 8 | return path.join(__dirname, '..', dir) 9 | } 10 | 11 | module.exports = { 12 | context: path.resolve(__dirname, '../'), 13 | entry: { 14 | app: './src/main.js' 15 | }, 16 | output: { 17 | path: config.build.assetsRoot, 18 | filename: '[name].js', 19 | publicPath: process.env.NODE_ENV === 'production' 20 | ? config.build.assetsPublicPath 21 | : config.dev.assetsPublicPath 22 | }, 23 | resolve: { 24 | extensions: ['.js', '.vue', '.json'], 25 | alias: { 26 | 'vue$': 'vue/dist/vue.esm.js', 27 | '@': resolve('src'), 28 | } 29 | }, 30 | module: { 31 | rules: [ 32 | ...(config.dev.useEslint? [{ 33 | test: /\.(js|vue)$/, 34 | loader: 'eslint-loader', 35 | enforce: 'pre', 36 | include: [resolve('src'), resolve('test')], 37 | options: { 38 | formatter: require('eslint-friendly-formatter'), 39 | emitWarning: !config.dev.showEslintErrorsInOverlay 40 | } 41 | }] : []), 42 | { 43 | test: /\.vue$/, 44 | loader: 'vue-loader', 45 | options: vueLoaderConfig 46 | }, 47 | { 48 | test: /\.js$/, 49 | loader: 'babel-loader', 50 | include: [resolve('src'), resolve('test')] 51 | }, 52 | { 53 | test: /\.(png|jpe?g|gif|svg)(\?.*)?$/, 54 | loader: 'url-loader', 55 | options: { 56 | limit: 10000, 57 | name: utils.assetsPath('img/[name].[hash:7].[ext]') 58 | } 59 | }, 60 | { 61 | test: /\.(mp4|webm|ogg|mp3|wav|flac|aac)(\?.*)?$/, 62 | loader: 'url-loader', 63 | options: { 64 | limit: 10000, 65 | name: utils.assetsPath('media/[name].[hash:7].[ext]') 66 | } 67 | }, 68 | { 69 | test: /\.(woff2?|eot|ttf|otf)(\?.*)?$/, 70 | loader: 'url-loader', 71 | options: { 72 | limit: 10000, 73 | name: utils.assetsPath('fonts/[name].[hash:7].[ext]') 74 | } 75 | } 76 | ] 77 | } 78 | } 79 | -------------------------------------------------------------------------------- /vue-adal-example/build/webpack.dev.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const utils = require('./utils') 3 | const webpack = require('webpack') 4 | const config = require('../config') 5 | const merge = require('webpack-merge') 6 | const baseWebpackConfig = require('./webpack.base.conf') 7 | const HtmlWebpackPlugin = require('html-webpack-plugin') 8 | const FriendlyErrorsPlugin = require('friendly-errors-webpack-plugin') 9 | const portfinder = require('portfinder') 10 | 11 | const devWebpackConfig = merge(baseWebpackConfig, { 12 | module: { 13 | rules: utils.styleLoaders({ sourceMap: config.dev.cssSourceMap, usePostCSS: true }) 14 | }, 15 | // cheap-module-eval-source-map is faster for development 16 | devtool: config.dev.devtool, 17 | 18 | // these devServer options should be customized in /config/index.js 19 | devServer: { 20 | historyApiFallback: true, 21 | hot: true, 22 | host: process.env.HOST || config.dev.host, 23 | port: process.env.PORT || config.dev.port, 24 | open: config.dev.autoOpenBrowser, 25 | overlay: config.dev.errorOverlay ? { 26 | warnings: false, 27 | errors: true, 28 | } : false, 29 | publicPath: config.dev.assetsPublicPath, 30 | proxy: config.dev.proxyTable, 31 | quiet: true, // necessary for FriendlyErrorsPlugin 32 | watchOptions: { 33 | poll: config.dev.poll, 34 | } 35 | }, 36 | plugins: [ 37 | new webpack.DefinePlugin({ 38 | 'process.env': require('../config/dev.env') 39 | }), 40 | new webpack.HotModuleReplacementPlugin(), 41 | new webpack.NamedModulesPlugin(), // HMR shows correct file names in console on update. 42 | new webpack.NoEmitOnErrorsPlugin(), 43 | // https://github.com/ampedandwired/html-webpack-plugin 44 | new HtmlWebpackPlugin({ 45 | filename: 'index.html', 46 | template: 'index.html', 47 | inject: true 48 | }), 49 | new FriendlyErrorsPlugin() 50 | ] 51 | }) 52 | 53 | module.exports = new Promise((resolve, reject) => { 54 | portfinder.basePort = process.env.PORT || config.dev.port 55 | portfinder.getPort((err, port) => { 56 | if (err) { 57 | reject(err) 58 | } else { 59 | // publish the new Port, necessary for e2e tests 60 | process.env.PORT = port 61 | // add port to devServer config 62 | devWebpackConfig.devServer.port = port 63 | 64 | // Add FriendlyErrorsPlugin 65 | devWebpackConfig.plugins.push(new FriendlyErrorsPlugin({ 66 | compilationSuccessInfo: { 67 | messages: [`Your application is running here: http://${config.dev.host}:${port}`], 68 | }, 69 | onErrors: config.dev.notifyOnErrors 70 | ? utils.createNotifierCallback() 71 | : undefined 72 | })) 73 | 74 | resolve(devWebpackConfig) 75 | } 76 | }) 77 | }) 78 | -------------------------------------------------------------------------------- /vue-adal-example/build/webpack.prod.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const path = require('path') 3 | const utils = require('./utils') 4 | const webpack = require('webpack') 5 | const config = require('../config') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | const CopyWebpackPlugin = require('copy-webpack-plugin') 9 | const HtmlWebpackPlugin = require('html-webpack-plugin') 10 | const ExtractTextPlugin = require('extract-text-webpack-plugin') 11 | const OptimizeCSSPlugin = require('optimize-css-assets-webpack-plugin') 12 | 13 | const env = require('../config/prod.env') 14 | 15 | const webpackConfig = merge(baseWebpackConfig, { 16 | module: { 17 | rules: utils.styleLoaders({ 18 | sourceMap: config.build.productionSourceMap, 19 | extract: true, 20 | usePostCSS: true 21 | }) 22 | }, 23 | devtool: config.build.productionSourceMap ? config.build.devtool : false, 24 | output: { 25 | path: config.build.assetsRoot, 26 | filename: utils.assetsPath('js/[name].[chunkhash].js'), 27 | chunkFilename: utils.assetsPath('js/[name].[chunkhash].js') 28 | }, 29 | plugins: [ 30 | // http://vuejs.github.io/vue-loader/en/workflow/production.html 31 | new webpack.DefinePlugin({ 32 | 'process.env': env 33 | }), 34 | // UglifyJs do not support ES6+, you can also use babel-minify for better treeshaking: https://github.com/babel/minify 35 | new webpack.optimize.UglifyJsPlugin({ 36 | compress: { 37 | warnings: false 38 | }, 39 | sourceMap: config.build.productionSourceMap, 40 | parallel: true 41 | }), 42 | // extract css into its own file 43 | new ExtractTextPlugin({ 44 | filename: utils.assetsPath('css/[name].[contenthash].css'), 45 | // set the following option to `true` if you want to extract CSS from 46 | // codesplit chunks into this main css file as well. 47 | // This will result in *all* of your app's CSS being loaded upfront. 48 | allChunks: false, 49 | }), 50 | // Compress extracted CSS. We are using this plugin so that possible 51 | // duplicated CSS from different components can be deduped. 52 | new OptimizeCSSPlugin({ 53 | cssProcessorOptions: config.build.productionSourceMap 54 | ? { safe: true, map: { inline: false } } 55 | : { safe: true } 56 | }), 57 | // generate dist index.html with correct asset hash for caching. 58 | // you can customize output by editing /index.html 59 | // see https://github.com/ampedandwired/html-webpack-plugin 60 | new HtmlWebpackPlugin({ 61 | filename: config.build.index, 62 | template: 'index.html', 63 | inject: true, 64 | minify: { 65 | removeComments: true, 66 | collapseWhitespace: true, 67 | removeAttributeQuotes: true 68 | // more options: 69 | // https://github.com/kangax/html-minifier#options-quick-reference 70 | }, 71 | // necessary to consistently work with multiple chunks via CommonsChunkPlugin 72 | chunksSortMode: 'dependency' 73 | }), 74 | // keep module.id stable when vender modules does not change 75 | new webpack.HashedModuleIdsPlugin(), 76 | // enable scope hoisting 77 | new webpack.optimize.ModuleConcatenationPlugin(), 78 | // split vendor js into its own file 79 | new webpack.optimize.CommonsChunkPlugin({ 80 | name: 'vendor', 81 | minChunks: function (module) { 82 | // any required modules inside node_modules are extracted to vendor 83 | return ( 84 | module.resource && 85 | /\.js$/.test(module.resource) && 86 | module.resource.indexOf( 87 | path.join(__dirname, '../node_modules') 88 | ) === 0 89 | ) 90 | } 91 | }), 92 | // extract webpack runtime and module manifest to its own file in order to 93 | // prevent vendor hash from being updated whenever app bundle is updated 94 | new webpack.optimize.CommonsChunkPlugin({ 95 | name: 'manifest', 96 | minChunks: Infinity, 97 | }), 98 | // This instance extracts shared chunks from code splitted chunks and bundles them 99 | // in a separate chunk, similar to the vendor chunk 100 | // see: https://webpack.js.org/plugins/commons-chunk-plugin/#extra-async-commons-chunk 101 | new webpack.optimize.CommonsChunkPlugin({ 102 | name: 'app', 103 | async: 'vendor-async', 104 | children: true, 105 | minChunks: 3, 106 | }), 107 | 108 | // copy custom static assets 109 | new CopyWebpackPlugin([ 110 | { 111 | from: path.resolve(__dirname, '../static'), 112 | to: config.build.assetsSubDirectory, 113 | ignore: ['.*'] 114 | } 115 | ]) 116 | ] 117 | }) 118 | 119 | if (config.build.productionGzip) { 120 | const CompressionWebpackPlugin = require('compression-webpack-plugin') 121 | 122 | webpackConfig.plugins.push( 123 | new CompressionWebpackPlugin({ 124 | asset: '[path].gz[query]', 125 | algorithm: 'gzip', 126 | test: new RegExp( 127 | '\\.(' + 128 | config.build.productionGzipExtensions.join('|') + 129 | ')$' 130 | ), 131 | threshold: 10240, 132 | minRatio: 0.8 133 | }) 134 | ) 135 | } 136 | 137 | if (config.build.bundleAnalyzerReport) { 138 | const BundleAnalyzerPlugin = require('webpack-bundle-analyzer').BundleAnalyzerPlugin 139 | webpackConfig.plugins.push(new BundleAnalyzerPlugin()) 140 | } 141 | 142 | module.exports = webpackConfig 143 | -------------------------------------------------------------------------------- /vue-adal-example/build/webpack.test.conf.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | // This is the webpack config used for unit tests. 3 | 4 | const utils = require('./utils') 5 | const webpack = require('webpack') 6 | const merge = require('webpack-merge') 7 | const baseWebpackConfig = require('./webpack.base.conf') 8 | 9 | const webpackConfig = merge(baseWebpackConfig, { 10 | // use inline sourcemap for karma-sourcemap-loader 11 | module: { 12 | rules: utils.styleLoaders() 13 | }, 14 | devtool: '#inline-source-map', 15 | resolveLoader: { 16 | alias: { 17 | // necessary to to make lang="scss" work in test when using vue-loader's ?inject option 18 | // see discussion at https://github.com/vuejs/vue-loader/issues/724 19 | 'scss-loader': 'sass-loader' 20 | } 21 | }, 22 | plugins: [ 23 | new webpack.DefinePlugin({ 24 | 'process.env': require('../config/test.env') 25 | }) 26 | ] 27 | }) 28 | 29 | // no need for app entry during tests 30 | delete webpackConfig.entry 31 | 32 | module.exports = webpackConfig 33 | -------------------------------------------------------------------------------- /vue-adal-example/config/dev.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | const merge = require('webpack-merge') 3 | const prodEnv = require('./prod.env') 4 | 5 | module.exports = merge(prodEnv, { 6 | NODE_ENV: '"development"' 7 | }) 8 | -------------------------------------------------------------------------------- /vue-adal-example/config/index.js: -------------------------------------------------------------------------------- 1 | 2 | 'use strict' 3 | // Template version: 1.2.0 4 | // see http://vuejs-templates.github.io/webpack for documentation. 5 | 6 | const path = require('path') 7 | 8 | module.exports = { 9 | dev: { 10 | 11 | // Paths 12 | assetsSubDirectory: 'static', 13 | assetsPublicPath: '/', 14 | proxyTable: {}, 15 | 16 | // Various Dev Server settings 17 | host: 'localhost', // can be overwritten by process.env.HOST 18 | port: 8080, // can be overwritten by process.env.HOST, if port is in use, a free one will be determined 19 | autoOpenBrowser: false, 20 | errorOverlay: true, 21 | notifyOnErrors: true, 22 | poll: false, // https://webpack.js.org/configuration/dev-server/#devserver-watchoptions- 23 | 24 | // Use Eslint Loader? 25 | // If true, your code will be linted during bundling and 26 | // linting errors and warings will be shown in the console. 27 | useEslint: true, 28 | // If true, eslint errors and warings will also be shown in the error overlay 29 | // in the browser. 30 | showEslintErrorsInOverlay: false, 31 | 32 | /** 33 | * Source Maps 34 | */ 35 | 36 | // https://webpack.js.org/configuration/devtool/#development 37 | devtool: 'eval-source-map', 38 | 39 | // If you have problems debugging vue-files in devtools, 40 | // set this to false - it *may* help 41 | // https://vue-loader.vuejs.org/en/options.html#cachebusting 42 | cacheBusting: true, 43 | 44 | // CSS Sourcemaps off by default because relative paths are "buggy" 45 | // with this option, according to the CSS-Loader README 46 | // (https://github.com/webpack/css-loader#sourcemaps) 47 | // In our experience, they generally work as expected, 48 | // just be aware of this issue when enabling this option. 49 | cssSourceMap: false, 50 | }, 51 | 52 | build: { 53 | // Template for index.html 54 | index: path.resolve(__dirname, '../dist/index.html'), 55 | 56 | // Paths 57 | assetsRoot: path.resolve(__dirname, '../dist'), 58 | assetsSubDirectory: 'static', 59 | assetsPublicPath: '/', 60 | 61 | /** 62 | * SourceMap 63 | */ 64 | productionSourceMap: true, 65 | // https://webpack.js.org/configuration/devtool/#production 66 | devtool: '#source-map', 67 | 68 | // Gzip off by default as many popular static hosts such as 69 | // Surge or Netlify already gzip all static assets for you. 70 | // Before setting to `true`, make sure to: 71 | // npm install --save-dev compression-webpack-plugin 72 | productionGzip: false, 73 | productionGzipExtensions: ['js', 'css'], 74 | 75 | // Run the build command with an extra argument to 76 | // View the bundle analyzer report after build finishes: 77 | // `npm run build --report` 78 | // Set to `true` or `false` to always turn it on or off 79 | bundleAnalyzerReport: process.env.npm_config_report 80 | } 81 | } 82 | -------------------------------------------------------------------------------- /vue-adal-example/config/prod.env.js: -------------------------------------------------------------------------------- 1 | 'use strict' 2 | module.exports = { 3 | NODE_ENV: '"production"' 4 | } 5 | -------------------------------------------------------------------------------- /vue-adal-example/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | vue-adal-example 7 | 8 | 9 |
10 | 11 | 12 | 13 | -------------------------------------------------------------------------------- /vue-adal-example/package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-adal-example", 3 | "version": "1.0.0", 4 | "description": "A standard issue Vue.js project - using ADAL js for authentication", 5 | "author": "Matt Ankerson", 6 | "private": true, 7 | "scripts": { 8 | "dev": "webpack-dev-server --inline --progress --config build/webpack.dev.conf.js", 9 | "start": "npm run dev", 10 | "lint": "eslint --ext .js,.vue src", 11 | "build": "node build/build.js" 12 | }, 13 | "dependencies": { 14 | "adal-angular": "^1.0.15", 15 | "vue": "^2.5.2", 16 | "vue-router": "^3.0.1" 17 | }, 18 | "devDependencies": { 19 | "autoprefixer": "^7.1.2", 20 | "babel-core": "^6.22.1", 21 | "babel-eslint": "^7.1.1", 22 | "babel-loader": "^7.1.1", 23 | "babel-plugin-transform-runtime": "^6.22.0", 24 | "babel-preset-env": "^1.3.2", 25 | "babel-preset-stage-2": "^6.22.0", 26 | "babel-register": "^6.22.0", 27 | "chalk": "^2.0.1", 28 | "copy-webpack-plugin": "^4.0.1", 29 | "css-loader": "^0.28.0", 30 | "eslint": "^3.19.0", 31 | "eslint-friendly-formatter": "^3.0.0", 32 | "eslint-loader": "^1.7.1", 33 | "eslint-plugin-html": "^3.0.0", 34 | "eslint-config-standard": "^10.2.1", 35 | "eslint-plugin-promise": "^3.4.0", 36 | "eslint-plugin-standard": "^3.0.1", 37 | "eslint-plugin-import": "^2.7.0", 38 | "eslint-plugin-node": "^5.2.0", 39 | "eventsource-polyfill": "^0.9.6", 40 | "express": "^4.14.1", 41 | "extract-text-webpack-plugin": "^3.0.0", 42 | "file-loader": "^1.1.4", 43 | "friendly-errors-webpack-plugin": "^1.6.1", 44 | "html-webpack-plugin": "^2.30.1", 45 | "webpack-bundle-analyzer": "^2.9.0", 46 | "node-notifier": "^5.1.2", 47 | "semver": "^5.3.0", 48 | "shelljs": "^0.7.6", 49 | "optimize-css-assets-webpack-plugin": "^3.2.0", 50 | "ora": "^1.2.0", 51 | "rimraf": "^2.6.0", 52 | "url-loader": "^0.5.8", 53 | "vue-loader": "^13.3.0", 54 | "vue-style-loader": "^3.0.1", 55 | "vue-template-compiler": "^2.5.2", 56 | "portfinder": "^1.0.13", 57 | "webpack": "^3.6.0", 58 | "webpack-dev-server": "^2.9.1", 59 | "webpack-merge": "^4.1.0" 60 | }, 61 | "engines": { 62 | "node": ">= 4.0.0", 63 | "npm": ">= 3.0.0" 64 | }, 65 | "browserslist": [ 66 | "> 1%", 67 | "last 2 versions", 68 | "not ie <= 8" 69 | ] 70 | } 71 | -------------------------------------------------------------------------------- /vue-adal-example/src/App.vue: -------------------------------------------------------------------------------- 1 | 8 | 9 | 25 | 26 | 36 | -------------------------------------------------------------------------------- /vue-adal-example/src/assets/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matt-ankerson/vue-adal/0a883ff87a50b3a98ea6bf74ee5aaa7b90ff7a5e/vue-adal-example/src/assets/logo.png -------------------------------------------------------------------------------- /vue-adal-example/src/authentication/index.js: -------------------------------------------------------------------------------- 1 | import AuthenticationContext from 'adal-angular/lib/adal.js' 2 | 3 | const config = { 4 | tenant: 'your aad tenant', 5 | clientId: 'your aad application client id', 6 | redirectUri: 'base uri for this application', 7 | cacheLocation: 'localStorage' 8 | }; 9 | 10 | export default { 11 | authenticationContext: null, 12 | /** 13 | * @return {Promise} 14 | */ 15 | initialize() { 16 | this.authenticationContext = new AuthenticationContext(config); 17 | 18 | return new Promise((resolve, reject) => { 19 | if (this.authenticationContext.isCallback(window.location.hash) || window.self !== window.top) { 20 | // redirect to the location specified in the url params. 21 | this.authenticationContext.handleWindowCallback(); 22 | } 23 | else { 24 | // try pull the user out of local storage 25 | let user = this.authenticationContext.getCachedUser(); 26 | 27 | if (user) { 28 | resolve(); 29 | } 30 | else { 31 | // no user at all - go sign in. 32 | this.signIn(); 33 | } 34 | } 35 | }); 36 | }, 37 | /** 38 | * @return {Promise.} A promise that resolves to an ADAL token for resource access 39 | */ 40 | acquireToken() { 41 | return new Promise((resolve, reject) => { 42 | this.authenticationContext.acquireToken('', (error, token) => { 43 | if (error || !token) { 44 | return reject(error); 45 | } else { 46 | return resolve(token); 47 | } 48 | }); 49 | }); 50 | }, 51 | /** 52 | * Issue an interactive authentication request for the current user and the api resource. 53 | */ 54 | acquireTokenRedirect() { 55 | this.authenticationContext.acquireTokenRedirect(''); 56 | }, 57 | /** 58 | * @return {Boolean} Indicates if there is a valid, non-expired access token present in localStorage. 59 | */ 60 | isAuthenticated() { 61 | // getCachedToken will only return a valid, non-expired token. 62 | if (this.authenticationContext.getCachedToken(config.clientId)) { return true; } 63 | return false; 64 | }, 65 | /** 66 | * @return An ADAL user profile object. 67 | */ 68 | getUserProfile() { 69 | return this.authenticationContext.getCachedUser().profile; 70 | }, 71 | signIn() { 72 | this.authenticationContext.login(); 73 | }, 74 | signOut() { 75 | this.authenticationContext.logOut(); 76 | } 77 | } 78 | -------------------------------------------------------------------------------- /vue-adal-example/src/components/HelloWorld.vue: -------------------------------------------------------------------------------- 1 | 22 | 23 | 33 | 34 | 35 | 51 | -------------------------------------------------------------------------------- /vue-adal-example/src/main.js: -------------------------------------------------------------------------------- 1 | // The Vue build version to load with the `import` command 2 | // (runtime-only or standalone) has been set in webpack.base.conf with an alias. 3 | import Vue from 'vue' 4 | import App from './App' 5 | import router from './router' 6 | import authentication from './authentication' 7 | 8 | Vue.config.productionTip = false 9 | 10 | // Init adal authentication - then create Vue app. 11 | authentication.initialize().then(_ => { 12 | /* eslint-disable no-new */ 13 | new Vue({ 14 | el: '#app', 15 | router, 16 | template: '', 17 | components: { App } 18 | }); 19 | }); 20 | -------------------------------------------------------------------------------- /vue-adal-example/src/router/index.js: -------------------------------------------------------------------------------- 1 | import Vue from 'vue' 2 | import Router from 'vue-router' 3 | import HelloWorld from '@/components/HelloWorld' 4 | import authentication from '../authentication' 5 | 6 | Vue.use(Router) 7 | 8 | const router = new Router({ 9 | mode: 'history', 10 | routes: [ 11 | { 12 | path: '/', 13 | name: 'HelloWorld', 14 | component: HelloWorld, 15 | meta: { 16 | requiresAuthentication: true 17 | } 18 | } 19 | ] 20 | }) 21 | 22 | // Global route guard 23 | router.beforeEach((to, from, next) => { 24 | if (to.matched.some(record => record.meta.requiresAuthentication)) { 25 | // this route requires auth, check if logged in 26 | if (authentication.isAuthenticated()) { 27 | // only proceed if authenticated. 28 | next(); 29 | } else { 30 | authentication.signIn(); 31 | } 32 | } else { 33 | next(); 34 | } 35 | }); 36 | 37 | export default router; 38 | -------------------------------------------------------------------------------- /vue-adal-example/static/.gitkeep: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/matt-ankerson/vue-adal/0a883ff87a50b3a98ea6bf74ee5aaa7b90ff7a5e/vue-adal-example/static/.gitkeep --------------------------------------------------------------------------------