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

6

13

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?

Crissman answered 17/11, 2017 at 6:16 Comment(7)
Upon a quick reading of the docs for userEntity, userPrincipalName seems to always be the email of user. Main gist of it: The UPN is an Internet-style login name for the user based on the Internet standard RFC 822. By convention, this should map to the user's email name. The general format is "alias@domain". For work or school accounts, the domain must be present in the tenant's collection of verified domains.Materfamilias
Did you discover any userPrincipalName which does not have email instead?Materfamilias
From their 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." learn.microsoft.com/en-us/azure/active-directory/connect/…Crissman
I thought its recommended to keep the UPN same as email. But it seems more like a general practice instead. This blog also suggests that many applications are actually using the UPN as email.Materfamilias
Yeah. It seems like the mail property should be the user's actual email address, but it's returning null.Crissman
When using the Bearer token from my own OAuth I got <username>_live.com#EXT#@<username>live.onmicrosoft.com. Using the same query (same headers etc.) just with a Bearer token from Graph Explorer I got just the email (<username>@live.com) as UPN. I tried playing with different permissions, but this did not change the returned value.Jackfish
Additional note: I also found other differences: Using my OAuth token I got my accounts businessPhones and a UUID4 as the id-value (my users Object ID in AAD). Using Graph Explorers token the businessPhones array was empty and the id had a 16-digit value I could not find anywhere in AAD.Jackfish
S
6

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,

Sewn answered 24/11, 2017 at 22:5 Comment(5)
Hey Dan, thanks for the response. If the "mail" property is null, does this mean that it does not exist, then? Furthermore: the approach we're taking right now to obtain the email address is to first check the "mail" property, and defer to "userPrincipalName" otherwise. Is that advisable?Crissman
Sorry for the delay. Let me ask something I should have asked earlier. What are you trying to do with the user's mail address? Are you planning to send email notifications to them for example? In this case, mail is not set because either the user does not have a mailbox from Office 365, or this property was not set on-premises (and sync'd through AD Connect).Sewn
No problem. We're using the email address to de-duplicate and match on users that might already exist in our accounts system. For example: it's possible that a user has already created an account via email/password, and has come back later and signed in via MS OAuth. We also ideally have their email so we can present them with the option to sign up for newsletters, send transactional emails, etc.Crissman
So if the "mail" property is not set, does this mean Microsoft does not know the user's email address?Crissman
Any update on this? I'm also having the same problem @DanKershaw-MSFTCasta
S
1

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);
};
Sammer answered 28/12, 2019 at 13:25 Comment(1)
I've been doing something similar, although there are scenarios where it won't work. For instance, when using MSAL and refreshing the ID Token, MSAL returns an ID token without the email field (because of the way MSAL is written). Requesting refresh tokens manually or with other libraries, this usually works fine.Taught
W
0

For me, https://graph.microsoft.com/v1.0/users/<userid>/authentication/emailMethods endpoint worked. For this, the client must have UserAuthenticationMethod.Read.All permission. One can find more documentation here.

Sample reponse:

{
    "@odata.context": "https://graph.microsoft.com/v1.0/$metadata#users('8225f1cd-8025-4f8b-bf94-c595e02e3403')/authentication/emailMethods",
    "value": [
        {
            "id": "3ddfcfc8-9383-446f-83cc-3ab9be4be18f",
            "emailAddress": "[email protected]"
        }
    ]
}
Webber answered 23/8, 2022 at 18:7 Comment(0)
R
0
var user = graphServiceClient.Users[UserId].Request().Select(x=>x.Identities).GetAsync();
var email = user.Identities.FirstOrDefault().IssuerAssignedId;
Revolution answered 10/11, 2022 at 14:45 Comment(0)
C
0

I ran into this recently myself, getting an error when trying to access https://graph.microsoft.com/v1.0/me:

requests.exceptions.HTTPError: 403 Client Error: Forbidden for url: https://graph.microsoft.com/v1.0/me | Error Message: Exception of type 'Microsoft.Fast.Profile.Core.Exception.ProfileAccessDeniedException' was thrown.

How I got around it:

I used the returned id_token included in the access token. You might not always receive this, for example the MSAL library may not always return this if simply refreshing an access token, so you should probably have a copy of it stored somewhere if you don't extract and save the id_token's information when you first get it.

The id_token is base64 encoded per the standard, which for debugging you can use this tool to figure out the right details: https://jwt.io/#debugger

I used this library to reliably decode the token: https://github.com/jpadilla/pyjwt/

pip install PyJWT
from jwt import decode

id_token = ''  # Replace this with the id_token provided to you when getting the access token
decoded_id_token = decode(id_token, algorithms=['RS256'], options={'verify_signature': False})
user_email = decoded_id_token.get('email') if decoded_id_token.get('email') else decoded_id_token.get('preferred_username')
Choriocarcinoma answered 9/3, 2023 at 20:18 Comment(0)
B
-1

create a trial account in microsoft office 365 business premium using the below link: https://signup.microsoft.com/Signup?OfferId=467eab54-127b-42d3-b046-3844b860bebf&dl=O365_BUSINESS_PREMIUM&culture=en-IN&country=IN&ali=1

Follow the steps while creating the account. It will allow us to create users in office 365. These users are like internal users of an organization. Now open azure portal with the above credential. All users of office 365 will be imported in active azure directory.

Now register an application with Read users basic profile delegated permission in active azure directory. note down client id , client secret and tenant domain to get access token for service to service authentication. This access token can be used to get user records which will be containing mail field as [email protected]

Bitner answered 26/6, 2018 at 5:55 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.