diff --git a/Microsoft.Identity.Web.sln b/Microsoft.Identity.Web.sln
index a073a2192..468f7bd45 100644
--- a/Microsoft.Identity.Web.sln
+++ b/Microsoft.Identity.Web.sln
@@ -164,6 +164,12 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "DaemonConsoleCallingDownstr
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "TokenAcquirerTests", "tests\IntegrationTests\TokenAcquirerTests\TokenAcquirerTests.csproj", "{AB8177BE-8961-4698-B064-42943885D48D}"
EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.GraphServiceClient", "src\Microsoft.Identity.Web.GraphServiceClient\Microsoft.Identity.Web.GraphServiceClient.csproj", "{4DF02DF7-D092-4F45-8892-8A1D3E612706}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Microsoft.Identity.Web.GraphServiceClientBeta", "src\Microsoft.Identity.Web.GraphServiceClientBeta\Microsoft.Identity.Web.GraphServiceClientBeta.csproj", "{608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}"
+EndProject
+Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "GraphServiceClientTests", "tests\IntegrationTests\GraphServiceClientTests\GraphServiceClientTests.csproj", "{F686A507-CAC6-4349-9112-27F5AEFBF12B}"
+EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
@@ -373,6 +379,18 @@ Global
{AB8177BE-8961-4698-B064-42943885D48D}.Debug|Any CPU.Build.0 = Debug|Any CPU
{AB8177BE-8961-4698-B064-42943885D48D}.Release|Any CPU.ActiveCfg = Release|Any CPU
{AB8177BE-8961-4698-B064-42943885D48D}.Release|Any CPU.Build.0 = Release|Any CPU
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706}.Release|Any CPU.Build.0 = Release|Any CPU
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}.Release|Any CPU.Build.0 = Release|Any CPU
+ {F686A507-CAC6-4349-9112-27F5AEFBF12B}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
+ {F686A507-CAC6-4349-9112-27F5AEFBF12B}.Debug|Any CPU.Build.0 = Debug|Any CPU
+ {F686A507-CAC6-4349-9112-27F5AEFBF12B}.Release|Any CPU.ActiveCfg = Release|Any CPU
+ {F686A507-CAC6-4349-9112-27F5AEFBF12B}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
@@ -448,6 +466,9 @@ Global
{D12AF43A-72EC-4459-B6F4-0755190D9222} = {E37CDBC1-18F6-4C06-A3EE-532C9106721F}
{5DE68949-118E-4A2E-A541-8FB5CA030CD6} = {E37CDBC1-18F6-4C06-A3EE-532C9106721F}
{AB8177BE-8961-4698-B064-42943885D48D} = {A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706} = {1DDE1AAC-5AE6-4725-94B6-A26C58D3423F}
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484} = {1DDE1AAC-5AE6-4725-94B6-A26C58D3423F}
+ {F686A507-CAC6-4349-9112-27F5AEFBF12B} = {A7B1AE31-4E89-42A0-8264-FBEA795AB7D2}
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {104367F1-CE75-4F40-B32F-F14853973187}
diff --git a/src/Microsoft.Identity.Web.Diagnostics/Properties/InternalsVisibleTo.cs b/src/Microsoft.Identity.Web.Diagnostics/Properties/InternalsVisibleTo.cs
index 47a95dfa6..50cad359f 100644
--- a/src/Microsoft.Identity.Web.Diagnostics/Properties/InternalsVisibleTo.cs
+++ b/src/Microsoft.Identity.Web.Diagnostics/Properties/InternalsVisibleTo.cs
@@ -9,6 +9,8 @@
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.CertificateLess, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.DownstreamApi, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraph, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClient, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClientBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraphBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.TokenAcquisition, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.TokenCache, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationOptions.cs b/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationOptions.cs
new file mode 100644
index 000000000..70c0affd0
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationOptions.cs
@@ -0,0 +1,18 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Kiota.Abstractions;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Authentication options controlling the authentication request to the Microsoft Graph service.
+ ///
+ public class GraphAuthenticationOptions : GraphServiceClientOptions, IRequestOption
+ {
+ ///
+ /// Base URL for the Microsoft Graph API. By default: "https://graph.microsoft.com/v1.0/"
+ ///
+ public new string BaseUrl { get { return base.BaseUrl!; } }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationProvider.cs b/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationProvider.cs
new file mode 100644
index 000000000..4725c1cb2
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/GraphAuthenticationProvider.cs
@@ -0,0 +1,145 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using Microsoft.Identity.Abstractions;
+using Microsoft.Kiota.Abstractions;
+using Microsoft.Kiota.Abstractions.Authentication;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Authentication provider for Microsoft Graph, based on IAuthorizationHeaderProvider. This is richer
+ /// than which only supports the bearer protocol.
+ ///
+ internal class GraphAuthenticationProvider : IAuthenticationProvider
+ {
+ const string ScopeKey = "scopes";
+ private const string AuthorizationHeaderKey = "Authorization";
+ private const string AuthorizationHeaderProviderOptionsKey = "authorizationHeaderProviderOptions";
+ readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
+ readonly GraphServiceClientOptions _defaultAuthenticationOptions;
+
+ ///
+ /// Constructor from the authorization header provider.
+ ///
+ ///
+ ///
+ public GraphAuthenticationProvider(IAuthorizationHeaderProvider authorizationHeaderProvider,
+ GraphServiceClientOptions defaultAuthenticationOptions)
+ {
+ _authorizationHeaderProvider = authorizationHeaderProvider;
+ _defaultAuthenticationOptions = defaultAuthenticationOptions;
+ }
+
+ ///
+ /// Method that performs the authentication, and adds the right headers to the request.
+ ///
+ ///
+ ///
+ ///
+ ///
+ ///
+ public async Task AuthenticateRequestAsync(
+ RequestInformation request,
+ Dictionary? additionalAuthenticationContext = null,
+ CancellationToken cancellationToken = default)
+ {
+ _ = Throws.IfNull(request);
+
+ // Attempts to get the scopes
+ IEnumerable? scopes;
+ GraphServiceClientOptions? graphServiceClientOptions;
+
+ if (additionalAuthenticationContext != null)
+ {
+ scopes = additionalAuthenticationContext.ContainsKey(ScopeKey) ? (string[])additionalAuthenticationContext[ScopeKey] : _defaultAuthenticationOptions?.Scopes;
+ graphServiceClientOptions = additionalAuthenticationContext.ContainsKey(AuthorizationHeaderProviderOptionsKey) ?
+ (GraphServiceClientOptions)additionalAuthenticationContext[AuthorizationHeaderProviderOptionsKey] :
+ _defaultAuthenticationOptions;
+ }
+ else
+ {
+ scopes = _defaultAuthenticationOptions.Scopes;
+ graphServiceClientOptions = _defaultAuthenticationOptions;
+ }
+
+ // Remove the authorization header if it exists
+ if (request.Headers.ContainsKey(AuthorizationHeaderKey))
+ {
+ request.Headers.Remove(AuthorizationHeaderKey);
+ }
+
+ // Data coming from the request (needed in protocols like "Pop")
+ AuthorizationHeaderProviderOptions? authorizationHeaderProviderOptions;
+ if (string.Compare(graphServiceClientOptions?.ProtocolScheme, "bearer", StringComparison.OrdinalIgnoreCase) == 0)
+ {
+ authorizationHeaderProviderOptions = new AuthorizationHeaderProviderOptions(graphServiceClientOptions!);
+ authorizationHeaderProviderOptions.BaseUrl = request.URI.Host;
+ authorizationHeaderProviderOptions.RelativePath = request.URI.LocalPath;
+ authorizationHeaderProviderOptions.HttpMethod = GetHttpMethod(request.HttpMethod);
+ }
+ else
+ {
+ authorizationHeaderProviderOptions = graphServiceClientOptions;
+ }
+
+ // Add the authorization header
+ if (!request.Headers.ContainsKey(AuthorizationHeaderKey))
+ {
+ string authorizationHeader;
+ if (authorizationHeaderProviderOptions!.RequestAppToken)
+ {
+ authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderForAppAsync(scopes!.FirstOrDefault()!,
+ authorizationHeaderProviderOptions);
+ }
+ else
+ {
+ authorizationHeader = await _authorizationHeaderProvider.CreateAuthorizationHeaderForUserAsync(
+ scopes!,
+ authorizationHeaderProviderOptions);
+ }
+ request.Headers.Add(AuthorizationHeaderKey, authorizationHeader);
+ }
+ }
+
+ ///
+ /// Transforms the Kiota HTTP Method (enum) into a .NET HttpMethod (static members).
+ ///
+ /// Kiota Http method
+ /// HttpMethod
+ private HttpMethod GetHttpMethod(Method httpMethod)
+ {
+ switch (httpMethod)
+ {
+ case Method.GET:
+ return HttpMethod.Get;
+ case Method.POST:
+ return HttpMethod.Post;
+ case Method.PUT:
+ return HttpMethod.Put;
+ case Method.PATCH:
+#if NETFRAMEWORK || NETSTANDARD2_0
+ return HttpMethod.Put;
+#else
+ return HttpMethod.Patch;
+#endif
+ case Method.DELETE:
+ return HttpMethod.Delete;
+ case Method.OPTIONS:
+ return HttpMethod.Options;
+ case Method.TRACE:
+ return HttpMethod.Trace;
+ case Method.HEAD:
+ return HttpMethod.Head;
+ default:
+ throw new ArgumentOutOfRangeException();
+ }
+ }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceClientOptions.cs b/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceClientOptions.cs
new file mode 100644
index 000000000..f7d4f1758
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceClientOptions.cs
@@ -0,0 +1,32 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using Microsoft.Identity.Abstractions;
+using System.Collections.Generic;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Options passed-in to call Microsoft Graph.
+ ///
+ public class GraphServiceClientOptions : AuthorizationHeaderProviderOptions
+ {
+ ///
+ /// Options used to configure the authentication provider for Microsoft Graph.
+ ///
+ public GraphServiceClientOptions()
+ {
+ BaseUrl = Constants.GraphBaseUrlV1;
+ Scopes = new[] { Constants.UserReadScope };
+ }
+
+ ///
+ /// Scopes required to call the downstream web API.
+ /// For instance "user.read mail.read".
+ /// For Microsoft identity, in the case of application tokens (token
+ /// requested by the app on behalf of itself), there should be only one scope, and it
+ /// should end in "./default")
+ ///
+ public IEnumerable Scopes { get; set; }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceCollectionExtensions.cs
new file mode 100644
index 000000000..709d4930f
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/GraphServiceCollectionExtensions.cs
@@ -0,0 +1,74 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Net.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.Graph;
+using Microsoft.Identity.Abstractions;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Extensions methods on a MicrosoftIdentityAppCallingWebApiAuthenticationBuilder builder
+ /// to add support to call Microsoft Graph.
+ ///
+ public static class GraphServiceCollectionExtensions
+ {
+ ///
+ /// Add support to call Microsoft Graph. From a named option and a configuration section.
+ ///
+ /// Builder.
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services)
+ {
+ services.AddTokenAcquisition();
+ services.AddHttpClient();
+ return services.AddMicrosoftGraph(options => { });
+ }
+
+ ///
+ /// Add support to call Microsoft Graph. From a base Graph URL and a default scope.
+ ///
+ /// Builder.
+ /// Configuration section containing the Microsoft graph config.
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, IConfiguration configurationSection)
+ {
+ return services.AddMicrosoftGraph(o => configurationSection.Bind(o));
+ }
+
+ ///
+ /// Add support to call Microsoft Graph. From a base Graph URL and a default scope.
+ ///
+ /// Builder.
+ /// Delegate to configure the graph service options
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraph(this IServiceCollection services, Action configureMicrosoftGraphOptions)
+ {
+ // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
+ services.AddOptions().Configure(configureMicrosoftGraphOptions);
+
+ services.AddScoped(serviceProvider =>
+ {
+ var authorizationHeaderProvider = serviceProvider.GetRequiredService();
+ var options = serviceProvider.GetRequiredService>();
+ var httpClientFactory = serviceProvider.GetRequiredService();
+ var microsoftGraphOptions = options.Value;
+ if (microsoftGraphOptions.Scopes == null)
+ {
+ Throws.ArgumentNullException("scopes", IDWebErrorMessage.CalledApiScopesAreNull);
+ }
+
+ var httpClient = httpClientFactory.CreateClient("GraphServiceClient");
+
+ GraphServiceClient graphServiceClient = new(httpClient,
+ new GraphAuthenticationProvider(authorizationHeaderProvider, microsoftGraphOptions), microsoftGraphOptions.BaseUrl);
+ return graphServiceClient;
+ });
+ return services;
+ }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/Microsoft.Identity.Web.GraphServiceClient.csproj b/src/Microsoft.Identity.Web.GraphServiceClient/Microsoft.Identity.Web.GraphServiceClient.csproj
new file mode 100644
index 000000000..84e94eab4
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/Microsoft.Identity.Web.GraphServiceClient.csproj
@@ -0,0 +1,22 @@
+
+
+
+ Microsoft Identity Web, Microsoft Graph v5+ helper
+ Microsoft Identity Web
+
+ This package enables ASP.NET Core web apps and web APIs to use the Microsoft identity platform (formerly Azure AD v2.0).
+ This package is specifically used for web applications, which sign-in users and call Microsoft Graph, and for protected web APIs
+ that call Microsoft Graph. Works specifically with MS Graph SDK v5 and above. For MS Graph SDK v4 support, please use Microsoft.Identity.Web.MicrosoftGraph.
+
+ {4DF02DF7-D092-4F45-8892-8A1D3E612706}
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/Properties/InternalsVisibleTo.cs b/src/Microsoft.Identity.Web.GraphServiceClient/Properties/InternalsVisibleTo.cs
new file mode 100644
index 000000000..4d20fccd3
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/Properties/InternalsVisibleTo.cs
@@ -0,0 +1,9 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System.Runtime.CompilerServices;
+
+// Allow this assembly to be serviced when run on desktop CLR
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test.Integration, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("GraphServiceClientTests, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClientBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/Readme.md b/src/Microsoft.Identity.Web.GraphServiceClient/Readme.md
new file mode 100644
index 000000000..852f202aa
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/Readme.md
@@ -0,0 +1,282 @@
+# Microsoft.Identity.Web.GraphServiceClient
+
+With the introduction of Microsoft.Identity.Web.GraphServiceClient and Microsoft.Identity.Web.GraphServiceClientBeta
+libraries in version Microsoft.Identity.Web 2.12, you now have the choice to use either the legacy
+Microsoft.Identity.Web.MicrosoftGraph and Microsoft.Identity.Web.MicrosoftGraphBeta NuGet packages
+based on Microsoft Graph SDK 4.x or the new libraries based on Microsoft Graph SDK 5.
+By keeping both options available, you can choose to migrate to the latest version of the SDK at your own pace
+and with minimal disruption to your existing code.
+
+By migrating to Microsoft.Identity.Web.GraphServiceClient, you'll benefit from the latest features of the Microsoft Graph SDK,
+including a simplified fluent API and the ability to use both Microsoft Graph and Microsoft Graph Beta APIs in the same application.
+However, migrating from Microsoft.Identity.Web.MicrosoftGraph 2.x to Microsoft.Identity.Web.GraphServiceClient requires moving some of your code,
+as discussed in the [migration guide](#migrate-from-microsoftidentitywebmicrosoftgraph-2x-to-microsoftidentitywebgraphserviceclient).
+
+## Usage
+
+1. Reference Microsoft.Identity.Web.GraphServiceClient in your project.
+ ```shell
+ dotnet add package Microsoft.Identity.Web.GraphServiceClient
+ ```
+
+1. In the startup method, add Microsoft Graph support to the service collection.
+ By default, the scopes are set to `User.Read` and the BaseUrl is "https://graph.microsoft.com/v1.0".
+ You can change them by passing a delegate to the `AddMicrosoftGraph` method (See below).
+
+ Use the following namespace.
+ ```csharp
+ using Microsoft.Identity.Web;
+ ```
+
+ Add the Microsoft Graph
+
+ ```csharp
+ services.AddMicrosoftGraph();
+ ```
+
+ or, if you have described Microsoft Graph options in your configuration file:
+ ```json
+ "AzureAd":
+ {
+ // more here
+ },
+
+ "DownstreamApis":
+ {
+ "MicrosoftGraph":
+ {
+ // Specify BaseUrl if you want to use Microsoft graph in a national cloud.
+ // See https://learn.microsoft.com/graph/deployments#microsoft-graph-and-graph-explorer-service-root-endpoints
+ // "BaseUrl": "https://graph.microsoft.com/v1.0",
+
+ // Set RequestAppToken this to "true" if you want to request an application token (to call graph on
+ // behalf of the application). The scopes will then automatically
+ // be ['https://graph.microsoft.com/.default'].
+ // "RequestAppToken": false
+
+ // Set Scopes to request (unless you request an app token).
+ "Scopes": ["User.Read", "User.ReadBasic.All"]
+
+ // See https://aka.ms/ms-id-web/downstreamApiOptions for all the properties you can set.
+ }
+ }
+ ```
+
+ The code to add Microsoft Graph based on the configuration is:
+
+ ```csharp
+ services.AddMicrosoftGraph();
+ services.Configure(options =>
+ services.Configuration.GetSection("DownstreamApis:MicrosoftGraph"));
+ ```
+
+ or
+
+ ```csharp
+ services.AddMicrosoftGraph(options =>
+ services.Configuration.GetSection("DownstreamApis:MicrosoftGraph").Bind(options) );
+ ```
+
+2. Inject the GraphServiceClient from the constructor of controllers.
+ ```csharp
+ using Microsoft.Graph;
+
+ public class HomeController : Controller
+ {
+ private readonly GraphServiceClient _graphServiceClient;
+ public HomeController(GraphServiceClient graphServiceClient)
+ {
+ _graphServiceClient = graphServiceClient;
+ }
+ }
+ ```
+
+3. Use Microsoft Graph SDK to call Microsoft Graph. For example, to get the current user's profile:
+ ```csharp
+ var user = await _graphServiceClient.Me.GetAsync();
+ ```
+
+4. You can override the default options in the GetAsync(), PostAsync() etc.. methods.
+ For example to get the mail folders of the current user, you'll need to request more scopes ("Mail.Read").
+ If your app registered several authentication schemes in ASP.NET Core, you'll also need to specify
+ which to authentication scheme to apply.
+
+ ```csharp
+ var mailFolders = await _graphServiceClient.Me.MailFolders.GetAsync(r =>
+ {
+ r.Options.WithScopes("Mail.Read")
+ .WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme);
+ });
+ ```
+
+ You could also write the same code as follows, which is more verbose, but enables you to set several options at once:
+
+ ```csharp
+ var mailFolders = await _graphServiceClient.Me.MailFolders.GetAsync(r =>
+ {
+ r.Options.WithAuthenticationOptions(o =>
+ {
+ // Specify scopes for the request
+ o.Scopes = new string[] { "Mail.Read" };
+
+ // Specify the ASP.NET Core authentication scheme if needed (in the case
+ // of multiple authentication schemes)
+ o.AcquireTokenOptions.AuthenticationOptionsName = JwtBearerDefaults.AuthenticationScheme;
+ });
+ });
+ ```
+
+ If your app calls the Graph API on behalf of itself, you'll need to request an application token.
+ You do this by setting WithAppOnly. For instance to get the number of applications in the tenant:
+
+ ```charp
+ int? appsInTenant = await _graphServiceClient.Applications.Count.GetAsync(
+ r => r.Options.WithAppOnly() );
+ ```
+
+ which is a shortcut for:
+
+ ```charp
+ int? appsInTenant = await _graphServiceClient.Applications.Count.GetAsync(r =>
+ {
+ r.Options.WithAuthenticationOptions(o =>
+ {
+ // Applications require app permissions, hence an app token
+ o.RequestAppToken = true;
+ });
+ });
+ ```
+
+## You can now use both Microsoft Graph and Microsoft Graph Beta
+
+You can now use both Microsoft Graph and Microsoft Graph Beta in the same application:
+
+1. Reference both Microsoft.Identity.Web.GraphServiceClient and Microsoft.Identity.Web.GraphServiceClientBeta in your project
+ ```shell
+ dotnet add package Microsoft.Identity.Web.GraphServiceClient
+ dotnet add package Microsoft.Identity.Web.GraphServiceClientBeta
+ ```
+
+1. In the startup method, add Microsoft Graph and Graph Beta to the service collection:
+
+ ```csharp
+ services.AddMicrosoftGraph();
+ services.AddMicrosoftGraphBeta();
+ ```
+
+1. In the controller or wherever you want to use them declare both GraphServiceClient and GraphServiceClientBeta
+ and inject them in the constructor:
+
+ ```csharp
+ using GraphServiceClient = Microsoft.Graph.GraphServiceClient;
+ using GraphBetaServiceClient = Microsoft.Graph.GraphBetaServiceClient;
+ ```
+
+ ```csharp
+ MyController(GraphServiceClient graphServiceClient, GraphBetaServiceClient graphServiceClient)
+ {
+ // more here
+ }
+ ```
+
+## Migrate from Microsoft.Identity.Web.MicrosoftGraph 2.x to Microsoft.Identity.Web.GraphServiceClient
+
+Microsoft.Identity.Web.GraphServiceClient is based on Microsoft.GraphSDK 5.x, which introduces breaking changes.
+The Request() method has disappeared, and the extension methods it enabled in Microsoft.Identity.Web.MicrosoftGraph
+are now moved to the GetAsync(), GetPost(), etc methods.
+
+The Microsoft Graph 4.x code:
+
+```csharp
+var user = await _graphServiceClient.Me.Request().GetAsync();
+```
+
+becomes with Microsoft.Graph 5.x:
+
+```csharp
+var user = await _graphServiceClient.Me.GetAsync();
+```
+
+The following paragraphs help you migrate from Microsoft.Identity.Web.MicrosoftGraph to Microsoft.Identity.Web.GraphServiceClient.
+
+### Replace the nuget packages
+
+1. Reference Microsoft.Identity.Web.GraphServiceClient in your project.
+ ```shell
+ dotnet remove package Microsoft.Identity.Web.MicrosoftGraph
+ dotnet add package Microsoft.Identity.Web.GraphServiceClient
+ ```
+
+### Update the code
+
+In addition to the changes to the code due to the migration from Microsoft.Graph 4.x to Microsoft.Graph 5.x, you need to change the location of the
+modifiers `.WithScopes()`, `.WithAppOnly()`, `WithAuthenticationScheme()` and `.WithAuthenticationOptions()`.
+
+#### WithScopes()
+
+In Microsoft.Identity.Web.MicrosoftGraph, you used to use `.WithScopes()` on the request to specify scopes to use to authenticate to Microsoft Graph:
+```csharp
+var messages = await _graphServiceClient.Users
+ .Request()
+ .WithScopes("User.Read.All")
+ .GetAsync();
+int NumberOfUsers = messages.Count;
+```
+
+With Microsoft.Identity.Web.GraphServiceClient, you need to call `.WithScopes()` on the options of the builder.
+
+```csharp
+var messages = await _graphServiceClient.Users
+ .GetAsync(b => b.Options.WithScopes("User.Read.All"));
+int NumberOfUsers = messages.Value.Count;
+```
+
+#### WithAppOnly()
+
+In Microsoft.Identity.Web.MicrosoftGraph 2.x, you could specify using app permissions (which require an app-only-token) by calling `.WithAppOnly()`.
+
+```csharp
+var messages = await _graphServiceClient.Users
+ .Request()
+ .WithAppOnly()
+ .GetAsync();
+int NumberOfUsers = messages.Count;
+```
+
+With Microsoft.Identity.Web.GraphServiceClient, you need to call `.WithAppOnly()` on the options of the builder.
+
+```csharp
+var messages = await _graphServiceClient.Users
+ .GetAsync(b => b.Options.WithAppOnly() ));
+int NumberOfUsers = messages.Value.Count;
+```
+
+Note that this will use, under the hood, the scopes **["https://graph.microsoft.com/.default"]** which means all the pre-authorized scopes. You don't need
+to specify these scopes, as this is the only possible when calling a Microsof Graph API requiring app permissions.
+
+#### WithAuthenticationScheme() in ASP.NET Core applications.
+
+If you are using Microsoft.Identity.Web.MicrosoftGraph in an ASP.NET Core application, you can specify the authentication scheme
+to use by calling `WithAuthenticationScheme()`.
+
+```csharp
+var messages = await _graphServiceClient.Users
+ .Request()
+ .WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme)
+ .GetAsync();
+int NumberOfUsers = messages.Count;
+```
+
+With Microsoft.Identity.Web.GraphServiceClient, this becomes:
+
+```csharp
+var messages = await _graphServiceClient.Users
+ .GetAsync(b => b.Options.WithAuthenticationScheme(JwtBearerDefaults.AuthenticationScheme) ));
+int NumberOfUsers = messages.Value.Count;
+```
+
+More information about the migration from Microsoft Graph SDK 4.x to 5.x can be found in [Microsoft Graph .NET SDK v5 changelog and upgrade guide](https://github.com/microsoftgraph/msgraph-sdk-dotnet/blob/dev/docs/upgrade-to-v5.md)
+
+#### Other authentication options
+
+You can use .WithAuthenticationOptions() on the builder options.
diff --git a/src/Microsoft.Identity.Web.GraphServiceClient/RequestOptionsExtension.cs b/src/Microsoft.Identity.Web.GraphServiceClient/RequestOptionsExtension.cs
new file mode 100644
index 000000000..e1205634d
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClient/RequestOptionsExtension.cs
@@ -0,0 +1,111 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using Microsoft.Kiota.Abstractions;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Extension class on the
+ ///
+ public static class RequestOptionsExtension
+ {
+ ///
+ /// Specify the authentication options for the request.
+ ///
+ /// Options to modify.
+ /// Authentication request options to set.
+ ///
+ public static IList WithAuthenticationOptions(this IList options, GraphAuthenticationOptions optionsValue)
+ {
+ GraphAuthenticationOptions? graphAuthenticationOptions = options.OfType().FirstOrDefault();
+ if (graphAuthenticationOptions != null)
+ {
+ throw new ArgumentException("Can't add GraphAuthenticationOptions twice. Rather use the delegate.");
+ }
+ options.Add(optionsValue);
+ return options;
+ }
+
+ ///
+ /// Specify a delegate setting the authentication options for the request.
+ ///
+ /// Options to modify.
+ /// Delegate setting the authentication request.
+ ///
+ public static IList WithAuthenticationOptions(this IList options, Action optionsValue)
+ {
+ GraphAuthenticationOptions? graphAuthenticationOptions = options.OfType().FirstOrDefault();
+ if (graphAuthenticationOptions == null)
+ {
+ graphAuthenticationOptions = new GraphAuthenticationOptions();
+ options.Add(graphAuthenticationOptions);
+ }
+ optionsValue(graphAuthenticationOptions);
+ return options;
+ }
+
+ ///
+ /// Specify the scopes to use to request a token for the request.
+ ///
+ /// Options to modify.
+ /// Microsoft Graph scopes used to authenticate this request.
+ ///
+ public static IList WithScopes(this IList options, params string[] scopes)
+ {
+ GraphAuthenticationOptions? graphAuthenticationOptions = options.OfType().FirstOrDefault();
+ if (graphAuthenticationOptions == null)
+ {
+ graphAuthenticationOptions = new GraphAuthenticationOptions();
+ options.Add(graphAuthenticationOptions);
+ }
+ graphAuthenticationOptions.Scopes = scopes;
+ return options;
+ }
+
+ ///
+ /// Specifies to use app only permissions for Graph.
+ ///
+ /// Options to modify.
+ /// Should the permissions be app only or not.
+ /// Tenant ID or domain for which we want to make the call..
+ ///
+ public static IList WithAppOnly(this IList options, bool appOnly=true, string? tenant=null)
+ {
+ GraphAuthenticationOptions? graphAuthenticationOptions = options.OfType().FirstOrDefault();
+ if (graphAuthenticationOptions == null)
+ {
+ graphAuthenticationOptions = new GraphAuthenticationOptions();
+ options.Add(graphAuthenticationOptions);
+ }
+ graphAuthenticationOptions.RequestAppToken = appOnly;
+ graphAuthenticationOptions.AcquireTokenOptions ??= new();
+ graphAuthenticationOptions.AcquireTokenOptions.Tenant = tenant;
+ return options;
+ }
+
+#if NETCOREAPP
+ ///
+ /// In ASP.NET Core, specify the authentication scheme used to authenticate this request.
+ ///
+ /// Options to modify.
+ /// ASP.NET Core authentication scheme used to authenticate this request.
+ ///
+ public static IList WithAuthenticationScheme(this IList options, string authenticationScheme)
+ {
+ GraphAuthenticationOptions? graphAuthenticationOptions = options.OfType().FirstOrDefault();
+ if (graphAuthenticationOptions == null)
+ {
+ graphAuthenticationOptions = new GraphAuthenticationOptions();
+ options.Add(graphAuthenticationOptions);
+ }
+ graphAuthenticationOptions.AcquireTokenOptions ??= new();
+ graphAuthenticationOptions.AcquireTokenOptions.AuthenticationOptionsName = authenticationScheme;
+ return options;
+ }
+#endif
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClientBeta/GraphBetaServiceCollectionExtensions.cs b/src/Microsoft.Identity.Web.GraphServiceClientBeta/GraphBetaServiceCollectionExtensions.cs
new file mode 100644
index 000000000..dc1247088
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClientBeta/GraphBetaServiceCollectionExtensions.cs
@@ -0,0 +1,75 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Net.Http;
+using Microsoft.Extensions.Configuration;
+using Microsoft.Extensions.DependencyInjection;
+using Microsoft.Extensions.Options;
+using Microsoft.Graph.Beta;
+using Microsoft.Identity.Abstractions;
+
+namespace Microsoft.Identity.Web
+{
+ ///
+ /// Extensions methods on a MicrosoftIdentityAppCallingWebApiAuthenticationBuilder builder
+ /// to add support to call Microsoft Graph Beta.
+ ///
+ public static class GraphBetaServiceCollectionExtensions
+ {
+ ///
+ /// Add support to call Microsoft Graph Beta. From a named option and a configuration section.
+ ///
+ /// Builder.
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraphBeta(this IServiceCollection services)
+ {
+ services.AddTokenAcquisition();
+ services.AddHttpClient();
+ return services.AddMicrosoftGraphBeta(options => { });
+ }
+
+ ///
+ /// Add support to call Microsoft Graph Beta. From a base Graph URL and a default scope.
+ ///
+ /// Builder.
+ /// Configuration section containing the Microsoft graph config.
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraphBeta(this IServiceCollection services, IConfiguration configurationSection)
+ {
+ return services.AddMicrosoftGraphBeta(o => configurationSection.Bind(o));
+ }
+
+ ///
+ /// Add support to call Microsoft Graph Beta. From a base Graph URL and a default scope.
+ ///
+ /// Builder.
+ /// Delegate to configure the graph service options
+ /// The service collection to chain.
+ public static IServiceCollection AddMicrosoftGraphBeta(this IServiceCollection services, Action configureMicrosoftGraphOptions)
+ {
+ // https://docs.microsoft.com/en-us/dotnet/standard/microservices-architecture/implement-resilient-applications/use-httpclientfactory-to-implement-resilient-http-requests
+ services.AddOptions().Configure(configureMicrosoftGraphOptions);
+
+ services.AddScoped(serviceProvider =>
+ {
+ var authorizationHeaderProvider = serviceProvider.GetRequiredService();
+ var options = serviceProvider.GetRequiredService>();
+ var httpClientFactory = serviceProvider.GetRequiredService();
+ var microsoftGraphOptions = options.Value;
+ if (microsoftGraphOptions.Scopes == null)
+ {
+ Throws.ArgumentNullException("scopes", IDWebErrorMessage.CalledApiScopesAreNull);
+ }
+
+ var httpClient = httpClientFactory.CreateClient("GraphServiceClientBeta");
+
+ GraphServiceClient betaGraphServiceClient = new(httpClient,
+ new GraphAuthenticationProvider(authorizationHeaderProvider, microsoftGraphOptions), microsoftGraphOptions.BaseUrl);
+ return betaGraphServiceClient;
+ });
+
+ return services;
+ }
+ }
+}
diff --git a/src/Microsoft.Identity.Web.GraphServiceClientBeta/Microsoft.Identity.Web.GraphServiceClientBeta.csproj b/src/Microsoft.Identity.Web.GraphServiceClientBeta/Microsoft.Identity.Web.GraphServiceClientBeta.csproj
new file mode 100644
index 000000000..4e1cb0698
--- /dev/null
+++ b/src/Microsoft.Identity.Web.GraphServiceClientBeta/Microsoft.Identity.Web.GraphServiceClientBeta.csproj
@@ -0,0 +1,23 @@
+
+
+
+ Microsoft Identity Web, Microsoft Graph v5+ helper
+ Microsoft Identity Web
+
+ This package enables ASP.NET Core web apps and web APIs to use the Microsoft identity platform (formerly Azure AD v2.0).
+ This package is specifically used for web applications, which sign-in users and call Microsoft Graph, and for protected web APIs
+ that call Microsoft Graph. Works specifically with MS Graph SDK v5 and above. For MS Graph SDK v4 support, please use Microsoft.Identity.Web.MicrosoftGraph.
+
+ {608F0E0B-A52D-4E0F-9B1A-BA9BDA866484}
+
+
+
+
+
+
+
+
+
+
+
+
diff --git a/src/Microsoft.Identity.Web.TokenAcquisition/Properties/InternalsVisibleTo.cs b/src/Microsoft.Identity.Web.TokenAcquisition/Properties/InternalsVisibleTo.cs
index f5d58958e..dbc337c80 100644
--- a/src/Microsoft.Identity.Web.TokenAcquisition/Properties/InternalsVisibleTo.cs
+++ b/src/Microsoft.Identity.Web.TokenAcquisition/Properties/InternalsVisibleTo.cs
@@ -6,6 +6,8 @@
// Allow this assembly to be serviced when run on desktop CLR
[assembly: InternalsVisibleTo("Microsoft.Identity.Web, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.OWIN, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClient, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
+[assembly: InternalsVisibleTo("Microsoft.Identity.Web.GraphServiceClientBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraph, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.MicrosoftGraphBeta, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
[assembly: InternalsVisibleTo("Microsoft.Identity.Web.Test, PublicKey=00240000048000009400000006020000002400005253413100040000010001002D96616729B54F6D013D71559A017F50AA4861487226C523959D1579B93F3FDF71C08B980FD3130062B03D3DE115C4B84E7AC46AEF5E192A40E7457D5F3A08F66CEAB71143807F2C3CB0DA5E23B38F0559769978406F6E5D30CEADD7985FC73A5A609A8B74A1DF0A29399074A003A226C943D480FEC96DBEC7106A87896539AD")]
diff --git a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllApps.cshtml.cs b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllApps.cshtml.cs
index abb3d2016..784293575 100644
--- a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllApps.cshtml.cs
+++ b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllApps.cshtml.cs
@@ -28,10 +28,9 @@ public AllAppsModel(ILogger logger, GraphServiceClient graphServiceC
public async Task OnGet()
{
var messages = await _graphServiceClient.Applications
- .Request()
- .WithAppOnly()
- .GetAsync();
- NumberOfApps = messages.Count;
+ .GetAsync(r => r.Options.WithAppOnly());
+ ;
+ NumberOfApps = messages.Value.Count;
}
}
}
diff --git a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllUsers.cshtml.cs b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllUsers.cshtml.cs
index 8420d0c2a..73599bc60 100644
--- a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllUsers.cshtml.cs
+++ b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/AllUsers.cshtml.cs
@@ -26,10 +26,8 @@ public AllUsersModel(ILogger logger, GraphServiceClient graphService
public async Task OnGet()
{
var messages = await _graphServiceClient.Users
- .Request()
- .WithScopes("User.Read.All")
- .GetAsync();
- NumberOfUsers = messages.Count;
+ .GetAsync(b => b.Options.WithAppOnly());
+ NumberOfUsers = messages.Value.Count;
}
}
}
diff --git a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/Index.cshtml.cs b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/Index.cshtml.cs
index fd8c4e72d..8e50a59c7 100644
--- a/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/Index.cshtml.cs
+++ b/tests/DevApps/WebAppCallsMicrosoftGraph/Pages/Index.cshtml.cs
@@ -29,11 +29,11 @@ public IndexModel(ILogger logger, GraphServiceClient graphServiceCli
public async Task OnGet()
{
- var user = await _graphServiceClient.Me.Request().GetAsync();
+ var user = await _graphServiceClient.Me.GetAsync(r => r.Options.WithScopes("user.read"));
try
{
- using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
+ using (var photoStream = await _graphServiceClient.Me.Photo.Content.GetAsync())
{
byte[] photoByte = ((MemoryStream)photoStream).ToArray();
ViewData["photo"] = Convert.ToBase64String(photoByte);
diff --git a/tests/DevApps/WebAppCallsMicrosoftGraph/Startup.cs b/tests/DevApps/WebAppCallsMicrosoftGraph/Startup.cs
index 6ee4dda54..bcc7e9736 100644
--- a/tests/DevApps/WebAppCallsMicrosoftGraph/Startup.cs
+++ b/tests/DevApps/WebAppCallsMicrosoftGraph/Startup.cs
@@ -11,6 +11,7 @@
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.Hosting;
using Microsoft.Identity.Web;
+using Microsoft.Identity.Web.TokenCacheProviders.InMemory;
using Microsoft.Identity.Web.UI;
namespace WebAppCallsMicrosoftGraph
@@ -36,7 +37,9 @@ public void ConfigureServices(IServiceCollection services)
services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection(configSection))
- .EnableTokenAcquisitionToCallDownstreamApi()
+ .EnableTokenAcquisitionToCallDownstreamApi();
+
+ services
.AddMicrosoftGraph(Configuration.GetSection("GraphBeta"))
.AddDownstreamApi("GraphBeta", Configuration.GetSection("GraphBeta"))
.AddInMemoryTokenCaches();
diff --git a/tests/DevApps/WebAppCallsMicrosoftGraph/WebAppCallsMicrosoftGraph.csproj b/tests/DevApps/WebAppCallsMicrosoftGraph/WebAppCallsMicrosoftGraph.csproj
index 6393f06b4..460db32e8 100644
--- a/tests/DevApps/WebAppCallsMicrosoftGraph/WebAppCallsMicrosoftGraph.csproj
+++ b/tests/DevApps/WebAppCallsMicrosoftGraph/WebAppCallsMicrosoftGraph.csproj
@@ -4,7 +4,7 @@
-
+
diff --git a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config
index 8a3c3f1e9..52a3a77f0 100644
--- a/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config
+++ b/tests/DevApps/aspnet-mvc/OwinWebApi/Web.config
@@ -98,7 +98,7 @@
-
+
diff --git a/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config b/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config
index d7e067c46..882eff566 100644
--- a/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config
+++ b/tests/DevApps/aspnet-mvc/OwinWebApp/Web.config
@@ -99,7 +99,7 @@
-
+
diff --git a/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.cs b/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.cs
new file mode 100644
index 000000000..44b65e461
--- /dev/null
+++ b/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.cs
@@ -0,0 +1,72 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+using System;
+using System.Threading.Tasks;
+using Microsoft.Graph;
+using Microsoft.Graph.Models;
+using Microsoft.Identity.Abstractions;
+
+namespace Microsoft.Identity.Web.Test.Integration
+{
+
+ ///
+ /// This is a compilation test only. It is not meant to be run.
+ ///
+ public class GraphServiceClientTests
+ {
+#pragma warning disable CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+#pragma warning disable CS0649 // Field 'GraphServiceClientTests._authorizationHeaderProvider' is never assigned to, and will always have its default value null
+ readonly IAuthorizationHeaderProvider _authorizationHeaderProvider;
+ readonly GraphServiceClientOptions _defaultAuthenticationOptions;
+#pragma warning restore CS0649 // Field 'GraphServiceClientTests._authorizationHeaderProvider' is never assigned to, and will always have its default value null
+#pragma warning restore CS8618 // Non-nullable field must contain a non-null value when exiting constructor. Consider declaring as nullable.
+
+#pragma warning disable IDE0051 // Remove unused private members
+ async Task Test()
+#pragma warning restore IDE0051 // Remove unused private members
+ {
+ GraphServiceClient graphServiceClient = new(new GraphAuthenticationProvider(_authorizationHeaderProvider, new GraphServiceClientOptions()));
+
+ User? me = await graphServiceClient.Me.GetAsync(r =>
+ {
+ r.Options.WithAuthenticationOptions(o =>
+ {
+ o.Scopes = new string[] { "user.read" };
+ o.RequestAppToken = true;
+ o.ProtocolScheme = "Pop";
+ o.AcquireTokenOptions.Claims = "claims";
+ o.AcquireTokenOptions.PopPublicKey = "";
+ o.AcquireTokenOptions.CorrelationId = Guid.NewGuid();
+ o.AcquireTokenOptions.UserFlow = "susi";
+ o.AcquireTokenOptions.AuthenticationOptionsName = "JwtBearer";
+ o.AcquireTokenOptions.Tenant = "TenantId";
+ });
+ }
+ );
+
+ MailFolderCollectionResponse? mailFolders = await graphServiceClient.Me.MailFolders.GetAsync(r =>
+ {
+ r.Options.WithAuthenticationOptions(o =>
+ {
+ // Specify scopes for the request
+ o.Scopes = new string[] { "Mail.Read" };
+
+ // Specify the ASP.NET Core authentication scheme if needed (in the case
+ // of multiple authentication schemes)
+ // o.AuthenticationOptionsName = JwtBearerDefaults.AuthenticationScheme;
+ });
+ });
+
+ int? appsInTenant = await graphServiceClient.Applications.Count.GetAsync(r =>
+ {
+ r.Options.WithAuthenticationOptions(o =>
+ {
+ // It's an app permission. Requires an app token
+ o.RequestAppToken = true;
+ });
+ });
+
+ }
+ }
+}
diff --git a/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.csproj b/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.csproj
new file mode 100644
index 000000000..a7f312181
--- /dev/null
+++ b/tests/IntegrationTests/GraphServiceClientTests/GraphServiceClientTests.csproj
@@ -0,0 +1,27 @@
+
+
+
+ net7.0
+ ../../../build/MSAL.snk
+
+
+
+
+
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+ runtime; build; native; contentfiles; analyzers; buildtransitive
+ all
+
+
+
+
+
+
+
+
+
diff --git a/tests/IntegrationTests/GraphServiceClientTests/Usings.cs b/tests/IntegrationTests/GraphServiceClientTests/Usings.cs
new file mode 100644
index 000000000..8190c642f
--- /dev/null
+++ b/tests/IntegrationTests/GraphServiceClientTests/Usings.cs
@@ -0,0 +1,4 @@
+// Copyright (c) Microsoft Corporation. All rights reserved.
+// Licensed under the MIT License.
+
+global using Xunit;
\ No newline at end of file
diff --git a/tests/IntegrationTests/TokenAcquirerTests/TokenAcquirer.cs b/tests/IntegrationTests/TokenAcquirerTests/TokenAcquirer.cs
index eaf85f419..86e7e7c26 100644
--- a/tests/IntegrationTests/TokenAcquirerTests/TokenAcquirer.cs
+++ b/tests/IntegrationTests/TokenAcquirerTests/TokenAcquirer.cs
@@ -196,7 +196,7 @@ private static RsaSecurityKey CreateRsaSecurityKey()
// the reason for creating the RsaSecurityKey from RSAParameters is so that a SignatureProvider created with this key
// will own the RSA object and dispose it. If we pass a RSA object, the SignatureProvider does not own the object, the RSA object will not be disposed.
RSAParameters rsaParameters = rsa.ExportParameters(true);
- RsaSecurityKey rsaSecuirtyKey = new RsaSecurityKey(rsaParameters) { KeyId = CreateRsaKeyId(rsaParameters) };
+ RsaSecurityKey rsaSecuirtyKey = new(rsaParameters) { KeyId = CreateRsaKeyId(rsaParameters) };
rsa.Dispose();
return rsaSecuirtyKey;
}
@@ -210,7 +210,9 @@ private static string CreateRsaKeyId(RSAParameters rsaParameters)
Array.Copy(rsaParameters.Exponent, 0, kidBytes, 0, rsaParameters.Exponent.Length);
Array.Copy(rsaParameters.Modulus, 0, kidBytes, rsaParameters.Exponent.Length, rsaParameters.Modulus.Length);
using (var sha2 = SHA256.Create())
+ {
return Base64UrlEncoder.Encode(sha2.ComputeHash(kidBytes));
+ }
}
private string? ComputePublicKeyString(X509Certificate2? certificate)