[email protected] from npm install msalIn my Vue.js SPA app I am authenticating the user using msal as it is done in the SPA example: I'm a little bit confused about using scopes and what happens when extra scopes are required later on in the app's life-cycle with authRedirect .
When using authPopup and clicking the button Read mail the user is requested to agree with the extra scopes in a nice popup window. But when using authRedirect the button for Read mail is not visible in the example and so I wonder how this would be handled.
Do we need to logout the user in that case and let him login again with the extra scopes? Also, the scopes here concerned are for MSGraph not for AD authentication, so requesting all scopes on login might not work as it's a different endpoint. On top of this there's only a maximum of 3 scopes you can ask at once I believe.
I'm sorry, I don't really understand how this part is done. Thank you for having a look.
authPopup

authRedirect

```javascript
function getTokenRedirect(request, endpoint) {
return myMSALObj.acquireTokenSilent(request, endpoint)
.then((response) => {
if (response.accessToken) {
callMSGraph(endpoint, response.accessToken, updateUI);
}
})
.catch(error => {
// fallback to interaction when silent call fails
return myMSALObj.acquireTokenRedirect(request)
});
}
function callMSGraph(endpoint, token, callback) {
const headers = new Headers();
const bearer = Bearer ${token};
headers.append("Authorization", bearer);
const options = { method: "GET", headers: headers };
fetch(endpoint, options)
.then(response => response.json())
.then(response => callback(response, endpoint))
.catch(error => console.log(error))
}
// Add here scopes for id token to be used at MS Identity Platform endpoints.
const loginRequest = { scopes: ["openid", "profile", "User.Read"] };
// Add here scopes for access token to be used at MS Graph API endpoints.
const tokenRequest = { scopes: ["Mail.Read"] };
const graphConfig = {
graphMeEndpoint: "https://graph.microsoft.com/v1.0/me",
graphMailEndpoint: "https://graph.microsoft.com/v1.0/me/messages"
};
function seeProfile() { getTokenRedirect(loginRequest, graphConfig.graphMeEndpoint); }
function readMail() { getTokenRedirect(tokenRequest, graphConfig.graphMailEndpoint); }
````
@DarkLite1 You can include scopes for up to 3 resources in your login request, I am not aware of a specific limit on the number of scopes you can request in a single call. This will allow you to require the user to pre-consent to the scopes needed to run the application. This will also allow you to later acquire the access tokens for these scopes silently. Any scopes you have not consented to later on in the app lifecycle will require an interactive token call and will prompt you for consent. This will behave the same for popup and redirect, the only difference being which window the page is loaded in.
As for the sample, it sounds like there's some logic that's hiding the button which is more of a ui issue than an msal issue. It looks like you've made some changes from the sample you linked above so I would suggest trying the sample as is and then debugging your implementation. If you find a bug with the sample let me know and I'd be happy to get that fixed.
Thank you for the feedback @tnorling , much appreciated as always. I really believe there is a bug in the sample as it's not showing the Read mail button when using the authRedirect method instead of the authPopup. So maybe someone should have a look at that.
Regarding scopes I'm still a bit confused. Consider the following code:
const graphConfig = {
profileUrl: "https://graph.microsoft.com/v1.0/me",
photoUrl: "https://graph.microsoft.com/v1.0/me/photo/$value",
getGroupMemberOfUrl: "https://graph.microsoft.com/v1.0/me/memberOf",
managerUrl: "https://graph.microsoft.com/v1.0/me/manager"
}
// MS Identity Platform endpoints.
const loginRequest = {
scopes: ["openid", "profile", "User.Read"]
}
// MS Graph API endpoints.
const tokenRequest = {
scopes: ["Mail.Read", "Group.Read"]
}
const callGraph = (url, token) => {
return axios.get(url, { headers: { Authorization: `Bearer ${token}` } })
}
export const getTokenPopup = (request) => {
return msal.acquireTokenSilent(request)
.catch(() => msal.acquireTokenPopup(request))
}
export const getTokenRedirect = (request, endpoint) => {
return msal.acquireTokenSilent(request, endpoint)
.catch(() => msal.acquireTokenRedirect(request)) // redirect loses page state
}
const getGraphDetails = async (tokenReq, graphUrl) => {
if (!auth.getAccount()) { throw new Error('no logged on user') }
if (auth.loginMethod === 'popup') {
return auth.getTokenPopup(tokenReq)
.then(response => callGraph(graphUrl, response.accessToken))
}
else {
return auth.getTokenRedirect(tokenReq, graphUrl)
.then(response => callGraph(graphUrl, response.accessToken))
}
}
export const getProfile = () => getGraphDetails(loginRequest, graphConfig.profileUrl)
export const getPhoto = () => getGraphDetails(loginRequest, graphConfig.photoUrl)
export const getMail = () => getGraphDetails(tokenRequest, graphConfig.profileUrl)
export const getGroupMemberOf = () => getGraphDetails(tokenRequest, graphConfig.getGroupMemberOfUrl)
Now when a user logs on to the app he will have to give consent for the scopes ["openid", "profile", "User.Read"]. This works fine for both popup and redirect. Later on in the app the user calls getMail() which uses a different token than the one used for logon. At this point msal checks the cache for the token with that scope and can't find it, so in popup mode it will present a popup to give consent and all is well. But in the redirect mode it will redirect the user to another website, the SPA loses its state and the consent is given. After which the user is navigated back to the app.
getMail but mail wont be retrieved as he navigated away from the page and the callbacks are broken on page reload..msal remembers this and doesn't ask it again. Could that be reset somewhere for testing purposes?The easiest solution would be to request the user for consent on all possible scopes in the app upon login. But as it's still unclear if that is only limited to 3 so I don't know if that's possible or even a good idea. What happens when MSGraph endpoint is offline, might be that the user can't even logon if all scopes are combined in one request.
@DarkLite1 There's a few things to unpack here. First, regarding state of the app, the easiest way to approach this is to implement the handleRedirectCallback method and call your callGraph method if the following conditions are met:
access_tokenUsing the response type will help you differentiate between whether you are returning from a login call vs an access_token call. When a page returns from a redirect, msal will automatically cache the token in your browsers local storage. So if you want to do this a different way, like by calling acquireTokenSilent on page load, you can. acquireTokenSilent will always check the cache first for a token, and then if it doesnt find one or if it finds an expired one it will make a network request for a new token.
Second, regarding consent. Consent is stored on your identity provider. Once a user has given consent to a scope they do not have to give consent again unless it is revoked. This is not something msal handles. This can be reset in the azure app portal.
Third, I think you are conflating scopes and resources. A single resource can have many scopes. MsGraph is an example of this. A login call is limited to 3 resources, an acquireToken call, on the other hand, is limited to 1 resource. There is not a limit I am aware of on how many scopes you can include in a request as long as you don't exceed the resource limit. You can feel free to include all of these in your login call if you want. However, if you want to give someone the possibility to only consent to a few scopes and only use a few of the features on your page you should only include the scopes absolutely necessary to access the page in your login call. Any additional or optional features can be 'unlocked', so to speak, by making a new request. This allows the user to have more control over how much consent they want to give you.
Another thing to note, you are not hitting the MSGraph endpoint when you make your token calls. Rather you are hitting your identity provider who will determine if you have the correct rights and privileges and hand you back an id token, in the case of a login call, or an access token, in the case of an acquireToken call. You will then use this access token when making an API call to MSGraph. If your identity provider is offline, then you would be correct, you would not be able to login. But the number of scopes in the request is irrelevant in this scenario.
@tnorling @DarkLite1 apologies for the UI issue. A PR is opened to resolve that.
@tnorling thanks again for the great clarification. I was indeed mixing up resources with scopes. It's all a lot clearer to me now, so much appreciated for explaining the flow.
The current code works flawlessly so I'll just stick with it instead of implementing the handleRedirectCallback method. The way it's done now gives me more freedom to call getGraphDetails whenever I like. If you do see something wrong, feel free to correct me.
@derisen no worries, we all make mistakes once a while. And if we have great support to help us stand up again it's even better. Thanks guys, stayhomestaysafe