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.
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.
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.
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.
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.
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 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:
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:
Change the ‘User assignment required’ to No and save the change
Go back to the App Registration portal and perform Granting Admin consent to the application. It should work this time
Set the ‘User assignment required’ back to Yes again
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:
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…
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:
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:
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:
Run az login to sign in
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):
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.
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
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.
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.
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
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.