Adding AAD Service Principal to the Company Administrator Role using the AAD PowerShell Module

When creating a new Azure Active Directory application, developers may run into a a problem when calling the AAD Graph API where they lack the correct permissions to call the APIs they want when calling in the App Only Flow (Client Credentials Flow).

Is this message familiar to you?

"odata.error":{
    "code":"Authorization_RequestDenied",
    "message":{
        "lang":"en","value":"Insufficient privileges to complete the operation."
    }
}

The correct thing to do would be to try and investigate the permissions you have granted to your application, but there are some APIs which are not even supported through the permissions publicly exposed by the AAD Graph API. Maybe you just want to overcome this error for the time being and continue testing your end to end experience.

Using the AAD PowerShell Module, you can:

  • Give your application full access to the Graph API in the context of my tenant.

or

  • Grant your application permissions to my tenant which are not currently supported with the permissions exposed by the AAD Graph API.

“How?” you might ask. Well, you can elevate the level of access an Application has in your tenant by adding the service principal of that application to the Company Administrator Directory Role. This will give the Application the same level of permissions as the Company Administrator, who can do anything. You can follow these same instructions for any type of Directory Role depending on the level of access you want to give to this application.

Note that this will only affect the access your app has in your tenant.
Also you must already be a Company Administrator of the tenant to follow these instructions.

 

In order to make the change, you will need to install the Azure Active Directory PowerShell Module.

Once you have the module installed, authenticate to your tenant with your Administrator Account:

Connect-MSOLService

Then we need to get the Object ID of both the Service Principal we want to elevate, and the Company Administrator Role for your tenant.

Search for Service Principal by App ID GUID:

$sp = Get-MsolServicePrincipal -AppPrincipalId <App ID GUID>

Search for Directory Role by Name

$role = Get-MsolRole -RoleName "Company Administrator"

Now we can use the Add-MsolRoleMember command to add this role to the service principal.

Add-MsolRoleMember -RoleObjectId $role.ObjectId -RoleMemberType ServicePrincipal -RoleMemberObjectId $sp.ObjectId

To check everything is working, lets get back all the members of the Company Administrator role:

Get-MsolRoleMember -RoleObjectId $role.ObjectId

You should see your application in that list, where RoleMemberType is ServicePrincipal and DisplayName is the name of your application.

Now your application should be able to perform any Graph API calls that the Company Administrator could do, all without a user signed-in, using the Client Credential Flow.

Let me know if this helps!

Does Company ‘X’ have an Azure Active Directory Tenant?

One of the cool things about the Open ID Configuration endpoint is that it not only tells us random facts about the tenant, but it confirms that the tenant exists! Make sure to check out my last post to learn more about this. Using some clever scripting and this endpoint behavior, we could probably figure out which companies have an Azure Active Directory Tenant. Let’s try that!

$csv = Import-Csv -Path .\input.csv
$output = @()

foreach ($line in $csv)
{
    $companyname = $line.CompanyName
    $companynameencoded = [System.Net.WebUtility]::UrlEncode($companyname)

    $GoogleURI = 'https://www.google.com/search?q=' + $companynameencoded + '&amp;btnI'
 
    try { 
        $GoogleResult = Invoke-WebRequest -Uri $GoogleURI
        $CompanyURI = ([System.Uri]$GoogleResult.BaseResponse.ResponseUri).Host.split('.')[-2..-1] -join '.'
    } catch {
        write-host $_.Exception
        $CompanyURI = "error"
    }

    $OpenIDConfigURL = 'https://login.microsoftonline.com/' + $CompanyURI + '/.well-known/openid-configuration'

    try {
        $OpenIDResult = (Invoke-WebRequest -Uri $OpenIDConfigURL).StatusCode
    } catch {
        $OpenIDResult = $_.Exception.Response.StatusCode.value__
    }

    if ($OpenIDResult -eq 200) {
        $tenant = $true
    } else {
        $tenant = $false
    }

    $result = [pscustomobject]@{
        CompanyName = $companyname.ToString()
        HomepageURI = $CompanyURI.ToString()
        OpenIDResult = $OpenIDResult.ToString()
        HasTenant = $tenant.ToString()
    }

    Write-Host $result
    $output += $result 
}

