Configuring Options and MaxRetries for Graph Java Client

We have a great Java Graph SDK sample in our tutorial document located here.  Pay special attention to the versions as not having the correct version of the prerequisites will give you difficulty!

The sample will run a console application and uses the Oauth2 Device Code flow for authentication.  Once it compiles and runs, you will get a message like this for the device code flow sign in: 

“To sign in, use a web browser to open the page https://microsoft.com/devicelogin and enter the code EDBKDR6TA to authenticate.” 

You can ctrl+click on the url in Visual Studio Code to open the link and you will then have a prompt to enter the highlighted device code ( your code will be different than mine ).  You only have a few minutes to complete this step before the code expires.  Once the code is accepted, you will then get a login screen to enter your credentials.  The app is polling a certain endpoint to determine if you have signed in ( see the related device code flow documentation ).  Once you are signed in, you will then get a menu in the console app screen to run some basic commands, including pressing 0 to exit.

The sample is straight forward however, occasionally, you may need to add additional header options to the request and this is not straight forward.  In my example, I am going to add sorting to the header options for the calendar events request and I also want to set the max retry count.  By default, the current versions of the Microsoft Graph clients, including the Java client, will now automatically handle the 429 error ( too many requests ) for you and retry the request up to 3 times, using the recommended wait period in the retry-after value that comes back in the response header.  After the 3rd 429 response, an exception will be thrown for that request.  For more information on 429 errors, see the Microsoft Graph throttling guidance page.  

So, in my example, I would like to increase the retry count to 5.  Please be advised that increasing the retry count will increase the wait duration for each attempt.  Setting to a number like 10 may cause a very long retry period for that last request as each attempt will increase the wait.

In the project code, under src\main\java\com\contoso is the Graph.java file.  This is where the sample has its graph requests.  There is a method for getEvents and this is the request I would like to add sorting and also change the default maxRetries option.  Since this is also a request to an Office 365 resource, I will be asking for the ImmutableId instead of the standard id ( not required but to show how this is done ). The standard request from the sample ( I did add a select parameter to only return the fields I wanted ):

public static List<Event> getEvents(String accessToken) {
ensureGraphClient(accessToken);

// Use QueryOption to specify the $orderby query parameter
final List<Option> options = new LinkedList<Option>();
// Sort results by createdDateTime, get newest first
options.add(new QueryOption("orderby", "createdDateTime DESC"));

// GET /me/events
IEventCollectionPage eventPage = graphClient
.me()
.events()
.buildRequest(options)
.select("id,subject,organizer,start,end")
.get();

return eventPage.getCurrentPage();
}

My modified version:

public static List<Event> getEvents(String accessToken) {
ensureGraphClient(accessToken);

// Use QueryOption to specify the $orderby query parameter
final List<Option> options = new LinkedList<Option>();
// Sort results by createdDateTime, get newest first
options.add(new QueryOption("orderby", "createdDateTime DESC"));

//options.add(new QueryOption("filter", "organizer eq 'ray@myemail.com'"));

options.add(new HeaderOption("maxRetries","5"));
options.add(new HeaderOption("Prefer","IdType='ImmutableId'"));

// GET /me/events
IEventCollectionPage eventPage = graphClient
.me()
.events()
.buildRequest(options)
.select("id,subject,organizer,start,end")
.get();

return eventPage.getCurrentPage();
}

This will now return sorted results, increase the retry attempts and also return an immutable id instead of the standard id.  I also have a commented out statement showing how you can filter the request, if needed.  See this related method I created to get a single CalendarEvent based on the immutable id:

public static Event getCalenderEvent(String accessToken, String id){
final List<Option> options = new LinkedList<Option>();
options.add(new HeaderOption("maxRetries","5"));
options.add(new HeaderOption("Prefer","IdType='ImmutableId'"));

Event event = graphClient
.me()
.events(id)
.buildRequest()
.select("id,subject,organizer,start,end")
.get();
return event;
}

Our GitHub for this library is located here.  Documentation for the additional request options is here.

Some notes regarding the Microsoft Graph Subscription and webhook

For certain Azure AD resources or Directory Objects you can use Microsoft Graph to create Subscriptions to receive change notifications event. Below are some notes to be aware of:

Subscription object

Lifetime

Each subscription object (except for Security alerts) is only valid for 3 days maximum, so make sure you renew the subscription before it expires to keep receiving change notifications. See https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-1.0 for more detail on maximum subscription length per resource type.

Subscription Update

The only updated operation on updating a subscription is to extend its expiry time. See https://docs.microsoft.com/en-us/graph/api/subscription-update?view=graph-rest-1.0&tabs=http for more info.

notificationUrl property

This is the webhook URL the client application sends to Microsoft Graph when creating a subscription. Microsoft Graph Service calls back to this endpoint to verify its validity during Subscription creation and uses this endpoint to notify the client applications about event changes. This callback mechanism is not built for a sophisticated scenario where the notificationUrl is protected by some additional security measure. Below are a couple of things that can lead to failure when setting up change notification URL:

  • the notification URL requires a client certificate
  • the notification URL requires some form of authentication
  • there is a problem with a certificate used to bind the notification URL, for instance untrusted CA, invalid hostname, or expired certificate, etc…

