How to get the organization (tenant) id from user profile using the Microsoft Graph API
Asked Answered
W

4

19

I'm creating an add-in that I to sell using organizational licenses.

I have implemented an authentication scheme on the add-in. I'm currently asking for User.Read scope for a sure authenticating using and Azure v2 endpoint. To get the user's information I'm querying

https://graph.microsoft.com/v1.0/me

To properly test for the user's license I need extract the user's organization's identification. However, the user information I receive from the Grah request is increadibly lean. For an AAD account the schema looks something like:

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

If I use

https://graph.microsoft.com/BETA/me

I get more information, but nothing that helps me pin down a unique id on the user's organization.

Is there a different scope I need to use to get information for the user's organization? And if there is not, can I rely on parsing the domain name from the user's email as a unique id for the user's organization? Do I need to query a different API?

Update: the OAuth response

In case it helps, after the user authenticates with AD, I receive the following response:

{
    access_token: "eyJ0eXAiOiJKV1QiLCJub25jZSI6IkFRQUJBQUFBQUFEWDhHQ2k2SnM2U0s4MlRzRDJQYjdyN1VLTzdJSDJSLWpTcmpScU9..."
    expires_at: Fri May 18 2018 07: 18: 42 GMT - 0400(Eastern Daylight Time) {}
    expires_in: "3599"
    provider: "Microsoft"
    scope: "https://graph.microsoft.com/User.Read"
    session_state: "012f4565-31bb-..."
    state: "259309..."
    token_type: "Bearer"
}

Update: The full AD response using https://graph.microsoft.com/BETA/me

{
    @odata.context: "https://graph.microsoft.com/beta/$metadata#users/$entity",
        accountEnabled: true,
        ageGroup: null,
        assignedLicenses: [],
        assignedPlans: [],
        businessPhones: [],
        city: null,
        companyName: null,
        consentProvidedForMinor: null,
        country: null,
        deletedDateTime: null,
        department: null,
        deviceKeys: [],
        displayName: "FirstName LastName",
        employeeId: null,
        givenName: "FirstName",
        id: "ebdcf715-43c5-4f48-ad0d-b798a3330849",
        imAddresses: [],
        jobTitle: null,
        legalAgeGroupClassification: null,
        mail: "[email protected]",
        mailNickname: "FirstName.LastName",
        mobilePhone: null,
        officeLocation: null,
        onPremisesDomainName: "COMPANYDOMAIN.COM",
        onPremisesExtensionAttributes: {
            …
        },
        onPremisesImmutableId: "...RVWAty...",
        onPremisesLastSyncDateTime: "2018-05-10T18:13:45Z",
        onPremisesProvisioningErrors: [],
        onPremisesSamAccountName: "FILastName",
        onPremisesSecurityIdentifier: "...-21-1412366426-...",
        onPremisesSyncEnabled: true,
        onPremisesUserPrincipalName: "[email protected]",
        passwordPolicies: "DisablePasswordExpiration",
        passwordProfile: null,
        postalCode: null,
        preferredDataLocation: null,
        preferredLanguage: null,
        provisionedPlans: [],
        proxyAddresses: [],
        refreshTokensValidFromDateTime: "2018-05-10T17:54:45Z",
        showInAddressList: null,
        state: null,
        streetAddress: null,
        surname: "LastName",
        usageLocation: "US",
        userPrincipalName: "[email protected]",
        userType: "Member"
}

Update: Decoding access_token with jwt.ms

