mvertopoulos / vue-msal

Vue plugin for using Microsoft Authentication Library (MSAL)
MIT License
123 stars 66 forks source link

vue-msal

Wrapper of MSAL.js (Microsoft Authentication Library) for usage in Vue.

The vue-msal library enables client-side vue applications, running in a web browser, to authenticate users using Azure AD work and school accounts (AAD), Microsoft personal accounts (MSA) and social identity providers like Facebook, Google, LinkedIn, Microsoft accounts, etc. through Azure AD B2C service. It also enables your app to access Microsoft Cloud services such as Microsoft Graph.

Installation

Add the vue-msal dependency to your project using yarn or npm.

npm install vue-msal
or
yarn add vue-msal

Vue Usage

Use the plugin in your vue instance like this

import msal from 'vue-msal'

Vue.use(msal, {
    auth: {
      clientId: '<YOUR CLIENT ID HERE>'
    }
});

new Vue({
  //... vue options
})

Nuxt Usage

Add a new javascript file like msal.js under /plugins/ directory with the following content

: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 section below for more information


import Vue from 'vue' //import Vue if you want to use the framework.globalMixin option
import MSAL from 'vue-msal'

export default ({ app, error, $axios }, inject) => { inject('msal', new MSAL( { auth: { clientId: '' } }, Vue / [optional] should be passed as an argument if you want to the framework.globalMixin option/ )) }

