0
votes

What I have so far

I'm using two different Node.js projects but I would like to put them together.

  1. The first uses interactive login (code submitted in browser) then finds all my (signed-on user) tenants.
  2. The second uses react to click a button to sign on and get information from Microsoft Graph.

Azure application API permissions

The Azure application for both has permission user_impersonation (for tenant/subscription info) and user.read (profile info).

enter image description here

Code for React app

I used this tutorial provided by Microsoft. The only change I made was the scopes to add the user_impersonation.

// config.js
module.exports = {
    appId: '1760c31a-....-baf68c2b3244',
    redirectUri: 'http://localhost:3000',
    scopes: [  'user.read'
    ]
  };

It uses the following auth library and login function:

 // app.js
  import { UserAgentApplication } from 'msal';

class App extends Component {
  constructor(props) {
    super(props);

    console.log(JSON.stringify(props));

    this.userAgentApplication = new UserAgentApplication({
      auth: {
        clientId: config.appId,
        redirectUri: config.redirectUri
      },
      cache: {
        cacheLocation: "localStorage",
        storeAuthStateInCookie: true
      }
    });

    var user = this.userAgentApplication.getAccount();

    this.state = {
      isAuthenticated: (user !== null),
      user: {},
      subscriptions: {},
      error: null
    };

    if (user) {
      // Enhance user object with data from Graph
      this.getUserProfile();
    }
  }

  setErrorMessage(message, debug) {
    this.setState({
      error: { message: message, debug: debug }
    });
  }

  async login() {
    try {
           await this.userAgentApplication.loginPopup(
           {
                scopes: [
                     'user.read',
                     'calendars.read'
                ],
                prompt: "select_account"
            });
           await this.getUserProfile();
    }
    catch (err) {
      var error = {};

      if (typeof (err) === 'string') {
        var errParts = err.split('|');
        error = errParts.length > 1 ?
          { message: errParts[1], debug: errParts[0] } :
          { message: err };
      } else {
        error = {
          message: err.message,
          debug: JSON.stringify(err)
        };
      }

      this.setState({
        isAuthenticated: false,
        user: {},
        error: error
      });
    }
  }

  logout() {
    this.userAgentApplication.logout();
  }
  async getSubscriptions(bearerToken) {

    const requestConfig = {
      url: "tenants",
      method: "GET",
      baseURL: "https://management.azure.com/",
      params: {
        'api-version': '2018-01-01'
      },
      headers: {
        'Content-Type': 'application/json',
        'Authorization': `Bearer ${bearerToken}`
      }
    }
    const subscriptions = await httpRequest(requestConfig);
    console.log(JSON.stringify(subscriptions.data));
    return subscriptions.data;
  }
  async getUserProfile() {
    try {
      // Get the access token silently
      // If the cache contains a non-expired token, this function
      // will just return the cached token. Otherwise, it will
      // make a request to the Azure OAuth endpoint to get a token

      var accessTokenGraphQL = await this.userAgentApplication.acquireTokenSilent({
        scopes: [
          'user.read',
          'calendars.read'
        ]
      });

      // JSON.stringify(accessTokenGraphQL.scopes) returns
      // ["User.Read","Calendars.Read","openid","profile"]

      var accessTokenSubscriptionList = await this.userAgentApplication.acquireTokenSilent({
        scopes: [
          'https://management.azure.com/user_impersonation'
        ]
      });

      if (accessTokenGraphQL) {
        // Get the user's profile from Graph
        console.log(`accessToken = ${JSON.stringify(accessTokenGraphQL)}`)
        var user = await getUserDetails(accessTokenGraphQL);

        if(accessTokenSubscriptionList){
          var subscriptions = await getSubscriptions(accessTokenSubscriptionList)
          console.log(`subscriptions = ${JSON.stringify(subscriptions)}`)
          user.subscriptions = subscriptions;
        }

        this.setState({
          isAuthenticated: true,
          user: {
            displayName: user.displayName,
            email: user.mail || user.userPrincipalName,
            accessToken: accessTokenGraphQL
          },
          error: null
        });
      }
    }
    catch (err) {
      var error = {};
      if (typeof (err) === 'string') {
        var errParts = err.split('|');
        error = errParts.length > 1 ?
          { message: errParts[1], debug: errParts[0] } :
          { message: err };
      } else {
        error = {
          message: err.message,
          debug: JSON.stringify(err)
        };
      }

      this.setState({
        isAuthenticated: false,
        user: {},
        error: error
      });
    }
  }

  render() {
    let error = null;
    if (this.state.error) {
      error = <ErrorMessage message={this.state.error.message} debug={this.state.error.debug} />;
    }

    return (
      <Router>
        <div>
          <NavBar
            isAuthenticated={this.state.isAuthenticated}
            authButtonMethod={this.state.isAuthenticated ? this.logout.bind(this) : this.login.bind(this)}
            user={this.state.user} />
          <Container>
            {error}
            <Route exact path="/"
              render={(props) =>
                <Welcome {...props}
                  isAuthenticated={this.state.isAuthenticated}
                  user={this.state.user}
                  authButtonMethod={this.login.bind(this)} />
              } />
            <Route exact path="/calendar"
              render={(props) =>
                <Calendar {...props}
                  showError={this.setErrorMessage.bind(this)} />
              } />
            <div>{JSON.stringify(this.state.user.accessToken)}</div>
            <hr></hr>
          </Container>
        </div>
      </Router>
    );
  }
}

I get the error:

`The provided value for the input parameter 'scope' is not valid. The scope 'https://management.azure.com/user_impersonation openid profile' does not exist.

{"errorCode":"invalid_scope","errorMessage":"The provided value for the input parameter 'scope' is not valid. The scope 'https://management.azure.com/user_impersonation openid profile' does not exist.","name":"ServerError"}`

1
The scope should only be management.azure.com/user_impersonation,no openid profileTony Ju
You can read my answer again.Tony Ju
The code doesn't add openid and profile so I don't know where those are getting picked up.DFBerry

1 Answers

0
votes

The scope is not correct. There is no scope called user.impersonation. It should be https://management.azure.com/user_impersonation.

Also, you can not have one token with multiple audiences. https://management.azure.com is for azure resources. user.read(https://graph.microsoft.com/User.Read) is for Azure AD resources.

You should call acquireTokenSilent method twice for different audiences.

var accessToken = await this.userAgentApplication.acquireTokenSilent({
        scopes: config.scopes
      });

Update:

enter image description here