$output | Export-Csv -Path output.csv -NoTypeInformation

So in summary what does this script do?

We take a CSV which lists a bunch of Company Names. We then do a Google search, and go to the first result (‘I’m Feeling Lucky’). We assume the first result is the homepage of that company, and the domain they would use for their tenant. We pull out the host name, and then check it against the Open ID Configuration endpoint. If we get a valid response from the endpoint, then we say that they have a tenant! Otherwise, we say they do not have a tenant.

One thing to note about these results is that when we get a result that says the company has a tenant, we are nearly 100% correct in that fact. However, if we say that a company does not have a tenant, we are not necessarily correct. It is possible that the google result did not point to their actual domain name, or they are using a different domain name for their AAD Tenant. If you wanted to do this really robustly, you would probably want to get a better source for your domain names than automated google search results. You might want to also look at other combinations like “<companyname>.onmicrosoft.com”, however we are doing just rough estimates here.

So lets look at the result for the Fortune 500. A quick Google search later, and I have a CSV with a list of all the Company Names for all 500 companies. Running it through this script, I find that 417, or 83.4% of companies have AAD, which is just a little off from Microsoft’s public claim of 85%. Not bad for a quick and dirty script!

Secret APIs in Azure Active Directory and Azure Resource Manager

Have you ever wondered what the Tenant ID for Microsoft (microsoft.com) or any other domain is? Have you ever wondered how you can find the right Tenant ID to sign in a user given their Azure Subscription ID?

Oh, you haven’t? Well that is certainly more reasonable than the fact that I have; but, if for some reason you are asking the same questions as I am, let me tell you about some of the “secret APIs” that are available to answer those questions.

Getting the Tenant ID for a Verified Domain in Azure Active Directory

Azure Active Directory tenants have a special type of domain called a ‘verified domain’. Verified domains are what they sound like, domains which a user has proven they own through DNS verification. These domains are unique across all tenants, and can act as a alternative domain to the initial domain given to all tenants (*.onmicrosoft.com).

While authentication and even the AAD Graph API both support the use of these domains for referencing a tenant, not all APIs support this. Sometimes you might need to convert the tenant domain to a Tenant ID… but how?

Well known open id config

Check out the specification here. This Open ID configuration endpoint is required for all Open ID Providers, which AAD is one of. Let’s take a look at what the response looks like for the Microsoft tenant using the verified domain ‘microsoft.com’:

https://login.microsoftonline.com/microsoft.com/.well-known/openid-configuration

{"authorization_endpoint":"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/authorize","token_endpoint":"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/token","token_endpoint_auth_methods_supported":["client_secret_post","private_key_jwt"],"jwks_uri":"https://login.microsoftonline.com/common/discovery/keys","response_modes_supported":["query","fragment","form_post"],"subject_types_supported":["pairwise"],"id_token_signing_alg_values_supported":["RS256"],"http_logout_supported":true,"frontchannel_logout_supported":true,"end_session_endpoint":"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/logout","response_types_supported":["code","id_token","code id_token","token id_token","token"],"scopes_supported":["openid"],"issuer":"https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/","claims_supported":["sub","iss","cloud_instance_name","cloud_graph_host_name","aud","exp","iat","auth_time","acr","amr","nonce","email","given_name","family_name","nickname"],"microsoft_multi_refresh_token":true,"check_session_iframe":"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/oauth2/checksession","userinfo_endpoint":"https://login.microsoftonline.com/72f988bf-86f1-41af-91ab-2d7cd011db47/openid/userinfo","tenant_region_scope":"WW","cloud_instance_name":"microsoftonline.com","cloud_graph_host_name":"graph.windows.net"}

