├── .gitignore ├── LICENSE ├── README.md ├── lib ├── mixin.js ├── plugin.ts └── src │ ├── UserAgentApplicationExtended.ts │ ├── main.ts │ └── types.ts ├── package-lock.json ├── package.json └── tsconfig.json /.gitignore: -------------------------------------------------------------------------------- 1 | /dist/ 2 | /node_modules/ 3 | /.idea 4 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) Marios Vertopoulos 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-msal 2 | 3 | #### Wrapper of [MSAL.js](https://github.com/AzureAD/microsoft-authentication-library-for-js#readme) (*Microsoft Authentication Library*) for usage in Vue. 4 | 5 | The vue-msal library enables client-side [vue](https://vuejs.org/) applications, running in a web browser, to authenticate users using [Azure AD](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-overview) work and school accounts (AAD), Microsoft personal accounts (MSA) and social identity providers like Facebook, Google, LinkedIn, Microsoft accounts, etc. through [Azure AD B2C](https://docs.microsoft.com/en-us/azure/active-directory-b2c/active-directory-b2c-overview#identity-providers) service. It also enables your app to access [Microsoft Cloud](https://www.microsoft.com/enterprise) services such as [Microsoft Graph](https://graph.microsoft.io/). 6 | 7 | ## Installation 8 | Add the `vue-msal` dependency to your project using yarn or npm. 9 | ```shell script 10 | npm install vue-msal 11 | or 12 | yarn add vue-msal 13 | ``` 14 | 15 | #### Vue Usage 16 | Use the plugin in your vue instance like this 17 | ```js 18 | import msal from 'vue-msal' 19 | 20 | Vue.use(msal, { 21 | auth: { 22 | clientId: '' 23 | } 24 | }); 25 | 26 | new Vue({ 27 | //... vue options 28 | }) 29 | ``` 30 | 31 | #### Nuxt Usage 32 | Add a new javascript file like `msal.js` under `/plugins/` directory with the following content 33 | > :grey_exclamation: *Note: you should add Vue as a second argument to the constructor if you want to add the global mixin automatically with the `framework.globalMixin` option. Check the [mixin](#mixin) section below for more information* 34 | ```js 35 | import Vue from 'vue' //import Vue if you want to use the framework.globalMixin option 36 | import MSAL from 'vue-msal' 37 | 38 | export default ({ app, error, $axios }, inject) => { 39 | inject('msal', new MSAL( 40 | { 41 | auth: { 42 | clientId: '' 43 | } 44 | }, Vue /* [optional] should be passed as an argument if you want to the framework.globalMixin option*/ 45 | )) 46 | } 47 | 48 | ``` 49 | Then include it to the plugins array in `nuxt.config.js` like this 50 | ```js 51 | export default { 52 | plugins: [ 53 | //... 54 | '@/plugins/msal' 55 | //... 56 | ] 57 | } 58 | ``` 59 | This will make the `$msal` object available in both the vue instances and the [context](https://nuxtjs.org/api/context/). For example you can access it in the context of a [middleware](https://nuxtjs.org/api/pages-middleware/) via the app object like this: 60 | ```js 61 | export default function ({ app, route, error }) { 62 | // If the user is not authenticated and he's not in the /login page throw Error 63 | if (!app.$msal.isAuthenticated() && route.name !== 'login') { 64 | error({ statusCode: 401, message: 'Unauthorized' }); 65 | } 66 | } 67 | ``` 68 | 69 | ## Plugin usage 70 | When the plugin is initialized it exposes its context to `vm.$msal` (where `vm` refers to the Vue's scope) so you can, for example, call the signIn method like this: 71 | ```js 72 | new Vue({ 73 | //... 74 | created() { 75 | if (!this.$msal.isAuthenticated()) { 76 | this.$msal.signIn(); 77 | } 78 | } 79 | }); 80 | ``` 81 | #### List of functions 82 | * `signIn()`: Start the sign-in process **manually** 83 | > :grey_exclamation: *Note: you can also start the process automatically **in case the user needs to be authorized in all pages** by setting the option `auth.requireAuthOnInitialize` to `true`. Check the [Auth Configuration Options](#auth-options--required-) below for more details* 84 | * `signOut()`: Sign out an authorized user 85 | * `isAuthenticated()`: Returns `true` if the user has been authenticated and `false` otherwise. 86 | > :grey_exclamation: *Note: This function should not be used for reactivity. In order to **watch** whether the user is authenticated or not you should use the [mixin](#mixin) data properties below.* 87 | * `acquireToken([request[,retries]])`: Acquire an access token manually. 88 | > :grey_exclamation: *Note: This will also run automatically after the user's successful authentication using the default permissions defined in the `auth.scopes` property of the configuration options. You should however run this manually in case you want to get an access token with more permissions than the default, by adding the new request options as an argument, like this
89 | >`acquireToken({scopes: ["user.read", "another.permission"]})`
90 | >Check the [Request Configuration Options](#request-options) below for more details*.
91 | >You can also pass in a second parameter, with a number of retries in case of an **unexpected** failure (i.e. network errors). 92 | * `msGraph(endpoints[,batchUrl])`: Manually call the MS Graph API using the acquired access token. 93 | > :grey_exclamation: *Note: Read the [Calling MS Graph](#calling-ms-graph) section for more details* 94 | * `saveCustomData(key, data)`: You can use this function to add custom data to the selected cache location (set with `cache.cacheLocation` in the [configuration options](#cache-options)), that will be automatically deleted when the user signs out or his access token expires. This should be used, for example, to store any user related data fetched from another API. 95 | > :grey_exclamation: *Note: You can read this data **without reactivity** from the [data object](#the-data-object) or **with reactivity** by watching the `msal.custom` property of the [mixin](#mixin)'s data object* 96 | 97 | ### The data object 98 | You can access the data object that contains all of the user related data using the `$msal.data` object which is available in [nuxt's context](https://nuxtjs.org/api/context/). However in case you want reactivity for this data, it is recomended that you use the [mixin](#mixin) method below. 99 | 100 | The properties provided in the data object are the following: 101 | * `isAuthenticated`: Is `true` if the user has been successfully authenticated and `false` otherwise. 102 | * `accessToken`: The authenticated user's **access** token. Read [below](#using-accesstoken-vs-idtoken) for information on usage. 103 | * `idToken`: The authenticated user's **id** token. Read [below](#using-accesstoken-vs-idtoken) for information on usage. 104 | * `user`: The user's data provided as a response by the **authentication's** API call 105 | * `graph`: The data provided as a response by the **MS Graph API** call that runs on initialization when the `graph.callAfterInit` option is set to true. Check the [Calling MS Graph](#calling-ms-graph) section for more details 106 | * `custom`: Whatever data you have saved using the `saveCustomData(key, data)` function call. (Check the relevant section in the plugin's [function list](#list-of-functions) above for more details) 107 | 108 | #### Using `accessToken` vs `idToken` 109 | 110 | * `accessToken`: This token is not validatable outside of *MS Graph API* and therefore can only be used for MS Graph calls. 111 | * `idToken`: This token is validatable and can be used for authentication / authorization with exteral APIs. 112 | 113 | #### Mixin 114 | All user related data can be exposed via a mixin in the `msal` data property so that you can have access to it like you would normally access any of the component's data properties **with reactivity**. 115 | > :exclamation: Notice that the dollar sign ($) is missing here. While `this.$msal` refers to the plugin's exposed object, `this.msal` refers to the mixin's data object. Be careful not to confuse these two. 116 | 117 | So for example you can do this: 118 | 119 | ```html 120 |
121 |
122 |
Welcome {{user.name}}
123 |
Your job title is {{user.profile.jobTitle}}
124 |
125 |
126 |
127 | Please sign-in 128 | 129 |
130 |
131 | 132 | 157 | ``` 158 | > :exclamation: *Note: In case you want to import the mixin **globally** instead of importing it to specific vue instances you can do so by simply setting the `framework.globalMixin` to `true` in the [Framework Configuration Options](#framework-options). This will automatically add the mixin to all vue instances so that you have out-of-the-box access to the msal object. In nuxt you must also add the Vue object as an argument to the plugin's initialization for this to work. Check the [nuxt usage](#nuxt-usage) section for details.* 159 | 160 | ## Calling MS Graph 161 | You can directly call the [MS Graph API](https://docs.microsoft.com/en-us/graph/overview) for a logged-in user, with the following methods. 162 | 163 | #### Manually calling the MS Graph 164 | In order to manually call the MS Graph API you can use the `$msal.msGraph(endpoints[,batchUrl])` function that will automatically use the access token set for the logged in user. 165 | 166 | This function receives the following arguments: 167 | * `endpoints`: **[required]** This can be either a **single value for a single request to the API**, or an **array of values for a [batch request](https://docs.microsoft.com/en-us/graph/json-batching) to the API**. Each value can be either: 168 | * An `object` containing the following properties: 169 | * `url`: **[required]** This can either be: 170 | * **A Full URL** (starting with *'http...'*) in case of a **single** request (this is invalid for batch requests) 171 | * **The URI part** (i.e. */me*), which **must** be used for **batch** requests but can also be used for single requests (in which case the full URL will be composed using the value of `graph.baseUrl` option from the [Graph Configuration Options](#graph-options) as the **Base URL**). 172 | * `id`: [optional] setting this to a string will result to returning a keyed object instead of an array containing the responses of a **batch** request. *This property is ignored for **single** requests.* 173 | * Any other optional property from the [Axios Request Configuration](https://github.com/axios/axios#request-config) 174 | * A `string` containing only the url (following the same rules as the `url` property of the object type argument) 175 | * `batchUrl`: [optional] using this argument you can set a custom URL for this batch call. If this is not set the `graph.baseUrl` option from the [Graph Configuration Options](#graph-options) will be used as the **Batch URL**. *This argument is ignored for **single** requests.* 176 | 177 | The response of this call depends on the arguments passed to it. 178 | * For a single request, it returns the response object (with properties: status, headers, body) 179 | * For a batch request: 180 | * with an array of URIs passed as strings in the endpoints argument, it will return an array of response objects that match the URI's index. 181 | * with an array of objects containing an id, it will return an object keyed with those ids containing the response object. 182 | 183 | Example usage: 184 | ```js 185 | new Vue({ 186 | //... 187 | async mounted() { 188 | let result; 189 | result = await app.$msal.msGraph('https://www.example.com/1.0/me'); 190 | // Single request at: https://www.example.com/1.0/me 191 | // Returns: { status: , headers: , body: } 192 | result = await app.$msal.msGraph('/me'); 193 | // Single request at: graph.baseUrl + '/me' 194 | // Returns: { status: , headers: , body: } 195 | await app.$msal.msGraph(['/me', '/me/messages']); 196 | // Batch request at: graph.baseUrl for endpoints '/me' & '/me/messages' 197 | // Returns: [ 198 | // { status: , headers: , body: }, 199 | // { status: , headers: , body: } 200 | // ] 201 | await app.$msal.msGraph(['/me', '/me/messages'], 'https://www.custom-msgraph-url.com'); 202 | // Batch request at: 'https://www.custom-msgraph-url.com' for endpoints '/me' & '/me/messages' 203 | // Returns: [ 204 | // { status: , headers: , body: }, 205 | // { status: , headers: , body: } 206 | // ] 207 | await app.$msal.msGraph([{ url: '/me'}, { url: '/me/photo/$value', responseType: 'blob' }]); 208 | // Batch request at: graph.baseUrl for endpoints '/me' & '/me/photo/$value' 209 | // Returns: [ 210 | // { status: , headers: , body: }, 211 | // { status: , headers: , body: } 212 | // ] 213 | await app.$msal.msGraph([{ url: '/me', id: 'profile'}, { url: '/me/photo/$value', id: 'photo', responseType: 'blob' }]); 214 | // Batch request at: graph.baseUrl for endpoints '/me' & '/me/photo/$value' 215 | // Returns: { 216 | // profile: { status: , headers: , body: }, 217 | // photo: { status: , headers: , body: } 218 | // } 219 | await app.$msal.msGraph(['/me', { url: '/me/photo/$value', id: 'photo', responseType: 'blob' }]); 220 | // Batch request at: graph.baseUrl in endpoints '/me' & '/me/photo/$value' 221 | // Returns: { 222 | // 0: { status: , headers: , body: }, 223 | // photo: { status: , headers: , body: } 224 | // } 225 | } 226 | }); 227 | ``` 228 | 229 | #### Automatically calling the MS Graph on initialization 230 | You can also call the MS Graph API on initialization (in case the user is logged-in) by setting the `graph.callAfterInit` option to true in the [Graph Configuration Options](#graph-options). 231 | 232 | You can assign the endpoints to be called in an object with keys like this: 233 | ```js 234 | { 235 | // Configuration options 236 | graph: { 237 | callAfterInit: true, 238 | endpoints: { 239 | // ... 240 | // 'key' : endpoint 241 | // ... 242 | profile: '/me', 243 | photo: { url: '/me/photo/$value', responseType: 'blob', force: true } 244 | } 245 | } 246 | } 247 | ``` 248 | This will create an object with **the body** of each result assigned to its respective key. You can get the result in `vm.msal.graph` data object (using the [mixin](#mixin)) or in `vm.$msal.data.graph`. The results are also cached to the storage you have selected (see [cache options](#cache-options)) unless the `force` option has been set to true in an endpoint (see bellow). 249 | The endpoints that can be passed as a value to that object can have any of the formats described in the [manual call](#manually-calling-the-ms-graph). However the object format can also have two extra properties: 250 | * `batchUrl`: [optional] If this option is set to a URL string, the endpoint will be grouped with any other endpoints that have the same batchUrl and the actual call to the API will be a batch call. You can also set this to `'default'` (as a string) in which case it will be executed as a batch request to the URL set in `graph.baseUrl` option in [graph configuration](#graph-options); 251 | * `force`: [optional] If this is set to `true`, the result of this endpoint will not be read from / written to the cache. All other endpoints that don't have this option set to true will be cached, but this will be executed on every initialization. You should use this option for any result that cannot be encoded to JSON (like a **blob** for example). 252 | 253 | ## General notes 254 | ### OAuth 2.0 and the Implicit Flow 255 | Msal implements the [Implicit Grant Flow](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow), 256 | as defined by the OAuth 2.0 protocol and is [OpenID](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-protocols-oidc) 257 | compliant. 258 | 259 | Our goal is that the library abstracts enough of the protocol away so that you can get plug and play authentication, but it is important to know and understand the implicit flow from a security perspective. The implicit flow runs in the context of a web browser which cannot manage client secrets securely. It is optimized for single page apps and has one less hop between client and server so tokens are returned directly to the browser. These aspects make it naturally less secure. These security concerns are mitigated per standard practices such as- use of short lived tokens (and so no refresh tokens are returned), the library requiring a registered redirect URI for the app, library matching the request 260 | and response with a unique nonce and state parameter. 261 | 262 | > :exclamation: *Please check this [article](https://docs.microsoft.com/en-us/azure/active-directory/develop/v2-oauth2-implicit-grant-flow#send-the-sign-in-request) for details on how to enable **implicit grant flow** for your project* 263 | 264 | ### Cache Storage 265 | 266 | We offer two methods of storage for Msal, `localStorage` and `sessionStorage`. Our recommendation is to use `sessionStorage` because it is more secure in storing tokens that are acquired by your users, but `localStorage` will give you Single Sign On across tabs and user sessions. We encourage you to explore the options and make the best decision for your application. 267 | 268 | ## Configuration Options 269 | Configuration options are organized into groups like this 270 | ```js 271 | Vue.use(msal, { 272 | auth: { //Group 273 | clientId: '', //Option 1 274 | tenantId: '', //Option 2 275 | //... 276 | }, 277 | request: { //Group 278 | //... 279 | }, 280 | cache: { //Group 281 | //... 282 | }, 283 | system: { //Group 284 | //... 285 | }, 286 | framework: { //Group 287 | //... 288 | }, 289 | }); 290 | ``` 291 | #### `auth` options (***Required**) 292 | 293 | Option | Type | Description 294 | ------ | ----------- | ----------- 295 | clientId | `string` | ***Required**. The clientID of your application, you should get this from the [application registration portal](https://go.microsoft.com/fwlink/?linkid=2083908). 296 | authority | `string` | Your application's authority URL. Check [this page](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-client-application-configuration#authority) for more details. 297 | tenantId (legacy) | `string` | This is an identifier representing the sign-in audience. Can be:
`common`: - Used to sign in users with work and school accounts or a Microsoft personal account.
`organizations` - Used to sign in users with work and school accounts.
`consumers` - Used to sign in users with only personal Microsoft account (live)
or `` from Azure AD.
**Default**: `common`
:exclamation: This option is deprecated and will be removed in next major version. Please use the `authority` option above instead. You should replace `tenantId` and `tenantName` options by adding the `authority` option with value:
`https://{tenantName}/{tenantId}` 298 | tenantName (legacy) | `string` | This is is the identity provider domain.
**Default**:`login.microsoftonline.com`
:exclamation: This option is deprecated and will be removed in next major version. Please use the `authority` option above instead. You should replace `tenantId` and `tenantName` options by adding the `authority` option with value:
`https://{tenantName}/{tenantId}` 299 | validateAuthority | `boolean` | Validate the issuer of tokens. For B2C applications, since the authority value is known and can be different per policy, the authority validation will not work and has to be set to false.
**Default**: `true` 300 | redirectUri | `string` | `(() => string)` | The redirect URI of your app, where authentication responses can be sent and received by your app. It must exactly match one of the redirect URIs you registered in the portal.
**Default**: `window.location.href`. 301 | postLogoutRedirectUri | `string` | `(() => string)` | Redirects the user to postLogoutRedirectUri after sign out.
**Default**: `redirectUri` *(the previous option)* 302 | navigateToLoginRequestUrl | `boolean` | Ability to turn off default navigation to start page after login.
**Default**: `true` 303 | requireAuthOnInitialize | `boolean` | Setting this to true will automatically require authentication right after the plugin has been initialized
**Default**: `false` 304 | autoRefreshToken | `boolean` | When a token expires (either the `idToken` or the `accessToken`), if this is set to:
`false` the plugin will set the relevant token to an empty string
`true` the plugin will automatically attempt to renew the token
:grey_exclamation: Note: Expiration time includes the `tokenRenewalOffsetSeconds` value set in [System Options](#system-options)
**Default**: `true` 305 | onAuthentication | `(ctx, error, response) => any` | Callback function to be executed after authentication request.
Function's arguments are:
`ctx` - the msal class's context (vm.$msal)
`error` - request error (=`null` if request was successful)
`response` - request's result (=`null` if request was unsuccessful) 306 | onToken | `(ctx, error, response) => any` | Callback function to be executed after token request.
Function's arguments are:
`ctx` - the msal class's context (vm.$msal)
`error` - request error (=`null` if request was successful)
`response` - request's result (=`null` if request was unsuccessful) 307 | beforeSignOut | `(ctx) => any` | Callback function to be executed before manual sign out.
Function's arguments are:
`ctx` - the msal class's context (vm.$msal) 308 | 309 | #### `request` options 310 | 311 | Option | Type | Description 312 | ------ | ----------- | ----------- 313 | scopes | `string[]` | An array of strings representing the scopes that will be used for the **Sign In** request and the default **Acquire Token** request
Default: `["user.read"]` 314 | 315 | #### `graph` options 316 | 317 | Option | Type | Description 318 | ------ | ----------- | ----------- 319 | callAfterInit | `boolean` | Setting this to `true` will automatically call `vm.$msal.callMSGraph()` once the user has been authenticated.
**Default**: `false` 320 | endpoints | `object` | Please check the endpoint options in the [Automatically calling the MS Graph on initialization](#automatically-calling-the-ms-graph-on-initialization) section.
Default: `{profile: '/me'}` 321 | baseUrl | `string` | The default URL to be used when no full URL is set in single requests or no batch URL is set in batch requests.
Default: `'https://graph.microsoft.com/v1.0'` 322 | onResponse | `(ctx, response) => any` | Callback function called when a response has been received from the graph call. Function's arguments are:
`ctx` - the msal class's context (vm.$msal)
`response` - the graph call's response 323 | 324 | #### `cache` options 325 | 326 | Option | Type | Description 327 | ------ | ----------- | ----------- 328 | cacheLocation | `"localStorage"` | `"sessionStorage"` | Sets browser storage to either `localStorage` or `sessionStorage`.
**Default**: `localstorage` 329 | storeAuthStateInCookie | boolean | This flag was introduced in MSAL.js v0.2.2 as a fix for the [authentication loop issues](https://github.com/AzureAD/microsoft-authentication-library-for-js/wiki/Known-issues-on-IE-and-Edge-Browser#1-issues-due-to-security-zones) on Microsoft Internet Explorer and Microsoft Edge. Set this flag to `true` to take advantage of this fix. When this is enabled, MSAL.js will store the auth request state required for validation of the auth flows in the browser cookies.
**Default**: `true` 330 | 331 | #### `system` options 332 | 333 | Option | Type | Description 334 | ------ | ----------- | ----------- 335 | logger | **Logger** object | A Logger object with a callback instance that can be provided by the developer to consume and publish logs in a custom manner. For details on passing logger object, see [logging with msal.js](https://docs.microsoft.com/en-us/azure/active-directory/develop/msal-logging). 336 | loadFrameTimeout | number | The number of milliseconds of inactivity before a token renewal response from Azure AD should be considered timed out.
**Default**: `6000`. 337 | tokenRenewalOffsetSeconds | number | The number of milliseconds which sets the window of offset needed to renew the token before expiry.
**Default**: `300`.
:grey_exclamation: **Note:** Setting this number too high may result in `invalid_grant` errors (more info [here](https://docs.microsoft.com/en-us/azure/active-directory/develop/reference-breaking-changes#looping-clients-will-be-interrupted)) 338 | 339 | #### `framework` options 340 | 341 | Option | Type | Description 342 | ------ | ----------- | ----------- 343 | globalMixin | `boolean` | Setting this to `true` will add a mixin with the `msal` data object to **ALL** vue instances. Check the [Mixin](#mixin) section for more information
**Default**: `false` 344 | 345 | ## Major (breaking) changes 346 | (2.x.x) to (3.x.x): Added timer for automatically changing the accessToken on expiration 347 | 348 | (1.x.x) to (2.x.x): Changed the methods used for accessing the MS Graph API 349 | 350 | ## License 351 | 352 | [MIT License](./LICENSE) 353 | 354 | Copyright (c) Marios Vertopoulos 355 | -------------------------------------------------------------------------------- /lib/mixin.js: -------------------------------------------------------------------------------- 1 | export const mixin = { 2 | data: function() { 3 | return { 4 | msal: (this.$msal) ? this.$msal.data : {} 5 | } 6 | }, 7 | created: function() { 8 | this.$watch('$msal.data', (value) => { this.msal = value; }, { deep: true }); 9 | } 10 | }; 11 | -------------------------------------------------------------------------------- /lib/plugin.ts: -------------------------------------------------------------------------------- 1 | 'use strict'; 2 | import { Options, MSALBasic } from './src/types'; 3 | import { MSAL } from './src/main'; 4 | import { mixin } from "./mixin"; 5 | export const msalMixin = mixin; 6 | 7 | export default class msalPlugin { 8 | static install(Vue: any, options: Options): void { 9 | Vue.prototype.$msal = new msalPlugin(options, Vue); 10 | } 11 | constructor(options: Options, Vue: any = undefined) { 12 | const msal = new MSAL(options); 13 | if (Vue && options.framework && options.framework.globalMixin) { 14 | Vue.mixin(mixin); 15 | } 16 | const exposed: MSALBasic = { 17 | data: msal.data, 18 | signIn() { msal.signIn(); }, 19 | async signOut() { await msal.signOut(); }, 20 | isAuthenticated() { return msal.isAuthenticated(); }, 21 | async acquireToken(request, retries = 0) { return await msal.acquireToken(request, retries); }, 22 | async msGraph(endpoints, batchUrl) { return await msal.msGraph(endpoints, batchUrl) }, 23 | saveCustomData(key: string, data: any) { msal.saveCustomData(key, data); } 24 | }; 25 | return exposed; 26 | } 27 | } 28 | -------------------------------------------------------------------------------- /lib/src/UserAgentApplicationExtended.ts: -------------------------------------------------------------------------------- 1 | import {Configuration, UserAgentApplication} from "msal"; 2 | 3 | export class UserAgentApplicationExtended extends UserAgentApplication { 4 | public store = {}; 5 | constructor(configuration: Configuration) { 6 | super(configuration); 7 | this.store = this.cacheStorage 8 | } 9 | } 10 | -------------------------------------------------------------------------------- /lib/src/main.ts: -------------------------------------------------------------------------------- 1 | import _ from "lodash"; 2 | import {default as axios, Method} from "axios"; 3 | import {UserAgentApplicationExtended} from "./UserAgentApplicationExtended"; 4 | import { 5 | Auth, 6 | Request, 7 | Graph, 8 | CacheOptions, 9 | Options, 10 | DataObject, 11 | CallbackQueueObject, 12 | AuthError, 13 | AuthResponse, 14 | MSALBasic, 15 | GraphEndpoints, 16 | GraphDetailedObject, 17 | CategorizedGraphRequests 18 | } from './types'; 19 | 20 | export class MSAL implements MSALBasic { 21 | private lib: any; 22 | private tokenExpirationTimers: {[key: string]: undefined | number} = {}; 23 | public data: DataObject = { 24 | isAuthenticated: false, 25 | accessToken: '', 26 | idToken: '', 27 | user: {}, 28 | graph: {}, 29 | custom: {} 30 | }; 31 | public callbackQueue: CallbackQueueObject[] = []; 32 | private readonly auth: Auth = { 33 | clientId: '', 34 | authority: '', 35 | tenantId: 'common', 36 | tenantName: 'login.microsoftonline.com', 37 | validateAuthority: true, 38 | redirectUri: window.location.href, 39 | postLogoutRedirectUri: window.location.href, 40 | navigateToLoginRequestUrl: true, 41 | requireAuthOnInitialize: false, 42 | autoRefreshToken: true, 43 | onAuthentication: (error, response) => {}, 44 | onToken: (error, response) => {}, 45 | beforeSignOut: () => {} 46 | }; 47 | private readonly cache: CacheOptions = { 48 | cacheLocation: 'localStorage', 49 | storeAuthStateInCookie: true 50 | }; 51 | private readonly request: Request = { 52 | scopes: ["user.read"] 53 | }; 54 | private readonly graph: Graph = { 55 | callAfterInit: false, 56 | endpoints: {profile: '/me'}, 57 | baseUrl: 'https://graph.microsoft.com/v1.0', 58 | onResponse: (response) => {} 59 | }; 60 | constructor(private readonly options: Options) { 61 | if (!options.auth.clientId) { 62 | throw new Error('auth.clientId is required'); 63 | } 64 | this.auth = Object.assign(this.auth, options.auth); 65 | this.cache = Object.assign(this.cache, options.cache); 66 | this.request = Object.assign(this.request, options.request); 67 | this.graph = Object.assign(this.graph, options.graph); 68 | 69 | this.lib = new UserAgentApplicationExtended({ 70 | auth: { 71 | clientId: this.auth.clientId, 72 | authority: this.auth.authority || `https://${this.auth.tenantName}/${this.auth.tenantId}`, 73 | validateAuthority: this.auth.validateAuthority, 74 | redirectUri: this.auth.redirectUri, 75 | postLogoutRedirectUri: this.auth.postLogoutRedirectUri, 76 | navigateToLoginRequestUrl: this.auth.navigateToLoginRequestUrl 77 | }, 78 | cache: this.cache, 79 | system: options.system 80 | }); 81 | 82 | this.getSavedCallbacks(); 83 | this.executeCallbacks(); 84 | // Register Callbacks for redirect flow 85 | this.lib.handleRedirectCallback((error: AuthError, response: AuthResponse) => { 86 | if (!this.isAuthenticated()) { 87 | this.saveCallback('auth.onAuthentication', error, response); 88 | } else { 89 | this.acquireToken(); 90 | } 91 | }); 92 | 93 | if (this.auth.requireAuthOnInitialize) { 94 | this.signIn() 95 | } 96 | this.data.isAuthenticated = this.isAuthenticated(); 97 | if (this.data.isAuthenticated) { 98 | this.data.user = this.lib.getAccount(); 99 | this.acquireToken().then(() => { 100 | if (this.graph.callAfterInit) { 101 | this.initialMSGraphCall(); 102 | } 103 | }); 104 | } 105 | this.getStoredCustomData(); 106 | } 107 | signIn() { 108 | if (!this.lib.isCallback(window.location.hash) && !this.lib.getAccount()) { 109 | // request can be used for login or token request, however in more complex situations this can have diverging options 110 | this.lib.loginRedirect(this.request); 111 | } 112 | } 113 | async signOut() { 114 | if (this.options.auth.beforeSignOut) { 115 | await this.options.auth.beforeSignOut(this); 116 | } 117 | this.lib.logout(); 118 | } 119 | isAuthenticated() { 120 | return !this.lib.isCallback(window.location.hash) && !!this.lib.getAccount(); 121 | } 122 | async acquireToken(request = this.request, retries = 0) { 123 | try { 124 | //Always start with acquireTokenSilent to obtain a token in the signed in user from cache 125 | const response = await this.lib.acquireTokenSilent(request); 126 | this.handleTokenResponse(null, response); 127 | return response; 128 | } catch (error) { 129 | // Upon acquireTokenSilent failure (due to consent or interaction or login required ONLY) 130 | // Call acquireTokenRedirect 131 | if (this.requiresInteraction(error.errorCode)) { 132 | this.lib.acquireTokenRedirect(request); 133 | } else if(retries > 0) { 134 | return await new Promise((resolve) => { 135 | setTimeout(async () => { 136 | const res = await this.acquireToken(request, retries-1); 137 | resolve(res); 138 | }, 60 * 1000); 139 | }) 140 | } 141 | return false; 142 | } 143 | } 144 | private handleTokenResponse(error, response) { 145 | if (error) { 146 | this.saveCallback('auth.onToken', error, null); 147 | return; 148 | } 149 | let setCallback = false; 150 | if(response.tokenType === 'access_token' && this.data.accessToken !== response.accessToken) { 151 | this.setToken('accessToken', response.accessToken, response.expiresOn, response.scopes); 152 | setCallback = true; 153 | } 154 | if(this.data.idToken !== response.idToken.rawIdToken) { 155 | this.setToken('idToken', response.idToken.rawIdToken, new Date(response.idToken.expiration * 1000), [this.auth.clientId]); 156 | setCallback = true; 157 | } 158 | if(setCallback) { 159 | this.saveCallback('auth.onToken', null, response); 160 | } 161 | } 162 | private setToken(tokenType:string, token: string, expiresOn: Date, scopes: string[]) { 163 | const expirationOffset = this.lib.config.system.tokenRenewalOffsetSeconds * 1000; 164 | const expiration = expiresOn.getTime() - (new Date()).getTime() - expirationOffset; 165 | if (expiration >= 0) { 166 | this.data[tokenType] = token; 167 | } 168 | if (this.tokenExpirationTimers[tokenType]) clearTimeout(this.tokenExpirationTimers[tokenType]); 169 | this.tokenExpirationTimers[tokenType] = window.setTimeout(async () => { 170 | if (this.auth.autoRefreshToken) { 171 | await this.acquireToken({ scopes }, 3); 172 | } else { 173 | this.data[tokenType] = ''; 174 | } 175 | }, expiration) 176 | } 177 | private requiresInteraction(errorCode: string) { 178 | if (!errorCode || !errorCode.length) { 179 | return false; 180 | } 181 | return errorCode === "consent_required" || 182 | errorCode === "interaction_required" || 183 | errorCode === "login_required"; 184 | } 185 | // MS GRAPH 186 | async initialMSGraphCall() { 187 | const {onResponse: callback} = this.graph; 188 | let initEndpoints = this.graph.endpoints; 189 | 190 | if (typeof initEndpoints === 'object' && !_.isEmpty(initEndpoints)) { 191 | const resultsObj = {}; 192 | const forcedIds: string[] = []; 193 | try { 194 | const endpoints: { [id: string]: GraphDetailedObject & { force?: Boolean } } = {}; 195 | for (const id in initEndpoints) { 196 | endpoints[id] = this.getEndpointObject(initEndpoints[id]); 197 | if (endpoints[id].force) { 198 | forcedIds.push(id); 199 | } 200 | } 201 | let storedIds: string[] = []; 202 | let storedData = this.lib.store.getItem(`msal.msgraph-${this.data.accessToken}`); 203 | if (storedData) { 204 | storedData = JSON.parse(storedData); 205 | storedIds = Object.keys(storedData); 206 | Object.assign(resultsObj, storedData); 207 | } 208 | const {singleRequests, batchRequests} = this.categorizeRequests(endpoints, _.difference(storedIds, forcedIds)); 209 | const singlePromises = singleRequests.map(async endpoint => { 210 | const res = {}; 211 | res[endpoint.id as string] = await this.msGraph(endpoint); 212 | return res; 213 | }); 214 | const batchPromises = Object.keys(batchRequests).map(key => { 215 | const batchUrl = (key === 'default') ? undefined : key; 216 | return this.msGraph(batchRequests[key], batchUrl); 217 | }); 218 | const mixedResults = await Promise.all([...singlePromises, ...batchPromises]); 219 | mixedResults.map((res) => { 220 | for (const key in res) { 221 | res[key] = res[key].body; 222 | } 223 | Object.assign(resultsObj, res); 224 | }); 225 | const resultsToSave = {...resultsObj}; 226 | forcedIds.map(id => delete resultsToSave[id]); 227 | this.lib.store.setItem(`msal.msgraph-${this.data.accessToken}`, JSON.stringify(resultsToSave)); 228 | this.data.graph = resultsObj; 229 | } catch (error) { 230 | console.error(error); 231 | } 232 | if (callback) 233 | this.saveCallback('graph.onResponse', this.data.graph); 234 | } 235 | } 236 | async msGraph(endpoints: GraphEndpoints, batchUrl: string | undefined = undefined) { 237 | try { 238 | if (Array.isArray(endpoints)) { 239 | return await this.executeBatchRequest(endpoints, batchUrl); 240 | } else { 241 | return await this.executeSingleRequest(endpoints); 242 | } 243 | } catch (error) { 244 | throw error; 245 | } 246 | } 247 | private async executeBatchRequest(endpoints: Array, batchUrl = this.graph.baseUrl) { 248 | const requests = endpoints.map((endpoint, index) => this.createRequest(endpoint, index)); 249 | const {data} = await axios.request({ 250 | url: `${batchUrl}/$batch`, 251 | method: 'POST' as Method, 252 | data: {requests: requests}, 253 | headers: {Authorization: `Bearer ${this.data.accessToken}`}, 254 | responseType: 'json' 255 | }); 256 | let result = {}; 257 | data.responses.map(response => { 258 | let key = response.id; 259 | delete response.id; 260 | return result[key] = response 261 | }); 262 | // Format result 263 | const keys = Object.keys(result); 264 | const numKeys = keys.sort().filter((key, index) => { 265 | if (key.search('defaultID-') === 0) { 266 | key = key.replace('defaultID-', ''); 267 | } 268 | return parseInt(key) === index; 269 | }); 270 | if (numKeys.length === keys.length) { 271 | result = _.values(result); 272 | } 273 | return result; 274 | } 275 | private async executeSingleRequest(endpoint: string | GraphDetailedObject) { 276 | const request = this.createRequest(endpoint); 277 | if (request.url.search('http') !== 0) { 278 | request.url = this.graph.baseUrl + request.url; 279 | } 280 | const res = await axios.request(_.defaultsDeep(request, { 281 | url: request.url, 282 | method: request.method as Method, 283 | responseType: 'json', 284 | headers: {Authorization: `Bearer ${this.data.accessToken}`} 285 | })); 286 | return { 287 | status: res.status, 288 | headers: res.headers, 289 | body: res.data 290 | } 291 | } 292 | private createRequest(endpoint: string | GraphDetailedObject, index = 0) { 293 | const request = { 294 | url: '', 295 | method: 'GET', 296 | id: `defaultID-${index}` 297 | }; 298 | endpoint = this.getEndpointObject(endpoint); 299 | if (endpoint.url) { 300 | Object.assign(request, endpoint); 301 | } else { 302 | throw ({error: 'invalid endpoint', endpoint: endpoint}); 303 | } 304 | return request; 305 | } 306 | private categorizeRequests(endpoints: { [id:string]: GraphDetailedObject & { batchUrl?: string } }, excludeIds: string[]): CategorizedGraphRequests { 307 | let res: CategorizedGraphRequests = { 308 | singleRequests: [], 309 | batchRequests: {} 310 | }; 311 | for (const key in endpoints) { 312 | const endpoint = { 313 | id: key, 314 | ...endpoints[key] 315 | }; 316 | if (!_.includes(excludeIds, key)) { 317 | if (endpoint.batchUrl) { 318 | const {batchUrl} = endpoint; 319 | delete endpoint.batchUrl; 320 | if (!res.batchRequests.hasOwnProperty(batchUrl)) { 321 | res.batchRequests[batchUrl] = []; 322 | } 323 | res.batchRequests[batchUrl].push(endpoint); 324 | } else { 325 | res.singleRequests.push(endpoint); 326 | } 327 | } 328 | } 329 | return res; 330 | } 331 | private getEndpointObject(endpoint: string | GraphDetailedObject): GraphDetailedObject { 332 | if (typeof endpoint === "string") { 333 | endpoint = {url: endpoint} 334 | } 335 | if (typeof endpoint === "object" && !endpoint.url) { 336 | throw new Error('invalid endpoint url') 337 | } 338 | return endpoint; 339 | } 340 | // CUSTOM DATA 341 | saveCustomData(key: string, data: any) { 342 | if (!this.data.custom.hasOwnProperty(key)) { 343 | this.data.custom[key] = null; 344 | } 345 | this.data.custom[key] = data; 346 | this.storeCustomData(); 347 | } 348 | private storeCustomData() { 349 | if (!_.isEmpty(this.data.custom)) { 350 | this.lib.store.setItem('msal.custom', JSON.stringify(this.data.custom)); 351 | } else { 352 | this.lib.store.removeItem('msal.custom'); 353 | } 354 | } 355 | private getStoredCustomData() { 356 | let customData = {}; 357 | const customDataStr = this.lib.store.getItem('msal.custom'); 358 | if (customDataStr) { 359 | customData = JSON.parse(customDataStr); 360 | } 361 | this.data.custom = customData; 362 | } 363 | // CALLBACKS 364 | private saveCallback(callbackPath: string, ...args: any[]) { 365 | if (_.get(this.options, callbackPath)) { 366 | const callbackQueueObject: CallbackQueueObject = { 367 | id: _.uniqueId(`cb-${callbackPath}`), 368 | callback: callbackPath, 369 | arguments: args 370 | }; 371 | _.remove(this.callbackQueue, (obj) => obj.id === callbackQueueObject.id); 372 | this.callbackQueue.push(callbackQueueObject); 373 | this.storeCallbackQueue(); 374 | this.executeCallbacks([callbackQueueObject]); 375 | } 376 | } 377 | private getSavedCallbacks() { 378 | const callbackQueueStr = this.lib.store.getItem('msal.callbackqueue'); 379 | if (callbackQueueStr) { 380 | this.callbackQueue = [...this.callbackQueue, ...JSON.parse(callbackQueueStr)]; 381 | } 382 | } 383 | private async executeCallbacks(callbacksToExec: CallbackQueueObject[] = this.callbackQueue) { 384 | if (callbacksToExec.length) { 385 | for (let i in callbacksToExec) { 386 | const cb = callbacksToExec[i]; 387 | const callback = _.get(this.options, cb.callback); 388 | try { 389 | await callback(this, ...cb.arguments); 390 | _.remove(this.callbackQueue, function (currentCb) { 391 | return cb.id === currentCb.id; 392 | }); 393 | this.storeCallbackQueue(); 394 | } catch (e) { 395 | console.warn(`Callback '${cb.id}' failed with error: `, e.message); 396 | } 397 | } 398 | } 399 | } 400 | private storeCallbackQueue() { 401 | if (this.callbackQueue.length) { 402 | this.lib.store.setItem('msal.callbackqueue', JSON.stringify(this.callbackQueue)); 403 | } else { 404 | this.lib.store.removeItem('msal.callbackqueue'); 405 | } 406 | } 407 | } 408 | -------------------------------------------------------------------------------- /lib/src/types.ts: -------------------------------------------------------------------------------- 1 | import msal from "msal"; 2 | import conf from "msal/lib-commonjs/Configuration"; 3 | import { AxiosRequestConfig } from "axios"; 4 | 5 | export type AuthError = msal.AuthError; 6 | export type AuthResponse = msal.AuthResponse; 7 | 8 | export type Auth = { 9 | clientId: string, 10 | authority? : string, 11 | tenantId?: string, 12 | tenantName?: string, 13 | validateAuthority?: boolean; 14 | redirectUri?: string | (() => string); 15 | postLogoutRedirectUri?: string | (() => string); 16 | navigateToLoginRequestUrl?: boolean; 17 | requireAuthOnInitialize?: boolean, 18 | autoRefreshToken?: boolean, 19 | onAuthentication: (ctx: object, error: AuthError, response: AuthResponse) => any, 20 | onToken: (ctx: object, error: AuthError | null, response: AuthResponse | null) => any, 21 | beforeSignOut: (ctx: object) => any 22 | } 23 | 24 | export type Request = { 25 | scopes?: string[] 26 | } 27 | 28 | export type GraphDetailedObject = AxiosRequestConfig & { 29 | url: string, 30 | id?: string 31 | } 32 | export type GraphEndpoints = string | GraphDetailedObject | Array 33 | export type Graph = { 34 | callAfterInit?: boolean, 35 | baseUrl?: string, 36 | endpoints?: { [id: string]: string | GraphDetailedObject }, 37 | onResponse?: (ctx: object, response: object) => any 38 | } 39 | 40 | export type CacheOptions = conf.CacheOptions; 41 | export type SystemOptions = conf.SystemOptions; 42 | export type FrameworkOptions = { 43 | globalMixin?: boolean 44 | } 45 | 46 | export type Options = { 47 | auth: Auth, 48 | request?: Request, 49 | graph?: Graph, 50 | cache?: CacheOptions, 51 | system?: SystemOptions, 52 | framework?: FrameworkOptions 53 | } 54 | 55 | export type DataObject = { 56 | isAuthenticated: boolean, 57 | accessToken: string, 58 | idToken: string, 59 | user: object, 60 | graph: object, 61 | custom: object 62 | } 63 | 64 | export type CallbackQueueObject = { 65 | id: string, 66 | callback: string, 67 | arguments: any[] 68 | } 69 | 70 | export interface MSALBasic { 71 | data: DataObject, 72 | signIn: () => void, 73 | signOut: () => Promise | void, 74 | isAuthenticated: () => boolean, 75 | acquireToken: (request: Request, retries: number) => Promise, 76 | msGraph: (endpoints: GraphEndpoints, batchUrl: string | undefined) => Promise, 77 | saveCustomData: (key: string, data: any) => void 78 | } 79 | 80 | export type CategorizedGraphRequests = { singleRequests: GraphDetailedObject[], batchRequests: { [id:string]: GraphDetailedObject[] } } 81 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-msal", 3 | "version": "3.2.0", 4 | "lockfileVersion": 1, 5 | "requires": true, 6 | "dependencies": { 7 | "@types/lodash": { 8 | "version": "4.14.138", 9 | "resolved": "https://registry.npmjs.org/@types/lodash/-/lodash-4.14.138.tgz", 10 | "integrity": "sha512-A4uJgHz4hakwNBdHNPdxOTkYmXNgmUAKLbXZ7PKGslgeV0Mb8P3BlbYfPovExek1qnod4pDfRbxuzcVs3dlFLg==", 11 | "dev": true 12 | }, 13 | "axios": { 14 | "version": "0.19.0", 15 | "resolved": "https://registry.npmjs.org/axios/-/axios-0.19.0.tgz", 16 | "integrity": "sha512-1uvKqKQta3KBxIz14F2v06AEHZ/dIoeKfbTRkK1E5oqjDnuEerLmYTgJB5AiQZHJcljpg1TuRzdjDR06qNk0DQ==", 17 | "requires": { 18 | "follow-redirects": "1.5.10", 19 | "is-buffer": "^2.0.2" 20 | } 21 | }, 22 | "debug": { 23 | "version": "3.1.0", 24 | "resolved": "https://registry.npmjs.org/debug/-/debug-3.1.0.tgz", 25 | "integrity": "sha512-OX8XqP7/1a9cqkxYw2yXss15f26NKWBpDXQd0/uK/KPqdQhxbPa994hnzjcE2VqQpDslf55723cKPUOGSmMY3g==", 26 | "requires": { 27 | "ms": "2.0.0" 28 | } 29 | }, 30 | "follow-redirects": { 31 | "version": "1.5.10", 32 | "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.5.10.tgz", 33 | "integrity": "sha512-0V5l4Cizzvqt5D44aTXbFZz+FtyXV1vrDN6qrelxtfYQKW0KO0W2T/hkE8xvGa/540LkZlkaUjO4ailYTFtHVQ==", 34 | "requires": { 35 | "debug": "=3.1.0" 36 | } 37 | }, 38 | "is-buffer": { 39 | "version": "2.0.3", 40 | "resolved": "https://registry.npmjs.org/is-buffer/-/is-buffer-2.0.3.tgz", 41 | "integrity": "sha512-U15Q7MXTuZlrbymiz95PJpZxu8IlipAp4dtS3wOdgPXx3mqBnslrWU14kxfHB+Py/+2PVKSr37dMAgM2A4uArw==" 42 | }, 43 | "lodash": { 44 | "version": "4.17.15", 45 | "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.15.tgz", 46 | "integrity": "sha512-8xOcRHvCjnocdS5cpwXQXVzmmh5e5+saE2QGoeQmbKmRS6J3VQppPOIt0MnmE+4xlZoumy0GPG0D0MVIQbNA1A==" 47 | }, 48 | "ms": { 49 | "version": "2.0.0", 50 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.0.0.tgz", 51 | "integrity": "sha1-VgiurfwAvmwpAd9fmGF4jeDVl8g=" 52 | }, 53 | "msal": { 54 | "version": "1.3.2", 55 | "resolved": "https://registry.npmjs.org/msal/-/msal-1.3.2.tgz", 56 | "integrity": "sha512-vhcpM/ELL+UI7i4HzCegcbSfPMLqf3kp8mAT840bK1ZaDcb7Z1mOJik1jg202V0yfnh/bBPxZhQP6xFgD9g5eA==", 57 | "requires": { 58 | "tslib": "^1.9.3" 59 | } 60 | }, 61 | "tslib": { 62 | "version": "1.13.0", 63 | "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", 64 | "integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==" 65 | } 66 | } 67 | } 68 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "vue-msal", 3 | "version": "3.2.0", 4 | "description": "Vue plugin for using Microsoft Authentication Library (MSAL)", 5 | "main": "dist/plugin.js", 6 | "scripts": { 7 | "test": "echo \"Error: no test specified\" && exit 1" 8 | }, 9 | "keywords": [ 10 | "vue", 11 | "msal", 12 | "adal", 13 | "authentication", 14 | "sso" 15 | ], 16 | "author": "Marios Vertopoulos", 17 | "license": "MIT", 18 | "bugs": { 19 | "url": "https://github.com/mvertopoulos/vue-msal/issues" 20 | }, 21 | "contributors": [ 22 | { 23 | "name": "Marios Vertopoulos" 24 | } 25 | ], 26 | "eslintIgnore": [ 27 | "lib/*.*" 28 | ], 29 | "homepage": "https://github.com/mvertopoulos/vue-msal#readme", 30 | "publishConfig": { 31 | "access": "public" 32 | }, 33 | "repository": { 34 | "type": "git", 35 | "url": "git+https://github.com/mvertopoulos/vue-msal.git" 36 | }, 37 | "dependencies": { 38 | "axios": "^0.19.0", 39 | "lodash": "^4.17.15", 40 | "msal": "^1.3.2" 41 | }, 42 | "devDependencies": { 43 | "@types/lodash": "^4.14.138" 44 | } 45 | } 46 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "es5", /* Specify ECMAScript target version: 'ES3' (default), 'ES5', 'ES2015', 'ES2016', 'ES2017', 'ES2018', 'ES2019' or 'ESNEXT'. */ 5 | "lib": ["es2015", "dom"], /* List of library files to be included in the compilation. */ 6 | "module": "commonjs", /* Specify module code generation: 'none', 'commonjs', 'amd', 'system', 'umd', 'es2015', or 'ESNext'. */ 7 | "allowJs": true, /* Allow javascript files to be compiled. */ 8 | "sourceMap": true, /* Generates corresponding '.map' file. */ 9 | "outDir": "./dist", /* Redirect output structure to the directory. */ 10 | /* Strict Type-Checking Options */ 11 | "strict": true, /* Enable all strict type-checking options. */ 12 | "noImplicitAny": false, /* Raise error on expressions and declarations with an implied 'any' type. */ 13 | /* Module Resolution Options */ 14 | "esModuleInterop": true /* Enables emit interoperability between CommonJS and ES Modules via creation of namespace objects for all imports. Implies 'allowSyntheticDefaultImports'. */ 15 | }, 16 | "include": [ 17 | "lib/**/*" 18 | ] 19 | } 20 | --------------------------------------------------------------------------------