Change notification for User and Group Objects

The User and Group objects only support ‘updated’ and ‘deleted’ change notification (see changeType propery in https://docs.microsoft.com/en-us/graph/api/resources/subscription?view=graph-rest-1.0). When an object is created, Azure AD does not create the object along with its properties at the same time. It creates the skeleton object first and then updates the object with certain properties. For this reason, the client application can receive ‘updated’ event for newly created objects.

When an object is deleted, Azure AD moves the object to a ‘soft’ delete state. After 30 days the object is then permanently deleted (‘hard’ delete). An object in the ‘soft’ delete state can be recovered. Microsoft Graph sends an ‘updated’ event when an object is moved to the ‘soft’ delete state and then ‘deleted’ event when it’s permanently deleted. To query for all the supported objects in the ‘soft’ delete state, refer to List Deleted items documentation and refer Restore Deleted objects documentation for how to recover them.

See https://docs.microsoft.com/en-us/graph/known-issues#change-notifications for more info.

Receiving error WIF10201: No valid key mapping found for securityToken

Customer has an ASP.Net MVC application using both WS-Federation OWIN middleware and Windows Identity Foundation (WIF) to authenticate to Azure AD. The application works fine initially and then fails with the following error:

Error Details:
Server Error in ‘/’ Application.
WIF10201: No valid key mapping found for securityToken: ‘System.IdentityModel.Tokens.X509SecurityToken’ and issuer: ‘https://sts.windows.net/<Directory ID>/’.

Description: An unhandled exception occurred during the execution of the current web request. Please review the stack trace for more information about the error and where it originated in the code.

Exception Details: System.IdentityModel.Tokens.SecurityTokenValidationException: WIF10201: No valid key mapping found for securityToken: ‘System.IdentityModel.Tokens.X509SecurityToken’ and issuer: ‘https://sts.windows.net/<Directory ID>/’.

Source Error:
An unhandled exception was generated during the execution of the current web request. Information regarding the origin and location of the exception can be identified using the exception stack trace below.

Stack Trace:
[SecurityTokenValidationException: WIF10201: No valid key mapping found for securityToken: ‘System.IdentityModel.Tokens.X509SecurityToken’ and issuer: ‘https://sts.windows.net/<Directory ID>/’.]
System.IdentityModel.Tokens.Saml2SecurityTokenHandler.ValidateToken(SecurityToken token) +873
System.IdentityModel.Tokens.SecurityTokenHandlerCollection.ValidateToken(SecurityToken token) +73
System.IdentityModel.Services.TokenReceiver.AuthenticateToken(SecurityToken token, Boolean ensureBearerToken, String endpointUri) +110
System.IdentityModel.Services.WSFederationAuthenticationModule.SignInWithResponseMessage(HttpRequestBase request) +527
System.IdentityModel.Services.WSFederationAuthenticationModule.OnAuthenticateRequest(Object sender, EventArgs args) +381
System.Web.SyncEventExecutionStep.System.Web.HttpApplication.IExecutionStep.Execute() +141
System.Web.HttpApplication.ExecuteStepImpl(IExecutionStep step) +48
System.Web.HttpApplication.ExecuteStep(IExecutionStep step, Boolean& completedSynchronously) +71
Version Information: Microsoft .NET Framework Version:4.0.30319; ASP.NET Version:4.7.3535.0

Root Cause:

Windows Identity Foundation uses the certificate thumbprint(s) in the web.config file (shown below) to verify the signature of the token returned from Azure AD upon successful sign in.

  <issuerNameRegistry type="System.IdentityModel.Tokens.ValidatingIssuerNameRegistry, 
    System.IdentityModel.Tokens.ValidatingIssuerNameRegistry">
    <authority name="https://sts.windows.net/<Directory ID>/">
      <keys>
        <add thumbprint="C142E..." />
        <add thumbprint="8BA94..." />
        <add thumbprint="D92E1..." />
      </keys>
      <validIssuers>
        <add name="https://sts.windows.net/<Directory ID>/" />
      </validIssuers>
    </authority>
  </issuerNameRegistry>

Note: The certificate thumbprints above can be generated from either the discovery keys endpoint ( https://login.microsoftonline.com/<Directory ID>/discovery/keys) or the federationmetadata endpoint ( https://login.microsoftonline.com/<Directory ID>/federationmetadata/2007-06/federationmetadata.xml)

The above error occurs when none of these certificate thumbprints match the one used by Azure AD to sign the token. Because Azure AD implements a signing key rollover mechanism for security purpose, the certificate used to sign the token changes over time. This key rollover causes the initial certificate thumbprints configured in the web.config file to become invalid, hence leading to the error.

Resolution:

You can either update the certificate thumbprints in the web.config file manually or use the programmatic approach documented in Vittorio Bertocci’s blog https://www.cloudidentity.com/blog/2013/04/02/auto-update-of-the-signing-keys-via-metadata/

using System;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
using System.Configuration;
using System.IdentityModel.Tokens;

namespace MvcNewVINR
{
    public class MvcApplication : System.Web.HttpApplication
    {
        protected void RefreshValidationSettings()
        {
            string configPath = 
              AppDomain.CurrentDomain.BaseDirectory + "\\" + "Web.config";
            string metadataAddress = 
              ConfigurationManager.AppSettings["ida:FederationMetadataLocation"];
            ValidatingIssuerNameRegistry.WriteToConfig(metadataAddress, configPath);
        }
        protected void Application_Start()
        {
            AreaRegistration.RegisterAllAreas();
            WebApiConfig.Register(GlobalConfiguration.Configuration);
            FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
            RouteConfig.RegisterRoutes(RouteTable.Routes);
            BundleConfig.RegisterBundles(BundleTable.Bundles);
            RefreshValidationSettings();
        }
    }
}

Note: The ‘ida:FederationMetadataLocation’ application setting above is the URL https://login.microsoftonline.com/<Directory ID>/federationmetadata/2007-06/federationmetadata.xml

The above certificate thumbprint update code runs when the Application is restarted or when its Application Pool gets recycled. For more information on Application Pool recycling refer to https://docs.microsoft.com/en-us/iis/configuration/system.applicationhost/applicationpools/add/recycling/.

If the application runs into an Access Denied error writing to the web.config file, see https://blogs.msdn.microsoft.com/cie/2016/01/15/error-access-to-the-path-esitesroot0web-config-is-denied-when-storing-azure-ads-public-key-in-web-config-of-an-azure-cloud-services-application/ for how to resolve the issue.

References:

Graph Client Authentication Provider

The Graph Client Authentication Providers allows for each authentication to the graph endpoint implementing a variety of OAUTH2 flows.  I will demonstrate the use of this library in c# code based on this GitHub. Previously, you had to build your own Authentication Provider ( see my creation of the client credentials provider in a vb.net application here ) .  This library will allow you to use the following flows:

  • Confidential Client Providers
    • Authorization code
    • Client Credentials
    • On behalf of
  • Public Client Providers
    • Device Code
    • Integrated windows authentication
    • Interactive Authentication ( This blog demonstrates this flow )

The GitHub readme does show all those basic flow implementations.

Note: The library is in preview release only and is subject to change without notice.

I used the same app registration I created demonstrating how to use MSAL here.

Start a new c# console application and install the following Nuget Packages.

Nuget Packages

  • Microsoft.Graph – Also installs:
    • Microsoft.Graph.Core
    • Newtonsoft.Json
    • System.Buffers
    • System.Diagnostics.DiagnosticSource
    • System.Memory
    • System.Numerics.Vectors
    • System.Runtime.CompilerServices.Unsafe
    • System.ValueTuple
  • Microsoft.Graph.Auth ( Preview Version – you must check the “Include Prerelease” box to find ) – Also installs:
    • System.Security.Principal
    • Microsoft.identity.Client

I only have one code file – Program.cs ( the default code file for a console app ).  Here is the code:

using Microsoft.Graph;
using Microsoft.Graph.Auth;
using Microsoft.Identity.Client;
using System;
using System.Threading.Tasks;

namespace GraphClient
{
    class Program
    {
        static String client_id = "{client_id}";
        static String tenant_id = "{tenant_id}";
        static String[] scopes = { "https://graph.microsoft.com/.default" };

        static IPublicClientApplication _pca = null;
        private static IPublicClientApplication Pca
        {
            get
            {
                if(_pca == null )
                {
                    _pca = PublicClientApplicationBuilder
                        .Create( client_id )
                        .WithTenantId( tenant_id )
                        .Build();
                }

                return _pca;
            }
        }

        static InteractiveAuthenticationProvider _authProvider = null;
        private static InteractiveAuthenticationProvider AuthProvider
        {
            get
            {
                if(_authProvider == null )
                {
                    _authProvider = new InteractiveAuthenticationProvider( Pca, scopes );
                }
                return _authProvider;
            }
        }

        static GraphServiceClient _graphClient = null;
        private static GraphServiceClient GraphClient
        {
            get
            {
                if(_graphClient == null )
                {
                    _graphClient = new GraphServiceClient( AuthProvider );
                }
                return _graphClient;
            }
        }

        static void Main(string[] args)
        {
            Get_Me().Wait();

            Console.WriteLine( $"\nPress any key to close..." );
            Console.ReadKey();
        }

        static async Task Get_Me()
        {
            User user = null;

            user = await GraphClient.Me.Request().GetAsync();

            Console.WriteLine( $"Display Name = {user.DisplayName}\nEmail = {user.Mail}" );
        }
    }
}

 

To implement one of the other flows, you may be modifying the Client type ( line 15 in the code file above ) based on whether or not it is a confidential client or a public client ( see list at the beginning of this post ) and the property for it just below, and line 39 ( code file above) where we are setting the auth provider as well as the underlying member for that. So, for example, a confidential client using the client credentials flow would look like this in the same code file:

        static String client_id = "{client_id}";
        static String tenant_id = "{tenant_id}";
        static String client_secret = "{client_secret}";
        static String[] scopes = { "https://graph.microsoft.com/.default" };
        static IConfidentialClientApplication _pca = null;
        private static IConfidentialClientApplication Pca
        {
            get
            {
                if(_pca == null )
                {
                    _pca = ConfidentialClientApplicationBuilder
                        .Create( client_id )
                        .WithTenantId( tenant_id )
                        .WithClientSecret( client_secret )
                        .Build();
                }
                return _pca;
            }
        }
        static ClientCredentialProvider _authProvider = null;
        private static ClientCredentialProvider AuthProvider
        {
            get
            {
                if(_authProvider == null )
                {
                    _authProvider = new ClientCredentialProvider( Pca );
                }
                return _authProvider;
            }
        }

 

receiving error AADSTS50105: The signed in user ‘{EmailHidden}’ is not assigned to a role for the application

Problem:

A tenant admin may receive the error “AADSTS50105: The signed in user ‘{EmailHidden}’ is not assigned to a role for the application…” when clicking on the “Grant Admin Consent” button in Azure AD’s App Registration portal as shown in the screen shot below:

Why is this happening?

This error typically happens when the Enterprise Application portion (or Service Principal) of the registered application has the setting ‘User Assignment Required’ set to Yes

So how do I resolve this issue?

You can follow the steps below to work around this issue:

  1. Change the ‘User assignment required’ to No and save the change
  2. Go back to the App Registration portal and perform Granting Admin consent to the application. It should work this time
  3. Set the ‘User assignment required’ back to Yes again

Exploring AzureServiceTokenProvider class with Azure Key Vault and Azure SQL

The AzureServiceTokenProvider class from the Nuget package Microsoft.Azure.Services.AppAuthentication can be used to obtain an access token.  When running in Azure it can also utilize managed identities to request an access token.  In this post I’ll focus on using this class to get an access token for Azure Key Vault.  Keep in mind that you can also use this class to obtain an access token for any Azure resources integrated with Azure Active Directory.  Below are some code sample showing a couple of ways to use this class to get an access token and call Azure Key Vault:

Note: Before trying out the following, you should make sure the signed-in principal (application or user) has access to Azure Key Vault.  See my previous blog post for more info on this and the Azure Key Vault documentation.  For example, I have configured my Azure Key Vault to allow access to the following 2 users and 2 Applications in the Azure Key Vault’s Access Policies blade:

using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider.KeyVaultTokenCallback));
var secret = await kv.GetSecretAsync("https://blogkv123.vault.azure.net", "SQLPassword").ConfigureAwait(false);

or

using Microsoft.Azure.Services.AppAuthentication;
using Microsoft.Azure.KeyVault;
// ...
var azureServiceTokenProvider = new AzureServiceTokenProvider();
string accessToken = await azureServiceTokenProvider.GetAccessTokenAsync("https://vault.azure.net");
using (var client = new HttpClient())
{
    var url = " https://blogkv123.vault.azure.net/secrets/SQLPassword";
    client.DefaultRequestHeaders.Add("Authorization", "Bearer " + accessToken);
    var response = await client.GetAsync(url);
    // Parse JSON response.
    ....
}

In the above code sample, we are using the default constructor of the AzureServiceTokenProvider class by instantiating an instance of this class without providing a connection string (which usually contains tenant and application ID info).  Later on, we will talk about how use a connection string with this class.  The default constructor tries to get an access token from the following methods:

  1. Managed Identities using client credentials grant flow – this option only works if the code is run in Azure environment, for example Azure Virtual Machine , Azure App Service, etc…

Note:  this option requires that the Azure resource has support for Managed Identities and Managed identity is enabled. For a list of Azure resources that support managed identities see https://docs.microsoft.com/en-us/azure/active-directory/managed-identities-azure-resources/services-support-managed-identities

  1. Visual Studio
  2. Azure CLI or Active Directory Integrated Authentication

If all 3 methods fail, you can get an Exception like the following:

Exception thrown: ‘Microsoft.Azure.Services.AppAuthentication.AzureServiceTokenProviderException’ in System.Private.CoreLib.dll

Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/<tenant ID>. Exception Message: Tried the following 3 methods to get an access token, but none of them worked.

Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/<tenant ID>. Exception Message: Tried to get token using Managed Service Identity. Unable to connect to the Managed Service Identity (MSI) endpoint. Please check that you are running on an Azure resource that has MSI setup.

Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/<tenant ID>. Exception Message: Tried to get token using Visual Studio. Access token could not be acquired.

Parameters: Connection String: [No connection string specified], Resource: https://vault.azure.net, Authority: https://login.windows.net/<tenant ID>. Exception Message: Tried to get token using Azure CLI. Access token could not be acquired. ERROR: No subscription found. Run ‘az account set’ to select a subscription.

For option 1, either System MSI or User-Assigned MSI can be used.  Refer to the appropriate Azure Resource documentation for how to do this.  As mentioned above, remember to give MSI access to Azure Key Vault.

Below are a couple of screen shots showing how I configured my Azure VM MSI using either System MSI or User-Assigned MSI (see more info about User MSI Connection String later on in this blog):

A good way to test if MSI is enabled for the Azure VM is by running the following command from the Powershell window in the VM:

Invoke-WebRequest -Uri ‘http://169.254.169.254/metadata/identity/oauth2/token?api-version=2018-02-01&resource=https%3A%2F%2Fvault.azure.net’ -Headers @{Metadata=”true”}

For Azure App Service, the MSI setting is similar in the Azure App Service’s Identity blade:

To test for MSI in Azure App Service you can create a file called ‘test.ps1’ with the following Powershell code and run the file from the Azure Web App’s kudu console:

$resourceURI = "https://vault.azure.net"
$tokenAuthURI = $env:MSI_ENDPOINT + "?resource=$resourceURI&api-version=2017-09-01"
$tokenResponse = Invoke-RestMethod -Method Get -Headers @{"Secret"="$env:MSI_SECRET"} -Uri $tokenAuthURI
$accessToken = $tokenResponse.access_token
Echo $accessToken

And you should see output like the following:

For option 2, refer to the documentation at https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication for instruction under “Authentication with Visual Studio” section,

For option 3, make sure you have Azure CLI v2.0.12 or later installed (to find out what version you have installed run az –version command)

You will need to sign in from the az cli window first before running your application code.  You can either sign in by a user account or by an application (service principal) account

Sign in with a user account:

  1. Run az login to sign in
  2. Run az account get-access-token –resource https://vault.azure.net to verify you can get an access token for Azure Key Vault

Sign in with an Application (or Service Principal):

az login –service-principal -u <app id> –password <app secret> –tenant <tenant-id> –allow-no-subscriptions

Using AzureServiceTokenProvider with your own Application…

So far, we have only discussed calling AzureServiceTokenProvider class with no argument (the default constructor).  When deploying the code to run in Azure service, it mainly relies on using Managed Identity (MSI) configured for that service to get an access token.  Both the Azure CLI and Visual Studio method are intended only for local development effort so they are not suitable for production environment.  The problem with using MSI is that the Azure resource must support MSI and not every resource has this capability. 

Besides using Managed Identities, you can also configure your own Application Registration and use that application for the AzureServiceTokenProvider class to acquire an access token.  This can be accomplished using a Connection String to specify our tenant and application info detail.  See code example below for how to do this:

var azureServiceTokenProvider1 = new AzureServiceTokenProvider(“RunAs=App;AppId=<Application ID>;TenantId=<Tenant Name>.onmicrosoft.com;AppKey=<App Secret>”);

Note: For User-Assigned Managed Identity, use the connection string as followed:

var azureServiceTokenProvider1 = new AzureServiceTokenProvider(“RunAs=App;AppId=<User-Assigned MSI Application ID>”);

Set up the Connection String via an environment variable:

Instead of passing in the connection string in the AzureServiceTokenProvider class, you can create an environment variable (via Visual Studio Project Properties) called AzureServicesAuthConnectionString and set the connection string as its value:

Once this environment variable is set, you can call the AzureServiceTokenProvider with its default constructor:

var azureServiceTokenProvider = new AzureServiceTokenProvider();

For debugging purpose, I use the following code in my controller to get more info about the token or Exception Info:

public async System.Threading.Tasks.Task<ActionResult> Privacy()
        {
            // Instantiate a new KeyVaultClient object, with an access token to Key Vault
            var azureServiceTokenProvider1 = new AzureServiceTokenProvider();
             var azureServiceTokenProvider2 = new AzureServiceTokenProvider("RunAs=App;AppId=<Application ID>");
            // var azureServiceTokenProvider2 = new AzureServiceTokenProvider();
            // string accessToken = await azureServiceTokenProvider2.GetAccessTokenAsync("https://management.azure.com/").ConfigureAwait(false);
            // var azureServiceTokenProvider1 = new AzureServiceTokenProvider("RunAs=App;AppId=<Application ID>;TenantId=<Tenant Name>.onmicrosoft.com;AppKey=<Application Secret>");
            try
            {
                var kv = new KeyVaultClient(new KeyVaultClient.AuthenticationCallback(azureServiceTokenProvider1.KeyVaultTokenCallback));
                var secret = await kv.GetSecretAsync("https://blogkv123.vault.azure.net", "SQLPassword").ConfigureAwait(false);
                ViewBag.Secret = $"Secret: {secret.Value}";
            }
            catch (Exception ex)
            {
                ViewBag.Error = $"Something went wrong: {ex.Message}";
                Debug.WriteLine(ex.Message);
            }
            ViewBag.Principal = azureServiceTokenProvider1.PrincipalUsed != null ? $"Principal Used: {azureServiceTokenProvider1.PrincipalUsed}" : string.Empty;

            try
            {
                string accessToken = await azureServiceTokenProvider2.GetAccessTokenAsync("https://vault.azure.net").ConfigureAwait(false);
                ViewBag.AccessToken = $"Access Token: {accessToken}";
            }
            catch (Exception e)
            {
                Debug.WriteLine(e.Message);
            }
            // Debug.WriteLine(ToString());
            return View();
        }

And here is the code for the View Page:

@{
    ViewData["Title"] = "Privacy Policy";
}
<h1>@ViewData["Title"]</h1>

<div>

    <div>@ViewBag.AccessToken</div>

    <div>@ViewBag.Secret</div>

    <div>@ViewBag.Principal</div>

    <div>@ViewBag.Error</div>



</div>

<p>Use this page to detail your site's privacy policy.</p>

Beware of Token Lifetime

Usually Managed Identities (System or User-Assigned) access token is valid up to 8 hours. Make sure your application code checks for token validity and renewing the token as needed to avoid unpredictable behavior. A typical problem that can occur is intermittent Database connectivity issue, for instance the one documented here. An Azure App Service using Managed Identity access token to connect to an Azure SQL Database with the following Entity Framework code:

public class MyDbContext : DbContext
    {
        public MyDbContext(DbContextOptions options) : base(options)
        {
            var conn =  (SqlConnection) this.Database.GetDbConnection();
            conn.AccessToken = (new AzureServiceTokenProvider()).GetAccessTokenAsync("https://database.windows.net/").Result;
        }
        public DbSet<Item> Items { get; set; }
    }

//Startup.cs
services.AddDbContext<ToDoDbContext>(options =>
{
    var connStr = Configuration["ConnectionString"];
    options.UseSqlServer(connStr);
});

The code works fine initially but after some time it starts throwing the following exception:

System.Data.SqlClient.SqlException (0x80131904): Login failed for user 'NT AUTHORITY\ANONYMOUS LOGON'.
   at System.Data.SqlClient.SqlInternalConnectionTds..ctor(DbConnectionPoolIdentity identity, SqlConnectionString connectionOptions, SqlCredential credential, Object providerInfo, String newPassword, SecureString newSecurePassword, Boolean redirectedUserInstance, SqlConnectionString userConnectionOptions, SessionData reconnectSessionData, Boolean applyTransientFaultHandling, String accessToken)
   at System.Data.SqlClient.SqlConnectionFactory.CreateConnection(DbConnectionOptions options, DbConnectionPoolKey poolKey, Object poolGroupProviderInfo, DbConnectionPool pool, DbConnection owningConnection, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionFactory.CreatePooledConnection(DbConnectionPool pool, DbConnection owningObject, DbConnectionOptions options, DbConnectionPoolKey poolKey, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionPool.CreateObject(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   at System.Data.ProviderBase.DbConnectionPool.UserCreateRequest(DbConnection owningObject, DbConnectionOptions userOptions, DbConnectionInternal oldConnection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, UInt32 waitForMultipleObjectsTimeout, Boolean allowCreate, Boolean onlyOneCheckConnection, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionPool.TryGetConnection(DbConnection owningObject, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionFactory.TryGetConnection(DbConnection owningConnection, TaskCompletionSource`1 retry, DbConnectionOptions userOptions, DbConnectionInternal oldConnection, DbConnectionInternal& connection)
   at System.Data.ProviderBase.DbConnectionInternal.TryOpenConnectionInternal(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.ProviderBase.DbConnectionClosed.TryOpenConnection(DbConnection outerConnection, DbConnectionFactory connectionFactory, TaskCompletionSource`1 retry, DbConnectionOptions userOptions)
   at System.Data.SqlClient.SqlConnection.TryOpen(TaskCompletionSource`1 retry)
   at System.Data.SqlClient.SqlConnection.Open()
   at Microsoft.EntityFrameworkCore.Storage.RelationalConnection.OpenDbConnection(Boolean errorsExpected)
ClientConnectionId:a3a93762-2a94-473b-ba4b-9c3da55d669f
Error Number:18456,State:1,Class:14

The root cause of the above error is due to expired token being used. The Application code above only retrieves the access token once during DB Context initialization and keeps using the same token beyond its lifetime. Check out the github issue above (https://github.com/dotnet/efcore/issues/19808) for ideas on how to work around this issue.

References:

https://stackoverflow.com/questions/14627399/setting-authorization-header-of-httpclient

https://www.rahulpnath.com/blog/authenticating-with-azure-key-vault-using-managed-service-identity/

https://docs.microsoft.com/en-us/samples/azure-samples/app-service-msi-keyvault-dotnet/keyvault-msi-appservice-sample/

https://docs.microsoft.com/en-us/samples/azure-samples/app-service-msi-keyvault-dotnet/keyvault-msi-appservice-sample/

https://docs.microsoft.com/en-us/azure/app-service/overview-managed-identity?context=azure%2Factive-directory%2Fmanaged-identities-azure-resources%2Fcontext%2Fmsi-context&tabs=powershell

https://docs.microsoft.com/en-us/azure/key-vault/service-to-service-authentication


Update 4/25/2020 – Added information about token life time and SQL Connection error

Graph Query to get B2B user using User Principal Name (UPN)

If you are using Microsoft Graph API Query to fetch B2B user using UPN, and experiencing below shown error:

Query: https://graph.microsoft.com/v1.0/users/example_gmail.com#EXT#@example.onmicrosoft.com

Response:

{

  ‘error’: {

    ‘code’: ‘Request_ResourceNotFound’,

    ‘message’: ‘Resource ‘*******’ does not exist or one of its queried reference-property objects are not present.’,

    ‘innerError’: {

      ‘request-id’: ‘8f390389-b9c6-4f6b-93ba-c531b3d7d595’,

      ‘date’: ‘2019-12-05T23:55:40’

    }

  }

}

Well, here is the fix:

You will need to encode the URL request. Reason, you are experiencing the error is because, ‘#‘ sign is treated as a special character in the URL. So ‘#‘ must be URL-encoded otherwise everything after it will be treated as a fragment and will not get sent over the wire. If you replace the ‘#‘ sign with ‘%23’, it should work. 

Working Query Format : https://graph.microsoft.com/v1.0/users/example_gmail.com%23EXT%23@example.onmicrosoft.com

Using the GraphClient SDK in a VB.Net Console Application

In this blog post, I will show you how to use the GraphClient in a VB.Net application. You will need to create an app registration for this project. I used the exact same app registration that I used in my previous VB.Net blog post here.

SDK Reference: https://docs.microsoft.com/en-us/graph/sdks/sdks-overview?view=graph-rest-1.0

I am using Visual Studio 2019 in this example. To begin, please start a VB.Net Console application and then install the following nuget packages:

Install nuget packages:

This will also install

  • Microsoft.Graph.Core
  • Microsoft.Json
  • System.Buffers
  • System.Diagnostics.DiagnosticSource
  • System.Numerics.Vectors
  • System.Runtime.CompilerServices.Unsafe
  • System.ValueTuple

I will be creating a custom Authentication Provider for this project. The reference for this:

Creating an Authentication Provider Reference: https://docs.microsoft.com/en-us/graph/sdks/choose-authentication-providers?tabs=CS

I am creating a custom authentication provider class called “InteractiveAuthentionProvider.vb”

There is a preview component you can install that has these classes built in so that you don’t have to implement your own class:  https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth  That is currently in preview mode so I will blog about this later.

I have the custom InteractiveAuthenticationProvider class that will be passed to GraphClient.  In Module1, you will need to enter your client ID and tenant ID.  Refer to my previous blog post here about setting up the app registration.

Imports System.Net.Http
Imports Microsoft.Graph
Imports Microsoft.Identity.Client

''' <summary>
''' This is a custom class to implement the IAuthenticationProvider class.  You can implement the new preview Microsoft.Graph.Auth which has 
''' built in classes for the authentication providers:  https://github.com/microsoftgraph/msgraph-sdk-dotnet-auth
''' </summary>
Public Class InteractiveAuthenticationProvider
    Implements IAuthenticationProvider

    Private Property Pca As IPublicClientApplication
    Private Property Scopes As List(Of String)

    Private Sub New()
        'Intentionally left blank to prevent empty constructor
    End Sub

    ''' <summary>
    ''' The constructor for this custom implementation of the IAuthenticationProvider
    ''' </summary>
    ''' <param name="pca">The public client application -- in this example, I have pre-set this up prior to creating the auth provider</param>
    ''' <param name="scopes">The scopes for the request</param>
    Public Sub New(pca As IPublicClientApplication, scopes As List(Of String))
        Me.Pca = pca
        Me.Scopes = scopes
    End Sub

    ''' <summary>
    ''' This is the required implmentation of the AuthenticateRequestAsync Method for the IAuthenticationProvider interface
    ''' </summary>
    ''' <param name="request">The current graph request being made</param>
    ''' <returns></returns>
    Public Async Function AuthenticateRequestAsync(request As HttpRequestMessage) As Task Implements IAuthenticationProvider.AuthenticateRequestAsync
        Dim accounts As IEnumerable(Of IAccount)
        Dim result As AuthenticationResult = Nothing

        accounts = Await Pca.GetAccountsAsync()
        Dim interactionRequired As Boolean = False

        Try
            result = Await Pca.AcquireTokenSilent(Scopes, accounts.FirstOrDefault).ExecuteAsync()
        Catch ex1 As MsalUiRequiredException
            interactionRequired = True
        Catch ex2 As Exception
            Console.WriteLine($"Authentication error: {ex2.Message}")
        End Try

        If interactionRequired Then
            Try
                result = Await Pca.AcquireTokenInteractive(Scopes).ExecuteAsync()
            Catch ex As Exception
                Console.WriteLine($"Authentication error: {ex.Message}")
            End Try
        End If

        Console.WriteLine($"Access Token: {result.AccessToken}{Environment.NewLine}")
        Console.WriteLine($"Graph Request: {request.RequestUri}")
        'You must set the access token for the authorization of the current request
        request.Headers.Authorization = New Headers.AuthenticationHeaderValue("Bearer", result.AccessToken)
    End Function
End Class

 

Imports Microsoft.Graph
Imports Microsoft.Identity.Client
Imports Newtonsoft.Json

Module Module1

    Private Const client_id As String = "{client id -- also known as application id}" '<-- enter the client_id guid here
    Private Const tenant_id As String = "{tenant id or name}" '<-- enter either your tenant id here
    Private authority As String = $"https://login.microsoftonline.com/{tenant_id}"

    Private _scopes As New List(Of String)
    Private ReadOnly Property scopes As List(Of String)
        Get
            If _scopes.Count = 0 Then
                _scopes.Add("User.read") '<-- add each scope you want to send as a seperate .add
            End If
            Return _scopes
        End Get
    End Property

    ''' <summary>
    ''' underlaying variable for the readonly property PCA which returns an instance of the PublicClientApplication
    ''' </summary>
    Private _pca As IPublicClientApplication = Nothing
    Private ReadOnly Property PCA As IPublicClientApplication
        Get
            If _pca Is Nothing Then
                _pca = PublicClientApplicationBuilder.Create(client_id).WithAdfsAuthority(authority).Build()
            End If
            Return _pca
        End Get
    End Property

    ''' <summary>
    ''' The underlaying variable for the Authentication Provider readonly property
    ''' </summary>
    Private _authProvider As InteractiveAuthenticationProvider = Nothing
    Private ReadOnly Property AuthProvider As InteractiveAuthenticationProvider
        Get
            If _authProvider Is Nothing Then
                _authProvider = New InteractiveAuthenticationProvider(PCA, scopes)
            End If
            Return _authProvider
        End Get
    End Property

    ''' <summary>
    ''' The underlaying variable for the graphClient readonly property
    ''' </summary>
    Private _graphClient As GraphServiceClient = Nothing
    Private ReadOnly Property GraphClient As GraphServiceClient
        Get
            If _graphClient Is Nothing Then
                _graphClient = New GraphServiceClient(AuthProvider)
            End If
            Return _graphClient
        End Get
    End Property


    Sub Main()

        Get_Me()


        Console.ReadKey()

    End Sub

    Private Async Sub Get_Me()
        Dim user As User

        'Using the Select to get the employeeId as the v1 endpoint does not automatically return that
        user = Await GraphClient.Me().Request().Select("displayName,employeeid").GetAsync()
        Console.WriteLine($"User = {user.DisplayName}, employeeid = {user.EmployeeId}")


    End Sub

End Module

In summary, this example of using the GraphClient library in a VB.Net application demonstrates that, although different than the c# examples we have in our docs, there really isn’t much to using the different language.

Using filter query on mail-related attributes in Microsoft Graph

The user object has email addresses stored in a couple of properties: the mail and otherMails properties. Both of these properties can be used to search for certain users having the desired email addresses. Here is an example of how to use the filter query to search for user using mail property:

beta endpoint:

GET https://graph.microsoft.com/beta/users?$filter=mail eq ‘john@contoso.com’

v1.0 endpoint:

GET https://graph.microsoft.com/v1.0/users?$filter=mail eq ‘john@contoso.com’

Unlike the mail attribute (string-type property), the otherMails attribute is a Collection of strings so we need to use the Any() function to do any searching on this property. Below are a couple of examples using the v1.0 endpoint:

GET https://graph.microsoft.com/v1.0/users?$filter=otherMails/any(x:x eq ‘xxx@abc.com’)

GET https://graph.microsoft.com/v1.0/users?$filter=otherMails/any(x:startswith(x, ‘xxx’))

Note: not every user object property supports filter query. Check the documentation for the resource to see which property is “filterable”.

In the example below from the User object, the licenseAssignmentStates property does not support filter while the mail property does

Segment Users in Azure AD

If you have been using Microsoft Graph API to add or modify users in Azure Active Directory (Azure AD) you may have noticed that when you create a new user it lives with all the other users, some of which may have nothing to do with your application. Ideally, you may want a sub-directory or business unit of sorts. Fortunately, there are ways to segment these users in a more practical way. There are currently two ways to do this: Groups and Administrative Units.

GROUPS

Groups are pretty straight forward and the name says it all. You can create a basic group using the Azure AD portal. See here for detailed instructions on how to create a basic group and add members.

ADMINISTRATIVE UNITS

An administrative unit is an Azure AD resource that can be a container for other Azure AD resources. In this preview release, these resources can be only users. For example, an administrative unit-scoped User account admin can update profile information, reset passwords, and assign licenses for users only in their administrative unit.

You can use administrative units to delegate administrative permissions over subsets of users and applying policies to a subset of users. You can use administrative units to delegate permissions to regional administrators or to set policy at a granular level.

Check out these demo scripts to help get you started and the MS Graph API administrativeUnit resource type. You can find more information about Administrative Units here.

NOTE:

ADMINISTRATIVE UNITS IS CURRENTLY IN PREVIEW AND CAN ONLY BE DONE VIA POWERSHELL CMDLETS OR WITH MS GRAPH BETA REST ENDPOINT AT THE MOMENT.