I honestly have never used most of the data in this JSON, and I am not really sure where it gets used… BUT, you will notice that all of the various authentication endpoints now have a Tenant ID GUID rather than a domain name! This tells us two things:

  1. The Tenant ID for Microsoft.com is 72f988bf-86f1-41af-91ab-2d7cd011db47
  2. (maybe this is obvious already…. but) Microsoft has a tenant!

Now the second realization is kind of a super-set of the first, but it makes me think about something else cool we can do. What if we wanted to get a count and see which companies have an Azure Active Directory Tenant? As long as we know their Domain Name, we should be able to use this endpoint to confirm if a tenant exists! I will save this exploration for my next blog post.

Get the Tenant ID for a Specific Azure Subscription ID

The world of Azure Subscriptions is one of the most complicated spaces that shouldn’t be complicated. Depending on how you start using Azure, you may never even know that you have an Azure Active Directory Tenant. You just have your Live ID, which you use to sign on to the Azure Portal, and from there you can access your Subscription ID!  You can’t even use the ‘common’ endpoint with Live IDs on AAD V1, so your lack of knowledge can be really painful here for app developers. We need your Tenant ID to know the right login endpoint to send you to. Luckily, we can find that using helpful error messages from Azure Resource Manager! All we need is an application for which we can get a token to Azure Resource Manager in the

We can easily execute this plan using my PowerShell Scripts. Update the scripts to have the following configuration:

  • Pick any Tenant ID and Application Information relative to that tenant
  • Set Resource ID to “https://management.azure.com/”
  • Create a variable “$subscriptionId” and set it to the Azure Subscription ID you are looking to investigate.
  • Set up the REST call like this:
try {
    Invoke-RestMethod -Method Get -Uri ("{0}/subscriptions/{1}?api-version=2016-06-01" -f $resourceId, $subscriptionId) -Headers $headers
} catch {
    Write-Host $_.ErrorDetails.Message
}

Hmm… why would I be catching an error? Well let’s run it and see what gets outputted:

{"error":{"code":"InvalidAuthenticationTokenTenant","message":"The access token is from the wrong issuer 'https://sts.windows.net/4a4d599f-e69d-4cd8-a9e1-9882ea340fb5/'. It must match the tenant 'https://sts.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47/' associated with this subscription. Please use the authority (URL) 'https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47' to get the token. Note, if the subscription is transferred to another tenant there is no impact to the services, but information about new tenant could take time to propagate (up to an hour). If you just transferred your subscription and see this error message, please try back later."}}

Right in the error they tell us the correct tenant for this Subscription ID!

Please use the authority (URL) ‘https://login.windows.net/72f988bf-86f1-41af-91ab-2d7cd011db47

This really is a “secret API”, and we can use it to consistently get back the right tenant for a user, as long as they know what their Azure Subscription is.

Azure AD Authentication with PowerShell and ADAL

In the 3 years I spent on the Azure AD team, I learned a number of useful ‘tricks’ to make my job (and usually the jobs of others) a ton easier. However, if I had to pick just one trick to share to others trying to learn, it would probably be the PowerShell scripts I wrote to quickly get an access token to Azure Active Directory and then call AAD protected APIs like the AAD Graph API.

In general, authentication is hard, and requires way more set up than should be needed for simple testing. To get AAD authentication working on other platforms, you may need to write a ton of code, compile it, or even publish it to the web. With these scripts, you can get authentication and REST API calls done with as little as 13 lines of PowerShell. Running the code is instant, and modifying the REST calls or even the authentication parameters takes seconds rather than minutes.

How to get the samples

You can find all the basic scripts I have written on GitHub here:

https://github.com/shawntabrizi/Azure-AD-Authentication-with-PowerShell-and-ADAL

