From 3d43d5bcab04f02c5db83eef77c6111484c3cc7d Mon Sep 17 00:00:00 2001 From: Timothy Wamalwa Date: Wed, 26 Jun 2024 10:22:42 +0300 Subject: [PATCH 1/9] Rolled back devicemanagement to the one that 2.19.0 had. --- openApiDocs/beta/DeviceManagement.Actions.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/openApiDocs/beta/DeviceManagement.Actions.yml b/openApiDocs/beta/DeviceManagement.Actions.yml index 1bb85dea98b..13ba0d3e82c 100644 --- a/openApiDocs/beta/DeviceManagement.Actions.yml +++ b/openApiDocs/beta/DeviceManagement.Actions.yml @@ -487,4 +487,4 @@ components: tokenUrl: https://login.microsoftonline.com/common/oauth2/v2.0/token scopes: { } security: - - azureaadv2: [ ] + - azureaadv2: [ ] \ No newline at end of file From d76c5461855d0a0c75bb09d534c4abe19ff815f6 Mon Sep 17 00:00:00 2001 From: "Taofeek F. Obafemi-Babatunde" Date: Thu, 28 Dec 2023 10:52:21 -0800 Subject: [PATCH 2/9] adding "-AT PoP" option to "Set-MgGraphOptions" Adding AT PoP skeleton (#2511) * adding "-AT PoP" option to "Set-MgGraphOptions" --------- AT PoP Version 1 Fehintolaobafemi/methodanduri (#2751) * Making changes to how httpmethod and uri is processed --------- Fixing and updating the docs resolving build break fixing build issues related to docs resolving PR comments updating nuget packages removing unnecessary string assignment Resolving PR comments reverting test csproj changes --- docs/AT-Pop.md | 79 +++++++++++++++++++ docs/authentication.md | 20 +++++ .../Common/GraphSession.cs | 5 ++ .../Interfaces/IGraphOptions.cs | 5 +- .../Interfaces/IGraphRequestPopContext.cs | 21 +++++ .../Interfaces/IGraphSession.cs | 1 + ...Microsoft.Graph.Authentication.Core.csproj | 2 +- .../Utilities/AuthenticationHelpers.cs | 64 +++++++++++++-- .../Cmdlets/InvokeMgGraphRequest.cs | 2 + .../Cmdlets/SetMgGraphOption.cs | 8 ++ .../Common/GraphSessionInitializer.cs | 3 +- .../Handlers/AuthenticationHandler.cs | 32 +++++++- .../Authentication/Models/GraphOption.cs | 1 + .../Models/GraphRequestPopContext.cs | 23 ++++++ .../test/Get-MgGraphOption.Tests.ps1 | 2 +- .../test/Set-MgGraphOption.Tests.ps1 | 16 +++- src/Authentication/docs/Get-MgGraphOption.md | 2 +- src/Authentication/docs/Set-MgGraphOption.md | 15 +++- .../examples/Set-MgGraphOption.md | 8 +- 19 files changed, 287 insertions(+), 22 deletions(-) create mode 100644 docs/AT-Pop.md create mode 100644 src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs create mode 100644 src/Authentication/Authentication/Models/GraphRequestPopContext.cs diff --git a/docs/AT-Pop.md b/docs/AT-Pop.md new file mode 100644 index 00000000000..7793dd6c9e8 --- /dev/null +++ b/docs/AT-Pop.md @@ -0,0 +1,79 @@ +# Microsoft Graph PowerShell SDK: Access Token Proof of Possession (AT PoP) Capability + +## Overview + +This README provides comprehensive details on the Access Token Proof of Possession (AT PoP) functionality introduced in the Microsoft Graph PowerShell SDK. This feature enhances security by binding tokens to specific HTTP methods and URIs, ensuring they are used only for their intended purposes. + +## Table of Contents + +- [Key Features](#key-features) +- [Installation](#installation) +- [Configuration](#configuration) +- [Usage Examples](#usage-examples) +- [References](#references) + +## Key Features + +- **Access Token Proof of Possession (AT PoP)**: This feature binds tokens to specific HTTP methods and URIs, preventing misuse of tokens by ensuring they are used only for the intended HTTP requests. +- **Updated Dependencies**: Compatibility improvements with recent library changes. +- **Enhanced Token Acquisition Options**: Users can now specify the HTTP method and URI during token acquisition to further secure token usage. + +### Token acquisition behaviors + +| Condition | Unbound (default) | Bound (PoP) | +|-----------|-----------|-----------| +| First sign-in | New token, interactive| New token, interactive | +| Existing token, same URI | No new token, silent | No new token, silent | +| Existing token, different URI | No new token, silent | New token, silent | +| Existing expired token, below max token refreshes | New token, silent | New token, silent | +| Existing expired token, exceeded max refreshes | New token, interactive | New token, interactive | + +## Installation + +To install the Microsoft Graph PowerShell SDK with the latest updates, use the following command: + +```powershell +Install-Module -Name Microsoft.Graph -AllowClobber -Force +``` + +Ensure you are using the latest version to access the AT PoP functionality. + +## Configuration + +### Enabling Access Token Proof of Possession + +To enable AT PoP, configure the Microsoft Graph SDK options as follows: + +```powershell +Set-MgGraphOption -EnableATPoP $true + +Connect-MgGraph +``` + +This configuration ensures that the acquired token is only valid for the specified HTTP method and URI. + +## Usage Examples + +### Example 1: + +```powershell +Set-MgGraphOption -EnableATPoP $true + +Connect-MgGraph + +Invoke-MgGraphRequest -Method GET https://graph.microsoft.com/v1.0/me -Debug +``` + +### Example 2: + +```powershell +Set-MgGraphOption -EnableATPoP $true + +Connect-MgGraph + +Invoke-MgGraphRequest -Uri "https://graph.microsoft.com/v1.0/me/sendMail" -Method POST -Debug +``` + +## References + +This README provides a detailed guide on the new AT PoP functionality, offering users the ability to secure their token usage effectively. If you have any questions or need further assistance, please refer to the official [Microsoft Graph PowerShell SDK documentation](https://docs.microsoft.com/en-us/powershell/microsoftgraph/). diff --git a/docs/authentication.md b/docs/authentication.md index 6cfbfda695f..4d587898495 100644 --- a/docs/authentication.md +++ b/docs/authentication.md @@ -112,6 +112,26 @@ When using `-AccessToken`, we won't have access to the refresh token and the cli Before using the provided `-AccessToken` to get Microsoft Graph resources, customers should ensure that the access token has the necessary scopes/ permissions needed to access/modify a resource. +### Access Token Proof of Possession (AT PoP) + +AT PoP is a security mechanism that binds an access token to a cryptographic key that only the token requestor has. This prevents unauthorized use of the token by malicious actors. AT PoP enhances data protection, reduces token replay attacks, and enables fine-grained authorization policies. + +Note: AT PoP requires Web Account Manager (WAM) to function. + +Microsoft Graph PowerShell module supports AT PoP in the following scenario: + +- To enable AT PoP on supported devices + +```PowerShell +Set-MgGraphOption -EnableATPoP $true +``` + +- To disable AT PoP on supported devices + +```PowerShell +Set-MgGraphOption -EnableATPoP $false +``` + ## Web Account Manager (WAM) WAM is a Windows 10+ component that acts as an authentication broker allowing the users of an app benefit from integration with accounts known to Windows, such as the account already signed into an active Windows session. diff --git a/src/Authentication/Authentication.Core/Common/GraphSession.cs b/src/Authentication/Authentication.Core/Common/GraphSession.cs index 6b893d4d1c4..f0941608403 100644 --- a/src/Authentication/Authentication.Core/Common/GraphSession.cs +++ b/src/Authentication/Authentication.Core/Common/GraphSession.cs @@ -56,6 +56,11 @@ public class GraphSession : IGraphSession /// public IGraphOption GraphOption { get; set; } + /// + /// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts. + /// + public IGraphRequestPopContext GraphRequestPopContext { get; set; } + /// /// Represents a collection of Microsoft Graph PowerShell meta-info. /// diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphOptions.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphOptions.cs index 3dd2483694f..de7f68f15d8 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IGraphOptions.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IGraphOptions.cs @@ -2,14 +2,11 @@ // Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. // ------------------------------------------------------------------------------ -using System; -using System.Security; -using System.Security.Cryptography.X509Certificates; - namespace Microsoft.Graph.PowerShell.Authentication { public interface IGraphOption { bool EnableWAMForMSGraph { get; set; } + bool EnableATPoPForMSGraph { get; set; } } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs new file mode 100644 index 00000000000..7a154ea9f4d --- /dev/null +++ b/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs @@ -0,0 +1,21 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +using Azure.Core; +using Azure.Identity; +using System; +using System.Net.Http; + +namespace Microsoft.Graph.PowerShell.Authentication +{ + public interface IGraphRequestPopContext + { + Uri Uri { get; set; } + HttpMethod HttpMethod { get; set; } + AccessToken AccessToken { get; set; } + PopTokenRequestContext PopTokenContext { get; set; } + Request Request { get; set; } + InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } + } +} \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs index c7f8ba48c3d..fc0fd19cbb5 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs @@ -12,5 +12,6 @@ public interface IGraphSession IDataStore DataStore { get; set; } IRequestContext RequestContext { get; set; } IGraphOption GraphOption { get; set; } + IGraphRequestPopContext GraphRequestPopContext { get; set; } } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj index 09fefaae4df..36545733ff8 100644 --- a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj +++ b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj @@ -1,4 +1,4 @@ - + 9.0 diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index 71919594c4a..60c23f93537 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------------ using Azure.Core; using Azure.Core.Diagnostics; +using Azure.Core.Pipeline; using Azure.Identity; using Azure.Identity.Broker; using Microsoft.Graph.Authentication; @@ -14,6 +15,8 @@ using System.Globalization; using System.IO; using System.Linq; +using System.Net.Http; +using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -114,23 +117,43 @@ private static async Task GetInteractiveBrowserCre { if (authContext is null) throw new AuthenticationException(ErrorConstants.Message.MissingAuthContext); - var interactiveOptions = IsWamSupported() ? new InteractiveBrowserCredentialBrokerOptions(WindowHandleUtlities.GetConsoleOrTerminalWindow()) : new InteractiveBrowserCredentialOptions(); + var interactiveOptions = IsWamSupported() ? + new InteractiveBrowserCredentialBrokerOptions(WindowHandleUtlities.GetConsoleOrTerminalWindow()) : + new InteractiveBrowserCredentialOptions(); interactiveOptions.ClientId = authContext.ClientId; interactiveOptions.TenantId = authContext.TenantId ?? "common"; interactiveOptions.AuthorityHost = new Uri(GetAuthorityUrl(authContext)); interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext); + var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions); + if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) + { + GraphSession.Instance.GraphRequestPopContext.PopTokenContext = await CreatePopTokenRequestContext(authContext); + GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential = interactiveBrowserCredential; + } + if (!File.Exists(Constants.AuthRecordPath)) { AuthenticationRecord authRecord; - var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions); if (IsWamSupported()) { - authRecord = await Task.Run(() => + // Adding a scenario to account for Access Token Proof of Possession + if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) { - // Run the thread in MTA. - return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken); - }); + authRecord = await Task.Run(() => + { + // Run the thread in MTA. + return interactiveBrowserCredential.AuthenticateAsync(GraphSession.Instance.GraphRequestPopContext.PopTokenContext, cancellationToken); + }); + } + else + { + authRecord = await Task.Run(() => + { + // Run the thread in MTA. + return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken); + }); + } } else { @@ -447,5 +470,34 @@ public static Task DeleteAuthRecordAsync() File.Delete(Constants.AuthRecordPath); return Task.CompletedTask; } + + private static async Task CreatePopTokenRequestContext(IAuthContext authContext) + { + // Creating a httpclient that would handle all pop calls + Uri popResourceUri = GraphSession.Instance.GraphRequestPopContext.Uri ?? new Uri("https://graph.microsoft.com/beta/organization"); + HttpClient popHttpClient = new(new HttpClientHandler()); + + // Find the nonce in the WWW-Authenticate header in the response. + var popMethod = GraphSession.Instance.GraphRequestPopContext.HttpMethod ?? HttpMethod.Get; + var popResponse = await popHttpClient.SendAsync(new HttpRequestMessage(popMethod, popResourceUri)); + + // Refresh token logic --- start + var popPipelineOptions = new HttpPipelineOptions(new PopClientOptions() + { + + }); + + var _popPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions()); + GraphSession.Instance.GraphRequestPopContext.Request = _popPipeline.CreateRequest(); + GraphSession.Instance.GraphRequestPopContext.Request.Method = RequestMethod.Parse(popMethod.Method.ToUpper()); + GraphSession.Instance.GraphRequestPopContext.Request.Uri.Reset(popResourceUri); + + // Refresh token logic --- end + var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(popResponse.Headers, "Pop").Nonce, request: GraphSession.Instance.GraphRequestPopContext.Request); + return popContext; + } + } + internal class PopClientOptions : ClientOptions + { } } \ No newline at end of file diff --git a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs index 4e16b462486..b1360636f43 100644 --- a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs +++ b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs @@ -1026,6 +1026,8 @@ private async Task ProcessRecordAsync() try { PrepareSession(); + GraphSession.Instance.GraphRequestPopContext.Uri = Uri; + GraphSession.Instance.GraphRequestPopContext.HttpMethod = GetHttpMethod(Method); var client = HttpHelpers.GetGraphHttpClient(); ValidateRequestUri(); using (var httpRequestMessage = GetRequest(client, Uri)) diff --git a/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs b/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs index ad4f0f76a11..fc4da16a8ed 100644 --- a/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs +++ b/src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs @@ -13,6 +13,9 @@ public class SetMgGraphOption : PSCmdlet { [Parameter] public bool EnableLoginByWAM { get; set; } + + [Parameter] + public bool EnableATPoP { get; set; } protected override void BeginProcessing() { @@ -27,6 +30,11 @@ protected override void ProcessRecord() GraphSession.Instance.GraphOption.EnableWAMForMSGraph = EnableLoginByWAM; WriteDebug($"Signin by Web Account Manager (WAM) is {(EnableLoginByWAM ? "enabled" : "disabled")}."); } + if (this.IsParameterBound(nameof(EnableATPoP))) + { + GraphSession.Instance.GraphOption.EnableATPoPForMSGraph = EnableATPoP; + WriteDebug($"Access Token Proof of Posession (AT-PoP) is {(EnableATPoP ? "enabled" : "disabled")}."); + } File.WriteAllText(Constants.GraphOptionsFilePath, JsonConvert.SerializeObject(GraphSession.Instance.GraphOption, Formatting.Indented)); } diff --git a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs index 4d3d527da14..ec165e5a942 100644 --- a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs +++ b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs @@ -47,7 +47,8 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null) { DataStore = dataStore ?? new DiskDataStore(), RequestContext = new RequestContext(), - GraphOption = graphOptions ?? new GraphOption() + GraphOption = graphOptions ?? new GraphOption(), + GraphRequestPopContext = new GraphRequestPopContext() }; } /// diff --git a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs index e57d74186b3..b78ed1eb59d 100644 --- a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs +++ b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs @@ -3,11 +3,15 @@ // ------------------------------------------------------------------------------ +using Azure.Core; using Microsoft.Graph.Authentication; +using Microsoft.Graph.PowerShell.Authentication.Core.Utilities; using Microsoft.Graph.PowerShell.Authentication.Extensions; +using Microsoft.Identity.Client; using System; using System.Collections.Generic; using System.Linq; +using System.Management.Automation; using System.Net; using System.Net.Http; using System.Net.Http.Headers; @@ -21,6 +25,7 @@ internal class AuthenticationHandler : DelegatingHandler { private const string ClaimsKey = "claims"; private const string BearerAuthenticationScheme = "Bearer"; + private const string PopAuthenticationScheme = "Pop"; private int MaxRetry { get; set; } = 1; public AzureIdentityAccessTokenProvider AuthenticationProvider { get; set; } @@ -45,6 +50,12 @@ protected override async Task SendAsync(HttpRequestMessage HttpResponseMessage response = await base.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); + // Continuous nonce extraction on each request + if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) + { + GraphSession.Instance.GraphRequestPopContext.PopTokenContext = new PopTokenRequestContext(GraphSession.Instance.AuthContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme).Nonce, request: GraphSession.Instance.GraphRequestPopContext.Request); + } + // Check if response is a 401 & is not a streamed body (is buffered) if (response.StatusCode == HttpStatusCode.Unauthorized && httpRequestMessage.IsBuffered()) { @@ -63,9 +74,24 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag { if (AuthenticationProvider != null) { - var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false); - if (!string.IsNullOrEmpty(accessToken)) - httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken); + if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) + { + GraphSession.Instance.GraphRequestPopContext.Request.Method = RequestMethod.Parse(httpRequestMessage.Method.Method.ToUpper()); + GraphSession.Instance.GraphRequestPopContext.Request.Uri.Reset(httpRequestMessage.RequestUri); + foreach (var header in httpRequestMessage.Headers) + { + GraphSession.Instance.GraphRequestPopContext.Request.Headers.Add(header.Key, header.Value.First()); + } + + var accessToken = await GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestPopContext.PopTokenContext, cancellationToken).ConfigureAwait(false); + httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token); + } + else + { + var accessToken = await AuthenticationProvider.GetAuthorizationTokenAsync(httpRequestMessage.RequestUri, additionalAuthenticationContext, cancellationToken: cancellationToken).ConfigureAwait(false); + if (!string.IsNullOrEmpty(accessToken)) + httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(BearerAuthenticationScheme, accessToken); + } } } diff --git a/src/Authentication/Authentication/Models/GraphOption.cs b/src/Authentication/Authentication/Models/GraphOption.cs index d8c48d7f70a..e8c83e6ef01 100644 --- a/src/Authentication/Authentication/Models/GraphOption.cs +++ b/src/Authentication/Authentication/Models/GraphOption.cs @@ -9,6 +9,7 @@ namespace Microsoft.Graph.PowerShell.Authentication internal class GraphOption : IGraphOption { public bool EnableWAMForMSGraph { get; set; } + public bool EnableATPoPForMSGraph { get; set; } } } \ No newline at end of file diff --git a/src/Authentication/Authentication/Models/GraphRequestPopContext.cs b/src/Authentication/Authentication/Models/GraphRequestPopContext.cs new file mode 100644 index 00000000000..0092543bb3e --- /dev/null +++ b/src/Authentication/Authentication/Models/GraphRequestPopContext.cs @@ -0,0 +1,23 @@ +// ------------------------------------------------------------------------------ +// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. +// ------------------------------------------------------------------------------ + +using Azure.Core; +using Azure.Identity; +using System; +using System.IO; +using System.Net.Http; + +namespace Microsoft.Graph.PowerShell.Authentication +{ + internal class GraphRequestPopContext : IGraphRequestPopContext + { + public Uri Uri { get; set; } + public HttpMethod HttpMethod { get; set; } + public AccessToken AccessToken { get; set; } + public PopTokenRequestContext PopTokenContext { get; set; } + public Request Request { get; set; } + public InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } + } + +} \ No newline at end of file diff --git a/src/Authentication/Authentication/test/Get-MgGraphOption.Tests.ps1 b/src/Authentication/Authentication/test/Get-MgGraphOption.Tests.ps1 index 786105807c7..2708d2f7f41 100644 --- a/src/Authentication/Authentication/test/Get-MgGraphOption.Tests.ps1 +++ b/src/Authentication/Authentication/test/Get-MgGraphOption.Tests.ps1 @@ -13,7 +13,7 @@ Describe "Get-MgGraphOption Command" { $GetMgGraphOptionCommand = Get-Command Set-MgGraphOption $GetMgGraphOptionCommand | Should -Not -BeNullOrEmpty $GetMgGraphOptionCommand.ParameterSets | Should -HaveCount 1 - $GetMgGraphOptionCommand.ParameterSets.Parameters | Should -HaveCount 13 # PS common parameters. + $GetMgGraphOptionCommand.ParameterSets.Parameters | Should -HaveCount 14 # PS common parameters. } It 'Executes successfully' { diff --git a/src/Authentication/Authentication/test/Set-MgGraphOption.Tests.ps1 b/src/Authentication/Authentication/test/Set-MgGraphOption.Tests.ps1 index 6a2cb60693a..775cdcaa629 100644 --- a/src/Authentication/Authentication/test/Set-MgGraphOption.Tests.ps1 +++ b/src/Authentication/Authentication/test/Set-MgGraphOption.Tests.ps1 @@ -9,14 +9,14 @@ Describe "Set-MgGraphOption" { Import-Module $ModulePath -Force -ErrorAction SilentlyContinue } Context "When executing the command" { - it 'Should have one ParameterSets' { + it 'Should have two ParameterSets' { $SetMgGraphOptionCommand = Get-Command Set-MgGraphOption $SetMgGraphOptionCommand | Should -Not -BeNullOrEmpty $SetMgGraphOptionCommand.ParameterSets | Should -HaveCount 1 - $SetMgGraphOptionCommand.ParameterSets.Parameters | Should -HaveCount 13 # PS common parameters. + $SetMgGraphOptionCommand.ParameterSets.Parameters | Should -HaveCount 14 # PS common parameters. } - It 'Executes successfully whren toggling WAM on' { + It 'Executes successfully when toggling WAM on' { { Set-MgGraphOption -EnableLoginByWAM $true -Debug | Out-Null } | Should -Not -Be $null { Set-MgGraphOption -EnableLoginByWAM $true -ErrorAction SilentlyContinue } | Should -Not -Throw } @@ -25,5 +25,15 @@ Describe "Set-MgGraphOption" { { Set-MgGraphOption -EnableLoginByWAM $false -Debug | Out-Null } | Should -Not -Be $null { Set-MgGraphOption -EnableLoginByWAM $false -ErrorAction SilentlyContinue } | Should -Not -Throw } + + It 'Executes successfully when toggling AT PoP on' { + { Set-MgGraphOption -EnableATPoP $true -Debug | Out-Null } | Should -Not -Be $null + { Set-MgGraphOption -EnableATPoP $true -ErrorAction SilentlyContinue } | Should -Not -Throw + } + + It 'Executes successfully when toggling AT PoP off' { + { Set-MgGraphOption -EnableATPoP $false -Debug | Out-Null } | Should -Not -Be $null + { Set-MgGraphOption -EnableATPoP $false -ErrorAction SilentlyContinue } | Should -Not -Throw + } } } \ No newline at end of file diff --git a/src/Authentication/docs/Get-MgGraphOption.md b/src/Authentication/docs/Get-MgGraphOption.md index f4ef0a7c258..7678d11d798 100644 --- a/src/Authentication/docs/Get-MgGraphOption.md +++ b/src/Authentication/docs/Get-MgGraphOption.md @@ -1,7 +1,7 @@ --- external help file: Microsoft.Graph.Authentication.dll-Help.xml Module Name: Microsoft.Graph.Authentication -online version: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/get-mgenvironment +online version: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/get-mggraphoption schema: 2.0.0 --- diff --git a/src/Authentication/docs/Set-MgGraphOption.md b/src/Authentication/docs/Set-MgGraphOption.md index 85ee3e9fca6..99ef8e95a29 100644 --- a/src/Authentication/docs/Set-MgGraphOption.md +++ b/src/Authentication/docs/Set-MgGraphOption.md @@ -1,7 +1,7 @@ --- external help file: Microsoft.Graph.Authentication.dll-Help.xml Module Name: Microsoft.Graph.Authentication -online version: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/set-mgenvironment +online version: https://learn.microsoft.com/en-us/powershell/module/microsoft.graph.authentication/set-mggraphoption schema: 2.0.0 --- @@ -15,6 +15,9 @@ Sets global configurations that apply to the SDK. For example, toggle Web Accoun ``` Set-MgGraphOption [-EnableLoginByWAM ] [] ``` +``` +Set-MgGraphOption [-EnableATPoP ] [] +``` ## DESCRIPTION Sets global configurations that apply to the SDK. For example, toggle Web Account Manager (WAM) support. @@ -28,11 +31,21 @@ PS C:\> Set-MgGraphOption -EnableLoginByWAM $True Sets web account manager support +### Example 2: Set access token proof of possession support +```powershell +PS C:\> Set-MgGraphOption -EnableATPoP $True +``` + + Sets access token proof of possession support + ## PARAMETERS ### -EnableLoginByWAM {{ Fill EnableLoginByWAM Description }} +### -EnableATPoP +{{ Fill EnableATPoP Description }} + ```yaml Type: Boolean Parameter Sets: (All) diff --git a/src/Authentication/examples/Set-MgGraphOption.md b/src/Authentication/examples/Set-MgGraphOption.md index 055431b5920..afc23d97cb1 100644 --- a/src/Authentication/examples/Set-MgGraphOption.md +++ b/src/Authentication/examples/Set-MgGraphOption.md @@ -2,4 +2,10 @@ ```powershell PS C:\> Set-MgGraphOption -EnableLoginByWAM $True ``` - Sets web account manager support \ No newline at end of file + Sets web account manager support + +### Example 2: Set access token proof of possession support +```powershell +PS C:\> Set-MgGraphOption -EnableATPoP $True +``` + Sets access token proof of possession support \ No newline at end of file From 16720291def54b98f84eaefc1ed223ad8eba1005 Mon Sep 17 00:00:00 2001 From: "Taofeek F. Obafemi-Babatunde" Date: Mon, 15 Jul 2024 13:30:50 -0700 Subject: [PATCH 3/9] Resolving comments regarding static instance --- .../Interfaces/IGraphRequestPopContext.cs | 4 ++-- .../Utilities/AuthenticationHelpers.cs | 15 ++++++++------- .../Handlers/AuthenticationHandler.cs | 12 +++++++----- .../Models/GraphRequestPopContext.cs | 4 ++-- 4 files changed, 19 insertions(+), 16 deletions(-) diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs index 7a154ea9f4d..61551edb318 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------------ using Azure.Core; +using Azure.Core.Pipeline; using Azure.Identity; using System; using System.Net.Http; @@ -14,8 +15,7 @@ public interface IGraphRequestPopContext Uri Uri { get; set; } HttpMethod HttpMethod { get; set; } AccessToken AccessToken { get; set; } - PopTokenRequestContext PopTokenContext { get; set; } - Request Request { get; set; } + HttpPipeline PopPipeline { get; set; } InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index 60c23f93537..fdb2dc8cc98 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -126,9 +126,10 @@ private static async Task GetInteractiveBrowserCre interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext); var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions); + var popTokenRequestContext = new PopTokenRequestContext(); if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) { - GraphSession.Instance.GraphRequestPopContext.PopTokenContext = await CreatePopTokenRequestContext(authContext); + popTokenRequestContext = await CreatePopTokenRequestContext(authContext); GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential = interactiveBrowserCredential; } @@ -143,7 +144,7 @@ private static async Task GetInteractiveBrowserCre authRecord = await Task.Run(() => { // Run the thread in MTA. - return interactiveBrowserCredential.AuthenticateAsync(GraphSession.Instance.GraphRequestPopContext.PopTokenContext, cancellationToken); + return interactiveBrowserCredential.AuthenticateAsync(popTokenRequestContext, cancellationToken); }); } else @@ -487,13 +488,13 @@ private static async Task CreatePopTokenRequestContext(I }); - var _popPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions()); - GraphSession.Instance.GraphRequestPopContext.Request = _popPipeline.CreateRequest(); - GraphSession.Instance.GraphRequestPopContext.Request.Method = RequestMethod.Parse(popMethod.Method.ToUpper()); - GraphSession.Instance.GraphRequestPopContext.Request.Uri.Reset(popResourceUri); + GraphSession.Instance.GraphRequestPopContext.PopPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions()); + var popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest(); + popRequest.Method = RequestMethod.Parse(popMethod.Method.ToUpper()); + popRequest.Uri.Reset(popResourceUri); // Refresh token logic --- end - var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(popResponse.Headers, "Pop").Nonce, request: GraphSession.Instance.GraphRequestPopContext.Request); + var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(popResponse.Headers, "Pop").Nonce, request: popRequest); return popContext; } } diff --git a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs index b78ed1eb59d..362a262abfc 100644 --- a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs +++ b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs @@ -27,6 +27,8 @@ internal class AuthenticationHandler : DelegatingHandler private const string BearerAuthenticationScheme = "Bearer"; private const string PopAuthenticationScheme = "Pop"; private int MaxRetry { get; set; } = 1; + private PopTokenRequestContext popTokenRequestContext; + private Request popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest(); public AzureIdentityAccessTokenProvider AuthenticationProvider { get; set; } @@ -53,7 +55,7 @@ protected override async Task SendAsync(HttpRequestMessage // Continuous nonce extraction on each request if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) { - GraphSession.Instance.GraphRequestPopContext.PopTokenContext = new PopTokenRequestContext(GraphSession.Instance.AuthContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme).Nonce, request: GraphSession.Instance.GraphRequestPopContext.Request); + popTokenRequestContext = new PopTokenRequestContext(GraphSession.Instance.AuthContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme).Nonce, request: popRequest); } // Check if response is a 401 & is not a streamed body (is buffered) @@ -76,14 +78,14 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag { if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) { - GraphSession.Instance.GraphRequestPopContext.Request.Method = RequestMethod.Parse(httpRequestMessage.Method.Method.ToUpper()); - GraphSession.Instance.GraphRequestPopContext.Request.Uri.Reset(httpRequestMessage.RequestUri); + popRequest.Method = RequestMethod.Parse(httpRequestMessage.Method.Method.ToUpper()); + popRequest.Uri.Reset(httpRequestMessage.RequestUri); foreach (var header in httpRequestMessage.Headers) { - GraphSession.Instance.GraphRequestPopContext.Request.Headers.Add(header.Key, header.Value.First()); + popRequest.Headers.Add(header.Key, header.Value.First()); } - var accessToken = await GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential.GetTokenAsync(GraphSession.Instance.GraphRequestPopContext.PopTokenContext, cancellationToken).ConfigureAwait(false); + var accessToken = await GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential.GetTokenAsync(popTokenRequestContext, cancellationToken).ConfigureAwait(false); httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token); } else diff --git a/src/Authentication/Authentication/Models/GraphRequestPopContext.cs b/src/Authentication/Authentication/Models/GraphRequestPopContext.cs index 0092543bb3e..4be69f16e7e 100644 --- a/src/Authentication/Authentication/Models/GraphRequestPopContext.cs +++ b/src/Authentication/Authentication/Models/GraphRequestPopContext.cs @@ -3,6 +3,7 @@ // ------------------------------------------------------------------------------ using Azure.Core; +using Azure.Core.Pipeline; using Azure.Identity; using System; using System.IO; @@ -15,8 +16,7 @@ internal class GraphRequestPopContext : IGraphRequestPopContext public Uri Uri { get; set; } public HttpMethod HttpMethod { get; set; } public AccessToken AccessToken { get; set; } - public PopTokenRequestContext PopTokenContext { get; set; } - public Request Request { get; set; } + public HttpPipeline PopPipeline { get; set; } public InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } } From 93d2b817e529823419d1ac08cf7370512efd6eb2 Mon Sep 17 00:00:00 2001 From: "Taofeek F. Obafemi-Babatunde" Date: Thu, 25 Jul 2024 14:37:37 -0700 Subject: [PATCH 4/9] resolving issues with PS 7.4.4 --- src/Authentication/Authentication/Helpers/HttpHelpers.cs | 1 + .../Authentication/Microsoft.Graph.Authentication.nuspec | 2 ++ 2 files changed, 3 insertions(+) diff --git a/src/Authentication/Authentication/Helpers/HttpHelpers.cs b/src/Authentication/Authentication/Helpers/HttpHelpers.cs index e54612ba0b7..26d5a5ead90 100644 --- a/src/Authentication/Authentication/Helpers/HttpHelpers.cs +++ b/src/Authentication/Authentication/Helpers/HttpHelpers.cs @@ -54,6 +54,7 @@ private static HttpClient GetGraphHttpClient(AzureIdentityAccessTokenProvider au new NationalCloudHandler(), new ODataQueryOptionsHandler(), new HttpVersionHandler(), + //new CompressionHandler(), new RetryHandler(new RetryHandlerOption{ Delay = requestContext.RetryDelay, MaxRetry = requestContext.MaxRetry, diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec index 6c2e698b026..c8cf6124e3e 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec @@ -50,12 +50,14 @@ + + From bd5c0504a059a21100504d6e1b4f87d8fd727fa7 Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Fri, 26 Jul 2024 12:51:44 +0300 Subject: [PATCH 5/9] Fixed pester test expected messages --- .../Authentication/test/Connect-MgGraph.Tests.ps1 | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 index 382ad62a98b..1b09fbfa6c1 100644 --- a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 @@ -69,7 +69,7 @@ Describe 'Connect-MgGraph In Delegated Mode' { Describe 'Connect-MgGraph In Environment Variable Mode' { It 'Should throw exception when supported environment variables are not specified' { - { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*EnvironmentCredential authentication unavailable. Environment variables are not fully configured*" + { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*ClientSecretCredential authentication failed*" } It 'Should attempt to use configured environment variables' { { @@ -77,7 +77,7 @@ Describe 'Connect-MgGraph In Environment Variable Mode' { $Env:AZURE_CLIENT_SECRET = "Not_Valid" $Env:AZURE_TENANT_ID = "common" Connect-MgGraph -EnvironmentVariable -ErrorAction Stop - } | Should -Throw -ExpectedMessage "ClientSecretCredential authentication failed: " + } | Should -Throw -ExpectedMessage "*ClientSecretCredential authentication failed*" } } @@ -95,7 +95,7 @@ Describe 'Connect-MgGraph In App Mode' { Describe 'Connect-MgGraph Dependency Resolution' { It 'Should load Mg module side by side with Az module.' { { Connect-AzAccount -ApplicationId $RandomClientId -CertificateThumbprint "Invalid" -Tenant "Invalid" -ErrorAction Stop } | Should -Throw -ExpectedMessage "*Could not find tenant id*" - { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "DeviceCodeCredential authentication failed: " + { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "*DeviceCodeCredential authentication failed*" } } From 0d7124a5b45a61f13eb665611a0e74dc57ffcb8e Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Fri, 26 Jul 2024 13:52:11 +0300 Subject: [PATCH 6/9] Updated expected message --- src/Authentication/docs/Connect-MgGraph.md | 46 ++++++++++++++++++---- 1 file changed, 39 insertions(+), 7 deletions(-) diff --git a/src/Authentication/docs/Connect-MgGraph.md b/src/Authentication/docs/Connect-MgGraph.md index 9ef5d0af7ce..5e793b1f142 100644 --- a/src/Authentication/docs/Connect-MgGraph.md +++ b/src/Authentication/docs/Connect-MgGraph.md @@ -16,38 +16,40 @@ Microsoft Graph PowerShell supports two types of authentication: delegated and a ``` Connect-MgGraph [[-Scopes] ] [[-ClientId] ] [-TenantId ] [-ContextScope ] [-Environment ] [-UseDeviceCode] [-ClientTimeout ] [-NoWelcome] - [] + [-ProgressAction ] [] ``` ### AppCertificateParameterSet ``` Connect-MgGraph [-ClientId] [[-CertificateSubjectName] ] [[-CertificateThumbprint] ] - [-Certificate ] [-TenantId ] [-ContextScope ] [-Environment ] - [-ClientTimeout ] [-NoWelcome] [] + [-SendCertificateChain ] [-Certificate ] [-TenantId ] + [-ContextScope ] [-Environment ] [-ClientTimeout ] [-NoWelcome] + [-ProgressAction ] [] ``` ### IdentityParameterSet ``` Connect-MgGraph [[-ClientId] ] [-ContextScope ] [-Environment ] - [-ClientTimeout ] [-Identity] [-NoWelcome] [] + [-ClientTimeout ] [-Identity] [-NoWelcome] [-ProgressAction ] [] ``` ### AppSecretCredentialParameterSet ``` Connect-MgGraph [-ClientSecretCredential ] [-TenantId ] [-ContextScope ] - [-Environment ] [-ClientTimeout ] [-NoWelcome] [] + [-Environment ] [-ClientTimeout ] [-NoWelcome] [-ProgressAction ] + [] ``` ### AccessTokenParameterSet ``` Connect-MgGraph [-AccessToken] [-Environment ] [-ClientTimeout ] [-NoWelcome] - [] + [-ProgressAction ] [] ``` ### EnvironmentVariableParameterSet ``` Connect-MgGraph [-ContextScope ] [-Environment ] [-ClientTimeout ] - [-EnvironmentVariable] [-NoWelcome] [] + [-EnvironmentVariable] [-NoWelcome] [-ProgressAction ] [] ``` ## DESCRIPTION @@ -351,6 +353,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -ProgressAction +{{ Fill ProgressAction Description }} + +```yaml +Type: ActionPreference +Parameter Sets: (All) +Aliases: proga + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -Scopes An array of delegated permissions to consent to. @@ -366,6 +383,21 @@ Accept pipeline input: False Accept wildcard characters: False ``` +### -SendCertificateChain +Include x5c header in client claims when acquiring a token to enable subject name / issuer based authentication using given certificate. + +```yaml +Type: Boolean +Parameter Sets: AppCertificateParameterSet +Aliases: + +Required: False +Position: Named +Default value: None +Accept pipeline input: False +Accept wildcard characters: False +``` + ### -TenantId The id of the tenant to connect to. You can also use this parameter to specify your sign-in audience. From 10586259b9e9541468190cfb674b441cc8da77aa Mon Sep 17 00:00:00 2001 From: Microsoft Graph DevX Tooling Date: Fri, 26 Jul 2024 14:02:16 +0300 Subject: [PATCH 7/9] Updated error message --- .../Authentication/test/Connect-MgGraph.Tests.ps1 | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 index 1b09fbfa6c1..a98601d51ba 100644 --- a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 @@ -69,7 +69,7 @@ Describe 'Connect-MgGraph In Delegated Mode' { Describe 'Connect-MgGraph In Environment Variable Mode' { It 'Should throw exception when supported environment variables are not specified' { - { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*ClientSecretCredential authentication failed*" + { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*EnvironmentCredential authentication unavailable*" } It 'Should attempt to use configured environment variables' { { From 91cc021a0de308f5ef9ee02a1c70a5db42d2e7c2 Mon Sep 17 00:00:00 2001 From: "Taofeek F. Obafemi-Babatunde" Date: Mon, 10 Feb 2025 10:41:43 -0800 Subject: [PATCH 8/9] Updating docs --- .../Authentication/Helpers/HttpHelpers.cs | 1 - .../test/Connect-MgGraph.Tests.ps1 | 6 +++--- src/Authentication/docs/Connect-MgGraph.md | 16 +++++++--------- 3 files changed, 10 insertions(+), 13 deletions(-) diff --git a/src/Authentication/Authentication/Helpers/HttpHelpers.cs b/src/Authentication/Authentication/Helpers/HttpHelpers.cs index 26d5a5ead90..e54612ba0b7 100644 --- a/src/Authentication/Authentication/Helpers/HttpHelpers.cs +++ b/src/Authentication/Authentication/Helpers/HttpHelpers.cs @@ -54,7 +54,6 @@ private static HttpClient GetGraphHttpClient(AzureIdentityAccessTokenProvider au new NationalCloudHandler(), new ODataQueryOptionsHandler(), new HttpVersionHandler(), - //new CompressionHandler(), new RetryHandler(new RetryHandlerOption{ Delay = requestContext.RetryDelay, MaxRetry = requestContext.MaxRetry, diff --git a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 index a98601d51ba..382ad62a98b 100644 --- a/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 +++ b/src/Authentication/Authentication/test/Connect-MgGraph.Tests.ps1 @@ -69,7 +69,7 @@ Describe 'Connect-MgGraph In Delegated Mode' { Describe 'Connect-MgGraph In Environment Variable Mode' { It 'Should throw exception when supported environment variables are not specified' { - { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*EnvironmentCredential authentication unavailable*" + { Connect-MgGraph -EnvironmentVariable -ErrorAction Stop } | Should -Throw -ExpectedMessage "*EnvironmentCredential authentication unavailable. Environment variables are not fully configured*" } It 'Should attempt to use configured environment variables' { { @@ -77,7 +77,7 @@ Describe 'Connect-MgGraph In Environment Variable Mode' { $Env:AZURE_CLIENT_SECRET = "Not_Valid" $Env:AZURE_TENANT_ID = "common" Connect-MgGraph -EnvironmentVariable -ErrorAction Stop - } | Should -Throw -ExpectedMessage "*ClientSecretCredential authentication failed*" + } | Should -Throw -ExpectedMessage "ClientSecretCredential authentication failed: " } } @@ -95,7 +95,7 @@ Describe 'Connect-MgGraph In App Mode' { Describe 'Connect-MgGraph Dependency Resolution' { It 'Should load Mg module side by side with Az module.' { { Connect-AzAccount -ApplicationId $RandomClientId -CertificateThumbprint "Invalid" -Tenant "Invalid" -ErrorAction Stop } | Should -Throw -ExpectedMessage "*Could not find tenant id*" - { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "*DeviceCodeCredential authentication failed*" + { Connect-MgGraph -TenantId "thisdomaindoesnotexist.com" -ErrorAction Stop -UseDeviceAuthentication } | Should -Throw -ExpectedMessage "DeviceCodeCredential authentication failed: " } } diff --git a/src/Authentication/docs/Connect-MgGraph.md b/src/Authentication/docs/Connect-MgGraph.md index 5e793b1f142..57730953e7e 100644 --- a/src/Authentication/docs/Connect-MgGraph.md +++ b/src/Authentication/docs/Connect-MgGraph.md @@ -16,40 +16,38 @@ Microsoft Graph PowerShell supports two types of authentication: delegated and a ``` Connect-MgGraph [[-Scopes] ] [[-ClientId] ] [-TenantId ] [-ContextScope ] [-Environment ] [-UseDeviceCode] [-ClientTimeout ] [-NoWelcome] - [-ProgressAction ] [] + [] ``` ### AppCertificateParameterSet ``` Connect-MgGraph [-ClientId] [[-CertificateSubjectName] ] [[-CertificateThumbprint] ] - [-SendCertificateChain ] [-Certificate ] [-TenantId ] - [-ContextScope ] [-Environment ] [-ClientTimeout ] [-NoWelcome] - [-ProgressAction ] [] + [-Certificate ] [-TenantId ] [-ContextScope ] [-Environment ] + [-ClientTimeout ] [-NoWelcome] [] ``` ### IdentityParameterSet ``` Connect-MgGraph [[-ClientId] ] [-ContextScope ] [-Environment ] - [-ClientTimeout ] [-Identity] [-NoWelcome] [-ProgressAction ] [] + [-ClientTimeout ] [-Identity] [-NoWelcome] [] ``` ### AppSecretCredentialParameterSet ``` Connect-MgGraph [-ClientSecretCredential ] [-TenantId ] [-ContextScope ] - [-Environment ] [-ClientTimeout ] [-NoWelcome] [-ProgressAction ] - [] + [-Environment ] [-ClientTimeout ] [-NoWelcome] [] ``` ### AccessTokenParameterSet ``` Connect-MgGraph [-AccessToken] [-Environment ] [-ClientTimeout ] [-NoWelcome] - [-ProgressAction ] [] + [] ``` ### EnvironmentVariableParameterSet ``` Connect-MgGraph [-ContextScope ] [-Environment ] [-ClientTimeout ] - [-EnvironmentVariable] [-NoWelcome] [-ProgressAction ] [] + [-EnvironmentVariable] [-NoWelcome] [] ``` ## DESCRIPTION From 0fe01a6f1345600b09af8da25185b25c7b6ae5e8 Mon Sep 17 00:00:00 2001 From: taofeeko Date: Fri, 21 Nov 2025 09:00:20 -0800 Subject: [PATCH 9/9] Rebasing to keep up to date --- .../Common/GraphSession.cs | 5 - .../Interfaces/IGraphRequestPopContext.cs | 21 ---- .../Interfaces/IGraphSession.cs | 1 - ...Microsoft.Graph.Authentication.Core.csproj | 8 +- .../Utilities/AuthenticationHelpers.cs | 58 +-------- .../Cmdlets/InvokeMgGraphRequest.cs | 2 - .../Common/GraphSessionInitializer.cs | 3 +- .../Handlers/AuthenticationHandler.cs | 114 ++++++++++++++++-- .../Microsoft.Graph.Authentication.nuspec | 2 - .../Models/GraphRequestPopContext.cs | 23 ---- 10 files changed, 112 insertions(+), 125 deletions(-) delete mode 100644 src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs delete mode 100644 src/Authentication/Authentication/Models/GraphRequestPopContext.cs diff --git a/src/Authentication/Authentication.Core/Common/GraphSession.cs b/src/Authentication/Authentication.Core/Common/GraphSession.cs index f0941608403..6b893d4d1c4 100644 --- a/src/Authentication/Authentication.Core/Common/GraphSession.cs +++ b/src/Authentication/Authentication.Core/Common/GraphSession.cs @@ -56,11 +56,6 @@ public class GraphSession : IGraphSession /// public IGraphOption GraphOption { get; set; } - /// - /// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts. - /// - public IGraphRequestPopContext GraphRequestPopContext { get; set; } - /// /// Represents a collection of Microsoft Graph PowerShell meta-info. /// diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs deleted file mode 100644 index 61551edb318..00000000000 --- a/src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs +++ /dev/null @@ -1,21 +0,0 @@ -// ------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. -// ------------------------------------------------------------------------------ - -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.Identity; -using System; -using System.Net.Http; - -namespace Microsoft.Graph.PowerShell.Authentication -{ - public interface IGraphRequestPopContext - { - Uri Uri { get; set; } - HttpMethod HttpMethod { get; set; } - AccessToken AccessToken { get; set; } - HttpPipeline PopPipeline { get; set; } - InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } - } -} \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs b/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs index fc0fd19cbb5..c7f8ba48c3d 100644 --- a/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs +++ b/src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs @@ -12,6 +12,5 @@ public interface IGraphSession IDataStore DataStore { get; set; } IRequestContext RequestContext { get; set; } IGraphOption GraphOption { get; set; } - IGraphRequestPopContext GraphRequestPopContext { get; set; } } } \ No newline at end of file diff --git a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj index 36545733ff8..16610b51c96 100644 --- a/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj +++ b/src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj @@ -1,10 +1,10 @@ - + 9.0 netstandard2.0;net6.0;net472 Microsoft.Graph.PowerShell.Authentication.Core - 2.31.0 + 2.32.0 true @@ -15,7 +15,9 @@ - + + + diff --git a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs index fdb2dc8cc98..23dbe2a5322 100644 --- a/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs +++ b/src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs @@ -15,8 +15,6 @@ using System.Globalization; using System.IO; using System.Linq; -using System.Net.Http; -using System.Net.Http.Headers; using System.Security.Cryptography.X509Certificates; using System.Threading; using System.Threading.Tasks; @@ -126,35 +124,16 @@ private static async Task GetInteractiveBrowserCre interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext); var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions); - var popTokenRequestContext = new PopTokenRequestContext(); - if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) - { - popTokenRequestContext = await CreatePopTokenRequestContext(authContext); - GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential = interactiveBrowserCredential; - } - if (!File.Exists(Constants.AuthRecordPath)) { AuthenticationRecord authRecord; if (IsWamSupported()) { - // Adding a scenario to account for Access Token Proof of Possession - if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) - { - authRecord = await Task.Run(() => - { - // Run the thread in MTA. - return interactiveBrowserCredential.AuthenticateAsync(popTokenRequestContext, cancellationToken); - }); - } - else + authRecord = await Task.Run(() => { - authRecord = await Task.Run(() => - { - // Run the thread in MTA. - return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken); - }); - } + // Run the thread in MTA. + return interactiveBrowserCredential.AuthenticateAsync(new TokenRequestContext(authContext.Scopes), cancellationToken); + }); } else { @@ -471,34 +450,5 @@ public static Task DeleteAuthRecordAsync() File.Delete(Constants.AuthRecordPath); return Task.CompletedTask; } - - private static async Task CreatePopTokenRequestContext(IAuthContext authContext) - { - // Creating a httpclient that would handle all pop calls - Uri popResourceUri = GraphSession.Instance.GraphRequestPopContext.Uri ?? new Uri("https://graph.microsoft.com/beta/organization"); - HttpClient popHttpClient = new(new HttpClientHandler()); - - // Find the nonce in the WWW-Authenticate header in the response. - var popMethod = GraphSession.Instance.GraphRequestPopContext.HttpMethod ?? HttpMethod.Get; - var popResponse = await popHttpClient.SendAsync(new HttpRequestMessage(popMethod, popResourceUri)); - - // Refresh token logic --- start - var popPipelineOptions = new HttpPipelineOptions(new PopClientOptions() - { - - }); - - GraphSession.Instance.GraphRequestPopContext.PopPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions()); - var popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest(); - popRequest.Method = RequestMethod.Parse(popMethod.Method.ToUpper()); - popRequest.Uri.Reset(popResourceUri); - - // Refresh token logic --- end - var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(popResponse.Headers, "Pop").Nonce, request: popRequest); - return popContext; - } - } - internal class PopClientOptions : ClientOptions - { } } \ No newline at end of file diff --git a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs index b1360636f43..4e16b462486 100644 --- a/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs +++ b/src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs @@ -1026,8 +1026,6 @@ private async Task ProcessRecordAsync() try { PrepareSession(); - GraphSession.Instance.GraphRequestPopContext.Uri = Uri; - GraphSession.Instance.GraphRequestPopContext.HttpMethod = GetHttpMethod(Method); var client = HttpHelpers.GetGraphHttpClient(); ValidateRequestUri(); using (var httpRequestMessage = GetRequest(client, Uri)) diff --git a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs index ec165e5a942..4d3d527da14 100644 --- a/src/Authentication/Authentication/Common/GraphSessionInitializer.cs +++ b/src/Authentication/Authentication/Common/GraphSessionInitializer.cs @@ -47,8 +47,7 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null) { DataStore = dataStore ?? new DiskDataStore(), RequestContext = new RequestContext(), - GraphOption = graphOptions ?? new GraphOption(), - GraphRequestPopContext = new GraphRequestPopContext() + GraphOption = graphOptions ?? new GraphOption() }; } /// diff --git a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs index 362a262abfc..4e6f5e9f7a6 100644 --- a/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs +++ b/src/Authentication/Authentication/Handlers/AuthenticationHandler.cs @@ -27,8 +27,8 @@ internal class AuthenticationHandler : DelegatingHandler private const string BearerAuthenticationScheme = "Bearer"; private const string PopAuthenticationScheme = "Pop"; private int MaxRetry { get; set; } = 1; - private PopTokenRequestContext popTokenRequestContext; - private Request popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest(); + private TokenRequestContext popTokenRequestContext; + private string cachedNonce; public AzureIdentityAccessTokenProvider AuthenticationProvider { get; set; } @@ -52,10 +52,22 @@ protected override async Task SendAsync(HttpRequestMessage HttpResponseMessage response = await base.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false); - // Continuous nonce extraction on each request - if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) + // Extract nonce from API responses for future PoP requests + if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph && IsApiRequest(httpRequestMessage.RequestUri)) { - popTokenRequestContext = new PopTokenRequestContext(GraphSession.Instance.AuthContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme).Nonce, request: popRequest); + try + { + var wwwAuthParams = WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme); + if (wwwAuthParams?.Nonce != null && !string.IsNullOrEmpty(wwwAuthParams.Nonce)) + { + cachedNonce = wwwAuthParams.Nonce; + } + } + catch (Exception ex) + { + System.Diagnostics.Debug.WriteLine($"AuthenticationHandler: Failed to extract PoP nonce: {ex.Message}"); + // Don't throw - nonce extraction failure shouldn't break the response + } } // Check if response is a 401 & is not a streamed body (is buffered) @@ -76,17 +88,48 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag { if (AuthenticationProvider != null) { + // Determine if this is an API request that should use PoP (when enabled) + // vs an authentication request that should always use Bearer + bool isApiRequest = IsApiRequest(httpRequestMessage.RequestUri); + bool shouldUsePoP = GraphSession.Instance.GraphOption.EnableATPoPForMSGraph && isApiRequest; + + // Debug logging for flow routing if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph) { - popRequest.Method = RequestMethod.Parse(httpRequestMessage.Method.Method.ToUpper()); - popRequest.Uri.Reset(httpRequestMessage.RequestUri); - foreach (var header in httpRequestMessage.Headers) + var requestType = isApiRequest ? "API" : "Auth"; + var tokenType = shouldUsePoP ? "PoP" : "Bearer"; + System.Diagnostics.Debug.WriteLine($"AuthenticationHandler: {requestType} request to {httpRequestMessage.RequestUri?.Host} using {tokenType} token"); + } + + if (shouldUsePoP) + { + // API Request with PoP enabled - use PoP tokens ONLY + try + { + // Create proper TokenRequestContext for PoP + // Note: cachedNonce may be null for initial requests - this is expected + popTokenRequestContext = new TokenRequestContext( + scopes: GraphSession.Instance.AuthContext.Scopes, + parentRequestId: null, + claims: additionalAuthenticationContext?.ContainsKey(ClaimsKey) == true ? additionalAuthenticationContext[ClaimsKey]?.ToString() : null, + tenantId: null, + isCaeEnabled: false, + isProofOfPossessionEnabled: true, + proofOfPossessionNonce: cachedNonce // May be null for initial requests + ); + + // Get TokenCredential from existing AuthenticationProvider + var tokenCredential = await AuthenticationHelpers.GetTokenCredentialAsync( + GraphSession.Instance.AuthContext, cancellationToken).ConfigureAwait(false); + + var accessToken = await tokenCredential.GetTokenAsync(popTokenRequestContext, cancellationToken).ConfigureAwait(false); + httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token); + } + catch (Exception ex) when (!(ex is OperationCanceledException)) { - popRequest.Headers.Add(header.Key, header.Value.First()); + // Re-throw with context for PoP-specific failures + throw new AuthenticationException($"Failed to acquire PoP token for {httpRequestMessage.RequestUri}: {ex.Message}", ex); } - - var accessToken = await GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential.GetTokenAsync(popTokenRequestContext, cancellationToken).ConfigureAwait(false); - httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token); } else { @@ -156,5 +199,52 @@ private static async Task DrainAsync(HttpResponseMessage response) } response.Dispose(); } + + /// + /// Determines if the request is an API request that should use PoP when enabled, + /// vs an authentication/token request that should always use Bearer tokens. + /// This method implements the core routing logic for AT-PoP: + /// - Graph API endpoints → PoP tokens (when enabled) + /// - Authentication endpoints → Bearer tokens (always) + /// - Unknown endpoints → Bearer tokens (safe default) + /// + /// The request URI to evaluate + /// True if this is an API request, false if it's an authentication request + private static bool IsApiRequest(Uri requestUri) + { + if (requestUri == null) return false; + + var host = requestUri.Host?.ToLowerInvariant(); + var path = requestUri.AbsolutePath?.ToLowerInvariant(); + + // Microsoft Graph API endpoints that should use PoP + if (host?.Contains("graph.microsoft.com") == true || + host?.Contains("graph.microsoft.us") == true || + host?.Contains("microsoftgraph.chinacloudapi.cn") == true || + host?.Contains("graph.microsoft.de") == true) + { + // Exclude authentication/token endpoints - these should always use Bearer + if (path?.Contains("/oauth2/") == true || + path?.Contains("/token") == true || + path?.Contains("/authorize") == true || + path?.Contains("/devicecode") == true) + { + return false; // Authentication request + } + return true; // API request + } + + // Azure AD/authentication endpoints - never use PoP + if (host?.Contains("login.microsoftonline.com") == true || + host?.Contains("login.microsoft.com") == true || + host?.Contains("login.chinacloudapi.cn") == true || + host?.Contains("login.microsoftonline.de") == true || + host?.Contains("login.microsoftonline.us") == true) + { + return false; // Authentication request + } + + return false; // Default to authentication request for unknown endpoints + } } } \ No newline at end of file diff --git a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec index c8cf6124e3e..6c2e698b026 100644 --- a/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec +++ b/src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec @@ -50,14 +50,12 @@ - - diff --git a/src/Authentication/Authentication/Models/GraphRequestPopContext.cs b/src/Authentication/Authentication/Models/GraphRequestPopContext.cs deleted file mode 100644 index 4be69f16e7e..00000000000 --- a/src/Authentication/Authentication/Models/GraphRequestPopContext.cs +++ /dev/null @@ -1,23 +0,0 @@ -// ------------------------------------------------------------------------------ -// Copyright (c) Microsoft Corporation. All Rights Reserved. Licensed under the MIT License. See License in the project root for license information. -// ------------------------------------------------------------------------------ - -using Azure.Core; -using Azure.Core.Pipeline; -using Azure.Identity; -using System; -using System.IO; -using System.Net.Http; - -namespace Microsoft.Graph.PowerShell.Authentication -{ - internal class GraphRequestPopContext : IGraphRequestPopContext - { - public Uri Uri { get; set; } - public HttpMethod HttpMethod { get; set; } - public AccessToken AccessToken { get; set; } - public HttpPipeline PopPipeline { get; set; } - public InteractiveBrowserCredential PopInteractiveBrowserCredential { get; set; } - } - -} \ No newline at end of file