What I have so far
I'm using two different Node.js projects but I would like to put them together.
- The first uses interactive login (code submitted in browser) then finds all my (signed-on user) tenants.
- 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).
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"}`