Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

Microsoft Azure AD graph API: How do I retrieve a user's email address?

I am able to get access to a user's accessToken, and am making a call to GET https://graph.microsoft.com/v1.0/me with an Authorization: Bearer <token> header.

However, in the response body I'm getting something like this:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users",
    "value": [
        {
            "givenName": "Foo",
            "surname": "Bar",
            "displayName": "Foo Bar",
            "id": "b41efha115adcca29",
            "userPrincipalName": "[email protected]",
            "businessPhones": [],
            "jobTitle": null,
            "mail": null,
            "mobilePhone": null,
            "officeLocation": null,
            "preferredLanguage": null
        }
    ]
}

The mail property is null, and the userPrincipalName in this response body happens to the be the user's email address. However, there's this from Microsoft's docs:

Although the UPN and email share the same format, the value of the UPN for a user might or might not be the same as the email address of the user.

When initiating the login request of the user, we're requesting for the "user.read" and "email" scopes. We're using the MSAL.js library to obtain the access token, and our code reads something like this:

login (): ng.IPromise<IMicrosoftOAuthResponse> {
  const prom = this.userAgentApplication.loginPopup(["user.read", "email"])
    .then((idToken) => {
      return this.userAgentApplication.acquireTokenSilent(["user.read", "email"]);
    })
    .then((accessToken) => {
      // at this point, we have the accessToken and make a call to the graph api
    });
  return this.$q.when(prom);
}

How do I get the actual email address of the user here?

like image 694
bioball Avatar asked Nov 17 '17 06:11

bioball


2 Answers

The mail property is set in one of 2 ways:

  1. It's been set on on-premises AD, and then synchronized to Azure AD using AD Connect
  2. The cloud user has been assigned an Office 365 license (and a mailbox), at which point the mail property is set for this licensed user.

If the user does not have an O365 mailbox/license, you could also search for the user by userPrincipalName, displayName, etc. $filter supports the OR operator.

Hope this helps,

like image 133
Dan Kershaw - MSFT Avatar answered Oct 11 '22 18:10

Dan Kershaw - MSFT


Even though this is an old question, I thought I would share my solution to getting the email of a signed-in user. Be aware that this solution requires access to the user's id_token.

The response from calling the /me endpoint looks as follows:

Object {
  "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users/$entity",
  "businessPhones": Array [],
  "displayName": "John Doe",
  "givenName": "John",
  "id": "xxxxxx",
  "jobTitle": null,
  "mail": null,
  "mobilePhone": null,
  "officeLocation": null,
  "preferredLanguage": null,
  "surname": "Doe",
  "userPrincipalName": "johndoe_gmail.com#EXT#@johndoegmail.onmicrosoft.com",
}

As we can see, the mail property of this response is null. I am however able to get the user email by decoding the jwt id_token passed along with the access_token when calling the /token endpoint.

By applying the decodeJwtToken() function (attached at the end of this post) to the id_token, I am able to get the user email from the result

Object {
  "aio": "xxxxxxxx",
  "amr": Array [
    "pwd",
  ],
  "aud": "xxxxx",
  "email": "[email protected]",
  "exp": xxxxxx,
  "family_name": "Doe",
  "given_name": "John",
  "iat": xxxxxx,
  "idp": "live.com",
  "ipaddr": "xxx.xxx.xxx.xxx",
  "iss": "https://sts.windows.net/xxxx/",
  "name": "John Doe",
  "nbf": xxxx,
  "nonce": "xxx",
  "oid": "xxxxxxx",
  "sub": "xxxxx",
  "tid": "xxxxx",
  "unique_name": "live.com#[email protected]",
  "uti": "xxxx",
  "ver": "1.0",
}

The decoding function looks as follows:

decodeIdToken = (token) => {
  var base64Url = token.split('.')[1];
  var base64 = base64Url.replace(/-/g, '+').replace(/_/g, '/');
  var jsonPayload = decodeURIComponent(Buffer.from(base64, 'base64').toString().split('').map(function(c) {
      return '%' + ('00' + c.charCodeAt(0).toString(16)).slice(-2);
  }).join(''));

  return JSON.parse(jsonPayload);
};
like image 1
sondrelv Avatar answered Oct 11 '22 20:10

sondrelv