Then include it to the plugins array in `nuxt.config.js` like this
```js
export default {
    plugins: [
        //...
        '@/plugins/msal'
        //...
    ]
}

This will make the $msal object available in both the vue instances and the context. For example you can access it in the context of a middleware via the app object like this:

export default function ({ app, route, error }) {
  // If the user is not authenticated and he's not in the /login page throw Error
  if (!app.$msal.isAuthenticated() && route.name !== 'login') {
    error({ statusCode: 401, message: 'Unauthorized' });
  }
}

Plugin usage

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:

new Vue({
    //...
    created() {
        if (!this.$msal.isAuthenticated()) {
            this.$msal.signIn();
        }
    }
});

List of functions

The data object

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. However in case you want reactivity for this data, it is recomended that you use the mixin method below.

The properties provided in the data object are the following:

Using accessToken vs idToken

Mixin

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.

: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.

So for example you can do this:

<div id="demo">
    <div v-if="user">
        <div>Welcome {{user.name}}</div>
        <div v-if="user.profile.jobTitle">Your job title is {{user.profile.jobTitle}}</div>
        <div><button @click="$msal.signOut()">logout</button></div>
    </div>
    <div v-else>
        Please sign-in
        <button @click="$msal.signIn()"></button>
    </div>
</div>

<script>
//Importing the mixin locally (omit the following line if you are using the 'framework.globalMixin' option)
import { msalMixin } from 'vue-msal'; 

new Vue({
    el: '#demo',
    //Importing the mixin locally (omit the following line if you are using the 'framework.globalMixin' option)
    mixins: [msalMixin],
    computed: {
        user() {
          let user = null;
          if (this.msal.isAuthenticated) { // Note that the dollar sign ($) is missing from this.msal
            user = {
              ...this.msal.user,
              profile: {}
            }
            if (this.msal.graph && this.msal.graph.profile) {
                user.profile = this.msal.graph.profile
            }
          }
          return user;
        }
    }
});
</script>

: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. 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 section for details.

Calling MS Graph

You can directly call the MS Graph API for a logged-in user, with the following methods.

Manually calling the MS Graph

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.

This function receives the following arguments:

The response of this call depends on the arguments passed to it.

Example usage:

new Vue({
    //...
    async mounted() {
        let result;
        result = await app.$msal.msGraph('https://www.example.com/1.0/me');
        // Single request at: https://www.example.com/1.0/me
        // Returns: { status: <number>, headers: <object>, body: <object> }
        result = await app.$msal.msGraph('/me');
        // Single request at: graph.baseUrl + '/me'
        // Returns: { status: <number>, headers: <object>, body: <object> }
        await app.$msal.msGraph(['/me', '/me/messages']);
        // Batch request at: graph.baseUrl for endpoints '/me' & '/me/messages'
        // Returns: [
        //      { status: <number>, headers: <object>, body: <object> },
        //      { status: <number>, headers: <object>, body: <object> }
        // ]
        await app.$msal.msGraph(['/me', '/me/messages'], 'https://www.custom-msgraph-url.com');
        // Batch request at: 'https://www.custom-msgraph-url.com' for endpoints '/me' & '/me/messages'
        // Returns: [
        //      { status: <number>, headers: <object>, body: <object> },
        //      { status: <number>, headers: <object>, body: <object> }
        // ]
        await app.$msal.msGraph([{ url: '/me'}, { url: '/me/photo/$value', responseType: 'blob' }]);
        // Batch request at: graph.baseUrl for endpoints '/me' & '/me/photo/$value'
        // Returns: [
        //      { status: <number>, headers: <object>, body: <object> },
        //      { status: <number>, headers: <object>, body: <object> }
        // ]
        await app.$msal.msGraph([{ url: '/me', id: 'profile'}, { url: '/me/photo/$value', id: 'photo', responseType: 'blob' }]);
        // Batch request at: graph.baseUrl for endpoints '/me' & '/me/photo/$value'
        // Returns: {
        //      profile: { status: <number>, headers: <object>, body: <object> },
        //      photo: { status: <number>, headers: <object>, body: <object> }
        // }
        await app.$msal.msGraph(['/me', { url: '/me/photo/$value', id: 'photo', responseType: 'blob' }]);
        // Batch request at: graph.baseUrl in endpoints '/me' & '/me/photo/$value'
        // Returns: {
        //      0: { status: <number>, headers: <object>, body: <object> },
        //      photo: { status: <number>, headers: <object>, body: <object> }
        // }
    }
});

Automatically calling the MS Graph on initialization

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.

You can assign the endpoints to be called in an object with keys like this:

{
    // Configuration options
    graph: {
      callAfterInit: true,
      endpoints: {
        // ...
        // 'key' : endpoint
        // ...
        profile: '/me',
        photo: { url: '/me/photo/$value', responseType: 'blob', force: true }
      }
    }
}

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) or in vm.$msal.data.graph. The results are also cached to the storage you have selected (see cache options) unless the force option has been set to true in an endpoint (see bellow). The endpoints that can be passed as a value to that object can have any of the formats described in the manual call. However the object format can also have two extra properties:

General notes

OAuth 2.0 and the Implicit Flow

Msal implements the Implicit Grant Flow, as defined by the OAuth 2.0 protocol and is OpenID compliant.

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 and response with a unique nonce and state parameter.

:exclamation: Please check this article for details on how to enable implicit grant flow for your project

Cache Storage

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.

Configuration Options

Configuration options are organized into groups like this

Vue.use(msal, {
    auth: { //Group
        clientId: '<YOUR CLIENT ID HERE>', //Option 1
        tenantId: '<YOUR TENANT ID HERE>', //Option 2
        //...
    },
    request: { //Group
        //...
    },
    cache: { //Group
        //...
    },
    system: { //Group
        //...
    },
    framework: { //Group
        //...
    },
});

auth options (*Required)

Option Type Description
clientId string *Required. The clientID of your application, you should get this from the application registration portal.
authority string Your application's authority URL. Check this page for more details.
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 <Tenant ID> 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}
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}
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
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.
postLogoutRedirectUri string | (() => string) Redirects the user to postLogoutRedirectUri after sign out.
Default: redirectUri (the previous option)
navigateToLoginRequestUrl boolean Ability to turn off default navigation to start page after login.
Default: true
requireAuthOnInitialize boolean Setting this to true will automatically require authentication right after the plugin has been initialized
Default: false
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
Default: true
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)
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)
beforeSignOut (ctx) => any Callback function to be executed before manual sign out.
Function's arguments are:
ctx - the msal class's context (vm.$msal)

request options

Option Type Description
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"]

graph options

Option Type Description
callAfterInit boolean Setting this to true will automatically call vm.$msal.callMSGraph() once the user has been authenticated.
Default: false
endpoints object Please check the endpoint options in the Automatically calling the MS Graph on initialization section.
Default: {profile: '/me'}
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'
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

cache options

Option Type Description
cacheLocation "localStorage" | "sessionStorage" Sets browser storage to either localStorage or sessionStorage.
Default: localstorage
storeAuthStateInCookie boolean This flag was introduced in MSAL.js v0.2.2 as a fix for the authentication loop issues 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

system options

Option Type Description
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.
loadFrameTimeout number The number of milliseconds of inactivity before a token renewal response from Azure AD should be considered timed out.
Default: 6000.
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)

framework options

Option Type Description
globalMixin boolean Setting this to true will add a mixin with the msal data object to ALL vue instances. Check the Mixin section for more information
Default: false

Major (breaking) changes

(2.x.x) to (3.x.x): Added timer for automatically changing the accessToken on expiration

(1.x.x) to (2.x.x): Changed the methods used for accessing the MS Graph API

License

MIT License

Copyright (c) Marios Vertopoulos