17

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: "First.LastName@COMPANYDOMAIN.COM",
  mobilePhone: null,
  officeLocation: null,
  preferredLanguage: null,
  surname: "LastName",
  userPrincipalName: "FILastName@COMPANYDOMAIN.COM"
}

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: "FirstName.LastName@COMPANYDOMAIN.COM",
        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: "FILastName@COMPANYDOMAIN.COM",
        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: "FILastName@COMPANYDOMAIN.COM",
        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]
Marc LaFleur
  • 31,987
  • 4
  • 37
  • 63
seebiscuit
  • 4,905
  • 5
  • 31
  • 47
  • After the user logs in, you should get an Id token. That contains the tenant Id. – juunas May 18 '18 at 11:10
  • @juunas 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? – seebiscuit May 18 '18 at 12:00
  • I mean the id_token, is it not available to you in this case? – juunas May 18 '18 at 12:56
  • @juunas 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 see – seebiscuit May 18 '18 at 13:42
  • If you decode the access token at https://jwt.ms do you see user info there? – juunas May 18 '18 at 13:46
  • I updated with the decoded token. Perhaps, `"tid"` may be the thing we are looking for? – seebiscuit May 18 '18 at 14:00
  • Alright, cool, well if nothing else works you can get the `tid` from the token. That is the orgamization id. – juunas May 18 '18 at 14:02
  • 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? – seebiscuit May 18 '18 at 14:03

3 Answers3

20

This seems to work

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

The id property is the Tenant Id

Grahp Explorer link

Alon Catz
  • 2,417
  • 1
  • 19
  • 23
13

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.

Ben
  • 161
  • 1
  • 5
  • 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 https://github.com/Azure-Samples/ms-identity-aspnet-daemon-webapp ) – Thomas Williams Jul 02 '20 at 13:44
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.

juunas
  • 54,244
  • 13
  • 113
  • 149