{
  "typ": "",
  "nonce": "",
  "alg": "",
  "x5t": "",
  "kid": "iBjL1Rcqzhiy4fpxIxdZqohM2Yk"
}.{
  "aud": "",
  "iss": "",
  "iat": "",
  "nbf": "",
  "exp": "",
  "acr": "",
  "aio": "",
  "amr": [
    "pwd"
  ],
  "app_displayname": "",
  "appid": "",
  "appidacr": "",
  "family_name": "",
  "given_name": "",
  "ipaddr": "",
  "name": "",
  "oid": "",
  "onprem_sid": "",
  "platf": "",
  "puid": "",
  "scp": "",
  "sub": "",
  "tid": "",
  "unique_name": "",
  "upn": "",
  "uti": "",
  "ver": "1.0"
}.[Signature]
Wince answered 18/5, 2018 at 10:48 Comment(8)
After the user logs in, you should get an Id token. That contains the tenant Id.Symposium
@Symposium do you mean the access_token? How do I extract the tenant id from it? Maybe you'd like to post a full answer with the details?Wince
I mean the id_token, is it not available to you in this case?Symposium
@Symposium I updated the answer with the OAuth payload. You can now see the schema for the full workflow. AD users do have a longer profile, but no tenant_id, that I can seeWince
If you decode the access token at jwt.ms do you see user info there?Symposium
I updated with the decoded token. Perhaps, "tid" may be the thing we are looking for?Wince
Alright, cool, well if nothing else works you can get the tid from the token. That is the orgamization id.Symposium
Really? Sweet! Much thanks for the education. If you write this up in an answer, I'll be happy to mark it as such. Also, any documentation to where I can reference all the token params?Wince
S
6

If nothing else works, you can decode the access token and get the tid claim. That is the id for the Azure AD tenant.

You can find the documentation for the claims in tokens from here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims

For example, here is what it says for tid:

An immutable, non-reusable identifier that identifies the directory tenant that issued the token. You can use this value to access tenant-specific directory resources in a multi-tenant application. For example, you can use this value to identify the tenant in a call to the Graph API.

Symposium answered 18/5, 2018 at 14:11 Comment(0)
B
21

This seems to work

GET https://graph.microsoft.com/v1.0/organization 

The id property is the Tenant Id

Grahp Explorer link

Brazilin answered 21/7, 2020 at 12:39 Comment(2)
well this worked, mind sharing the documentation for the request body you have used please?Basilica
It's a GET request. There is no body. Will update the answerBrazilin
S
16

Now this may not be using the graph API directly , but makes it extremely simple to obtain the tenant id of an organization. Just do a GET to "https://login.microsoftonline.com/{yourdomainname}/.well-known/openid-configuration". The structure returned will have the tenant id. Try this url for example in your browser: https://login.microsoftonline.com/microsoft.com/.well-known/openid-configuration.

Slumberous answered 28/8, 2018 at 9:35 Comment(1)
Perfect for first asking for a domain, checking it and finally ask for admin grants. otherwise I would have to do two authentications for my daemon app (Also see github.com/Azure-Samples/ms-identity-aspnet-daemon-webapp )Piraeus
S
6

If nothing else works, you can decode the access token and get the tid claim. That is the id for the Azure AD tenant.

You can find the documentation for the claims in tokens from here: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-token-and-claims

For example, here is what it says for tid:

An immutable, non-reusable identifier that identifies the directory tenant that issued the token. You can use this value to access tenant-specific directory resources in a multi-tenant application. For example, you can use this value to identify the tenant in a call to the Graph API.

Symposium answered 18/5, 2018 at 14:11 Comment(0)
M
0

I used the URL @ben suggested

"https://login.microsoftonline.com/{yourdomainname}/.well-known/openid-configuration"

The tenant_id is not an actual field; you need to parse from one of the URLs. I used "issuer": "https://sts.windows.net/{yourTenantIdIsHere}/",

or "token_endpoint": "https://login.microsoftonline.com/{yourTenantIdIsHere}/oauth2/token",

In Python you can parse like this

import json

# Sample JSON string
json_str = '{"issuer": "https://sts.windows.net/59ea4f14-dba2-4123-b73d-100c40a34608/"}'

# Parse the JSON string into a Python dictionary
data = json.loads(json_str)

# Extract the 'issuer' URL
issuer_url = data['issuer']

# Split the URL by '/' and filter out empty strings
parts = [part for part in issuer_url.split('/') if part]

# The tenant ID should be the last part of the URL
tenant_id = parts[-1]

print("Tenant ID:", tenant_id)
Maelstrom answered 2/2 at 1:41 Comment(0)

© 2022 - 2024 — McMap. All rights reserved.