mvertopoulos / vue-msal

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

ctx.isAuthenticated() is false inside onAuthentication #1

Closed Agendum closed 4 years ago

Agendum commented 4 years ago

Hi,

Thanks for putting vue-msal together. It is generally working for me. However I noticed that the isAuthenticated() value is false within the onAuthentication handler:

onAuthentication: (ctx, error, response) => {
  console.log({ ctx, error, response });
  console.log(">>> isAuthenticated = " + ctx.isAuthenticated());
}

With this code I can see there is no error, but isAuthenticated is still false. If I peak at the the response I can see a valid response.

The next strange thing about all of this is if I call ctx.acquireToken() within onAuthentication, the returned promise of acquireToken is simply never resolved:

onAuthentication: (ctx, error, response) =>
{
  console.log({ ctx, error, response });
  console.log(">>> isAuthenticated = " + ctx.isAuthenticated());

  // Nothing is printed beyond this point, even though acquireToken yields a valid promise...
  ctx.acquireToken().then(
    () => {
      console.log(">>> success");
    },
    () => {
      console.log(">>> error");
    },
    () => {
      console.log(">>> rejected");
    });
}

So using vue-msal, what would be an appropriate technique to get the auth token immediately after a successful authentication? I am trying to make my code reactive based on that state. If I wait to call acquireToken, for example, from a click event, everything works. But I need to get the token ASAP.

Thanks!

Agendum commented 4 years ago

After further experimentation, it looks like this is the standard behavior of MSAL itself, not vue-msal.

mvertopoulos commented 4 years ago

Hi Agendum,

Thank you for the issues you opened. I am going through each of them and will reply to your other issues as well, when I am able to do so.

I am reopening this issue to make some clarifications:

I noticed that the isAuthenticated() value is false within the onAuthentication handler

This is actually happening because the onAuthentication handler is running right after the user has been redirected to your application by the Microsoft login page. This is considered to be a callback and the isAuthenticated value is false at this stage because of it. The isAuthenticated value is true based on the two following conditions:

The next strange thing about all of this is if I call ctx.acquireToken() within onAuthentication, the returned promise of acquireToken is simply never resolved.

This is simply because your function's execution is finished before the actual promise is resolved. In order to wait for the promise to be resolved your function must return a promise by itself. For your example you could write:

onAuthentication: (ctx, err, response) => {
  return new Promise((resolve, reject) => {
    ctx.acquireToken().then((token) => { 
      //Note that an unsuccessful token request returns false, not an error.
      //Also the promise / then based syntax accepts two callback functions not three as you have in your example
      console.log(token);
      resolve();
    })
  });
}

or simply using ES6 syntax

onAuthentication: async (ctx, err, response) => {
  try {
    const token = await ctx.acquireToken();
    console.log(token); //Note that an unsuccessful token request returns false, not an error
  } catch (error) {
    console.log(error);
  }
}

So using vue-msal, what would be an appropriate technique to get the auth token immediately after a successful authentication? I am trying to make my code reactive based on that state.

In general the aquireToken function should be used mainly for getting an updated token with new permissions required only for a section of your application. This allows for dynamic / incremental permissions requested, that the user can accept only when it is necessary.

To actually read the access token or the isAuthenticated state you should use the data object provided by the plugin. To use that object with reactivity it is recommended to use the mixin that is also provided in the package.

So for example you could do this:

<script>
//You can also import the mixin globally (check the mixin section in the documentation for more info)
import { msalMixin } from 'vue-msal'; 

new Vue({
  el: '#demo',
  mixins: [msalMixin],
  watch: {
    'msal.isAuthenticated': {
      handler(value) {
        console.log('isAuthenticated is ', value)
        // Do something here
      },
      immediate: true
    },
    'msal.accessToken': {
      handler(value) {
        console.log('accessToken is ', value)
        // Do something here
      },
      immediate: true
    }
  }
});
</script>

Check the relevant sections in the documentation provided in the README.md for more info on what other properties the data object contains.

Hope this answers your questions. Let me know if you have any other queries.

Agendum commented 4 years ago

Thanks this does make a lot of sense. I didn't trust that msal.accessToken was reactive and it sure is. I now don't need to custom watch anything at all, actually. Once I wired up vue-msal using globalMixin, everything just worked in all of my components without any extra business logic. Well, the only thing I had to do with toggle the config 'requireAuthOnInitialize' based on window.location.pathname for my signedout route because it doesn't seem the vue router is ready yet. But other than that, it made this whole auth story so painless. I even ended up dropping the group checking and went with checking for app roles instead, so no graph calls needed even.

Great job on vue-msal!

Agendum commented 4 years ago

You mentioned:

you should use the data object provided by the plugin.

But I have noticed vue-msal's login state / data.accessToken is not automatically renewed when the token's exp elapses. Creating a timer to renew the access token at exp-minus-tokenRenewalOffsetSeconds time is trivial, but me manually updating data.accessToken also doesn't update any other vue-msal's data properties. For my scenario, this is actually okay, but I am not sure this is a good pattern and the inconsistent state makes me uncomfortable. What is your opinion on the matter?

mvertopoulos commented 4 years ago

Hi @Agendum ,

Sorry for the late reply.

How are you checking that the data.accessToken is not automatically renewed?

The token expiration check is performed on initial page load (where the plugin is initialized), and not when navigating through pages using the vue router. When the token is found to be expired during that check, the user is redirected to the Microsoft login page.

A timer for renewing the token on that scenario should indeed solve the problem. I will have to get back to you, when I have the time to implement it though. Otherwise, you could fork this project and create a pull request.

mvertopoulos commented 4 years ago

Moved the timer to a new issue #9 .