-2

I have an angular SPA that runs in an office add-in (word) that I need to authenticate against Azure AD using Oauth2, consume resources from multiple apis and make graph calls. I have been successful doing this in a standard web app but not with the introduction of office add-ins and using the office dialog api. Currently, I have implemented my own authentication service to get auth code with PKCE, get an access token & refresh token, and manage the lifetimes of them. I am stuck trying to take an existing access token and exchange it for use on a different resource without starting the process entirely over. I have read about the on-behalf-of flow to do this task, but it seems to require a client secret or a client assertion and typically is designed for a "middle-man" api. I do not believe that is safe on a SPA. Reading through the MSAL.js source code it appears it can be done, but it also looks like they may be building the new token, encoding it and signing it within the package that runs in the browser. Is there a safe way to use on-behalf-of flow for exchanging tokens? Is there a different option to do this that I don't know about? Thanks!

4
  • Please edit the question to limit it to a specific problem with enough detail to identify an adequate answer.
    – Community Bot
    Commented Jun 30, 2023 at 23:08
  • It is impossible to exchange an access_token for another access_token -- that would be a major security flaw if this were possible (The whole point of an access_token is that its scope is strictly limited at the time the token is issued). if for any reason the scope of the token needs to be expanded to some other resource then only the resource owner themselves can grant permission for this, which means you must go through the auth flow again to obtain their consent via the auth server for that resource. It is deliberately impossible to do this silently without their consent. Commented Jul 1, 2023 at 6:49
  • The principle behind access tokens are the same as their real-world physical equivalents (e.g. keys used for building security). Imagine you are the holder of some physical keys that open some locks in a building (i.e. you don't own the building, but the building's owner approved your access to some areas like the front door and the car park and handed you copies of the keys). In this scenario you cannot just go and help yourself to a whole bunch of other keys, such a request must be approved by whoever owns those restricted areas. Commented Jul 1, 2023 at 7:08
  • Have you developed an application using MSAL.js? You 100% login to Azure AD once and using the "Protected Resource Map" of the MSAL interceptor a new access token is somehow generated for the API being called in the request. In our example you login once and tokens can be provided for the graph or our API without logging in to both. So something is happening that exchanges the token, or some other type of microsoft magic.
    – Josh Engel
    Commented Jul 5, 2023 at 17:50

1 Answer 1

-1

**Update: With the help of Microsoft (surprise, surprise...) I was able to accomplish this authentication scenario. With one user interaction I can successfully login to Azure AD with an active account and connect to any resource protected by Azure with separate JWT access tokens without subsequent user interactions. Doing this in an office add-in required resolving 3 basic issues: login/logout, login from an http interceptor and login from a route guard. The trick was to override the authRequest 'onRedirectNavigate' and send the url request to the office.context.ui.displayDialogAsync() method. You must also send the resulting authentication redirect back to the taskpane and call the MsalService handleRedirectPromise method with the redirect as the parameter.

This example code shows how to use the onRedirectNavigate of the authRequest to use the office dialog for authentication:

 authRequest: {
            scopes: [],
            onRedirectNavigate: (url: string) => {
                    Office.context.ui.displayDialogAsync(
                      url,
                      { height: 50, width: 40 },
                      (result) => {
                        if (result.status === Office.AsyncResultStatus.Failed) {
                          console.log(`${result.error.code} ${result.error.message}`);
                        } else {
                          dialog = result.value;
                          dialog.addEventHandler(
                            Office.EventType.DialogMessageReceived,
                            // officeDialogHelper.processLoginMessage 
                            async (arg: any) => {
                                let messageFromDialog = JSON.parse(arg.message);
                                if (messageFromDialog.status === 'success') {

and then take the resulting redirect:

async handleRedirectCallback(authCode: string) {
        await this.msalService.instance.handleRedirectPromise(authCode);
    }

The places to implement this are exactly...

  • In your login & logout methods
 async login() {
    this.msalService.loginRedirect({
      onRedirectNavigate: (url: string) => {
        Office.context.ui.displayDialogAsync(
          url,
          { height: 50, width: 40 },
          (result) => {
            if (result.status === Office.AsyncResultStatus.Failed) {
              console.log(`${result.error.code} ${result.error.message}`);
            } else {
              this.loginDialog = result.value;
              this.loginDialog.addEventHandler(
                Office.EventType.DialogMessageReceived,
                this.processLoginMessage
              );
...
logout() {
    this.msalService.logoutRedirect({
      onRedirectNavigate: (url: string) => {
        Office.context.ui.displayDialogAsync(
          url,
          { height: 50, width: 40 },
          (result) => {
  • In the MsalGuard configuration seen in your module you set the guard up as a provider
export function MSALGuardConfigFactory(officeDialogHelper: officeDialogHelper): MsalGuardConfiguration {
    let dialog: Office.Dialog;
    return {
        interactionType: InteractionType.Redirect,
        authRequest: {
            scopes: [],
            onRedirectNavigate: (url: string) => {
                    Office.context.ui.displayDialogAsync(
                      url,
                      { height: 50, width: 40 },
                      (result) => {
  • In the MsalInterceptor config factory
 return {
        interactionType: InteractionType.Redirect,
        protectedResourceMap,
        authRequest: {
            onRedirectNavigate: (url: string) => {
                Office.context.ui.displayDialogAsync(
                  url,
                  { height: 50, width: 40 },
                  (result) => {
                    if (result.status === Office.AsyncResultStatus.Failed) {
                      console.log(`${result.error.code} ${result.error.message}`);
                    } else {
                       dialog = result.value;
                        dialog.addEventHandler(
                        Office.EventType.DialogMessageReceived,
                        async (arg: any) => {

Here is my issue I created on the Microsoft GitHub page: https://github.com/AzureAD/microsoft-authentication-library-for-js/issues/6177

Hope this helps someone! - Josh

1
  • As a rule of thumb, if a post is more than half code it means this should have been on Stack Overflow, not here. Commented Jul 18, 2023 at 12:09

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.