I provide different scripts for different authentication flows:

  1. Authorization Code Grant Flow for Confidential Client
  2. Native Client Authentication
  3. Client Credential Flow
    1. Using Application Key
    2. Using Client Certificate

Each script ends with a REST API call to get the list of Users in your tenant using the AAD Graph API. You should be able to do this with any application because it uses the “Sign in and read basic profile” permission which is assigned to all AAD Applications by default.

Note that to get these samples running, you will need to add the .NET dlls for ADAL v2 into the ADAL folder. You can find those files on NuGet.

Why it is so darn useful

So now that you have the scripts downloaded, and hopefully working, let me illustrate to you just a few of the different scenarios where I have used this tool to greatly simplify my work.

Verifying Token Claims

So many errors in AAD app development come from some sort of wrong setting, which may manifest itself in your access token. You might want to check the ‘scp’ claims to see if your app has the right permissions. You might want to check the ‘tid’ claim to make sure that you are getting a token to the right tenant! Or even the ‘aud’ claim to make sure the token is for the correct resource. You can simply pump in the settings for your application into the appropriate PowerShell script, run the script, and you will get a .txt file with your access token in it. Then you can pop that JWT token into a JWT decoder like the one I created… and viola! There are your claims, and it took literally seconds.

Making quick REST API calls

Another thing that comes up very often around work is just pulling random data from AAD. Let’s say that someone wants to know the settings of a certain Application Object, Service Principal, or even User. You may be able to do this with tools like the Graph Explorer, but what about some more complicated queries, or ones that you want to download to a file for later? Or how about simply wanting to test that YOUR app can make those queries rather than the Graph Explorer app. Not to mention the fact that you can call ANY AAD protected API, not just the AAD Graph API with these scripts. Simply update the Invoke-RestMethod command and bam, results will be saved into a .json file!

Making scripted REST API calls

Maybe you are still not convinced that these scripts are useful. Most of what I showed above can be done if you want to use multiple other tools. However, I challenge you to find a quicker way to create “scripted” REST API calls. What do I mean by that? Lets say you wanted to pull a list of all the users in your company. Well the AAD Graph API can return at most 999 results in a single call, so you probably want to create a loop that iterates over the paged results that the Graph API returns. This is SIMPLE!

Here is the loop I wrote to solve this exact problem:

$result = Invoke-RestMethod -Method Get -Uri ('{0}/{1}/users/?api-version=1.6&amp;amp;amp;$top=999' -f $resourceId,$tenantId) -Headers $headers
$count = 0
$result.value | Export-Csv ([String]$count + "_" +$output) -Encoding UTF8

while (($result.'odata.nextLink' -split 'skiptoken=')[1] -ne $null)
{
  $skiptoken = ($result.'odata.nextLink' -split 'skiptoken=')[1]
  Write-Host ('{0}/{1}/users/?api-version=1.6&amp;amp;amp;$top=999&amp;amp;amp;$skiptoken={2}' -f $resourceId,$tenantId,$skiptoken)

  try
  {
    $result = Invoke-RestMethod -Method Get -Uri ('{0}/{1}/users/?api-version=1.6&amp;amp;amp;$top=999&amp;amp;amp;$skiptoken={2}' -f $resourceId,$tenantId,$skiptoken) -Headers $headers
    $count += 1
    $result.value | Export-Csv ([String]$count + "_" + $output) -Encoding UTF8
  }
  catch
  {
    Write-Host "Error with Invoke Rest Method!"
    Write-Host $result.'odata.nextLink'
    break
  }
}

The result is a folder of CSV files all numbered and ready to be merged. If the script fails at some point (like if I lose an internet connection), I can use the outputted ‘odata.nextLink’ and just pick up where I left off. I couldn’t imagine doing this any other way for my needs.

Convinced?

I hope that you too will be able to find this little tool helpful for your day to day needs. Let me know if you find some other unconventional uses for this!