Skip to content

Commit 0fe01a6

Browse files
Rebasing to keep up to date
1 parent 91cc021 commit 0fe01a6

File tree

10 files changed

+112
-125
lines changed

10 files changed

+112
-125
lines changed

src/Authentication/Authentication.Core/Common/GraphSession.cs

Lines changed: 0 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -56,11 +56,6 @@ public class GraphSession : IGraphSession
5656
/// </summary>
5757
public IGraphOption GraphOption { get; set; }
5858

59-
/// <summary>
60-
/// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts.
61-
/// </summary>
62-
public IGraphRequestPopContext GraphRequestPopContext { get; set; }
63-
6459
/// <summary>
6560
/// Represents a collection of Microsoft Graph PowerShell meta-info.
6661
/// </summary>

src/Authentication/Authentication.Core/Interfaces/IGraphRequestPopContext.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/Authentication/Authentication.Core/Interfaces/IGraphSession.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,5 @@ public interface IGraphSession
1212
IDataStore DataStore { get; set; }
1313
IRequestContext RequestContext { get; set; }
1414
IGraphOption GraphOption { get; set; }
15-
IGraphRequestPopContext GraphRequestPopContext { get; set; }
1615
}
1716
}

src/Authentication/Authentication.Core/Microsoft.Graph.Authentication.Core.csproj

Lines changed: 5 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,10 @@
1-
<Project Sdk="Microsoft.NET.Sdk">
1+
<Project Sdk="Microsoft.NET.Sdk">
22
<Import Project="$(MSBuildThisFileDirectory)..\..\..\Repo.props" />
33
<PropertyGroup>
44
<LangVersion>9.0</LangVersion>
55
<TargetFrameworks>netstandard2.0;net6.0;net472</TargetFrameworks>
66
<RootNamespace>Microsoft.Graph.PowerShell.Authentication.Core</RootNamespace>
7-
<Version>2.31.0</Version>
7+
<Version>2.32.0</Version>
88
<!-- Suppress .NET Target Framework Moniker (TFM) Support Build Warnings -->
99
<SuppressTfmSupportBuildWarnings>true</SuppressTfmSupportBuildWarnings>
1010
</PropertyGroup>
@@ -15,7 +15,9 @@
1515
<ItemGroup>
1616
<PackageReference Include="Azure.Identity" Version="1.13.2" />
1717
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0" />
18-
<PackageReference Include="Microsoft.Graph.Core" Version="3.2.4" />
18+
<PackageReference Include="Microsoft.Graph.Core" Version="3.2.2" />
19+
<PackageReference Include="Microsoft.Identity.Client" Version="4.67.2" />
20+
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.67.2" />
1921
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
2022
<PackageReference Include="System.Text.Json" Version="8.0.5" />
2123
</ItemGroup>

src/Authentication/Authentication.Core/Utilities/AuthenticationHelpers.cs

Lines changed: 4 additions & 54 deletions
Original file line numberDiff line numberDiff line change
@@ -15,8 +15,6 @@
1515
using System.Globalization;
1616
using System.IO;
1717
using System.Linq;
18-
using System.Net.Http;
19-
using System.Net.Http.Headers;
2018
using System.Security.Cryptography.X509Certificates;
2119
using System.Threading;
2220
using System.Threading.Tasks;
@@ -126,35 +124,16 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
126124
interactiveOptions.TokenCachePersistenceOptions = GetTokenCachePersistenceOptions(authContext);
127125

128126
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
129-
var popTokenRequestContext = new PopTokenRequestContext();
130-
if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph)
131-
{
132-
popTokenRequestContext = await CreatePopTokenRequestContext(authContext);
133-
GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential = interactiveBrowserCredential;
134-
}
135-
136127
if (!File.Exists(Constants.AuthRecordPath))
137128
{
138129
AuthenticationRecord authRecord;
139130
if (IsWamSupported())
140131
{
141-
// Adding a scenario to account for Access Token Proof of Possession
142-
if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph)
143-
{
144-
authRecord = await Task.Run(() =>
145-
{
146-
// Run the thread in MTA.
147-
return interactiveBrowserCredential.AuthenticateAsync(popTokenRequestContext, cancellationToken);
148-
});
149-
}
150-
else
132+
authRecord = await Task.Run(() =>
151133
{
152-
authRecord = await Task.Run(() =>
153-
{
154-
// Run the thread in MTA.
155-
return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken);
156-
});
157-
}
134+
// Run the thread in MTA.
135+
return interactiveBrowserCredential.AuthenticateAsync(new TokenRequestContext(authContext.Scopes), cancellationToken);
136+
});
158137
}
159138
else
160139
{
@@ -471,34 +450,5 @@ public static Task DeleteAuthRecordAsync()
471450
File.Delete(Constants.AuthRecordPath);
472451
return Task.CompletedTask;
473452
}
474-
475-
private static async Task<PopTokenRequestContext> CreatePopTokenRequestContext(IAuthContext authContext)
476-
{
477-
// Creating a httpclient that would handle all pop calls
478-
Uri popResourceUri = GraphSession.Instance.GraphRequestPopContext.Uri ?? new Uri("https://graph.microsoft.com/beta/organization");
479-
HttpClient popHttpClient = new(new HttpClientHandler());
480-
481-
// Find the nonce in the WWW-Authenticate header in the response.
482-
var popMethod = GraphSession.Instance.GraphRequestPopContext.HttpMethod ?? HttpMethod.Get;
483-
var popResponse = await popHttpClient.SendAsync(new HttpRequestMessage(popMethod, popResourceUri));
484-
485-
// Refresh token logic --- start
486-
var popPipelineOptions = new HttpPipelineOptions(new PopClientOptions()
487-
{
488-
489-
});
490-
491-
GraphSession.Instance.GraphRequestPopContext.PopPipeline = HttpPipelineBuilder.Build(popPipelineOptions, new HttpPipelineTransportOptions());
492-
var popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest();
493-
popRequest.Method = RequestMethod.Parse(popMethod.Method.ToUpper());
494-
popRequest.Uri.Reset(popResourceUri);
495-
496-
// Refresh token logic --- end
497-
var popContext = new PopTokenRequestContext(authContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(popResponse.Headers, "Pop").Nonce, request: popRequest);
498-
return popContext;
499-
}
500-
}
501-
internal class PopClientOptions : ClientOptions
502-
{
503453
}
504454
}

src/Authentication/Authentication/Cmdlets/InvokeMgGraphRequest.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1026,8 +1026,6 @@ private async Task ProcessRecordAsync()
10261026
try
10271027
{
10281028
PrepareSession();
1029-
GraphSession.Instance.GraphRequestPopContext.Uri = Uri;
1030-
GraphSession.Instance.GraphRequestPopContext.HttpMethod = GetHttpMethod(Method);
10311029
var client = HttpHelpers.GetGraphHttpClient();
10321030
ValidateRequestUri();
10331031
using (var httpRequestMessage = GetRequest(client, Uri))

src/Authentication/Authentication/Common/GraphSessionInitializer.cs

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -47,8 +47,7 @@ internal static GraphSession CreateInstance(IDataStore dataStore = null)
4747
{
4848
DataStore = dataStore ?? new DiskDataStore(),
4949
RequestContext = new RequestContext(),
50-
GraphOption = graphOptions ?? new GraphOption(),
51-
GraphRequestPopContext = new GraphRequestPopContext()
50+
GraphOption = graphOptions ?? new GraphOption()
5251
};
5352
}
5453
/// <summary>

src/Authentication/Authentication/Handlers/AuthenticationHandler.cs

Lines changed: 102 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -27,8 +27,8 @@ internal class AuthenticationHandler : DelegatingHandler
2727
private const string BearerAuthenticationScheme = "Bearer";
2828
private const string PopAuthenticationScheme = "Pop";
2929
private int MaxRetry { get; set; } = 1;
30-
private PopTokenRequestContext popTokenRequestContext;
31-
private Request popRequest = GraphSession.Instance.GraphRequestPopContext.PopPipeline.CreateRequest();
30+
private TokenRequestContext popTokenRequestContext;
31+
private string cachedNonce;
3232

3333
public AzureIdentityAccessTokenProvider AuthenticationProvider { get; set; }
3434

@@ -52,10 +52,22 @@ protected override async Task<HttpResponseMessage> SendAsync(HttpRequestMessage
5252

5353
HttpResponseMessage response = await base.SendAsync(httpRequestMessage, cancellationToken).ConfigureAwait(false);
5454

55-
// Continuous nonce extraction on each request
56-
if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph)
55+
// Extract nonce from API responses for future PoP requests
56+
if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph && IsApiRequest(httpRequestMessage.RequestUri))
5757
{
58-
popTokenRequestContext = new PopTokenRequestContext(GraphSession.Instance.AuthContext.Scopes, isProofOfPossessionEnabled: true, proofOfPossessionNonce: WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme).Nonce, request: popRequest);
58+
try
59+
{
60+
var wwwAuthParams = WwwAuthenticateParameters.CreateFromAuthenticationHeaders(response.Headers, PopAuthenticationScheme);
61+
if (wwwAuthParams?.Nonce != null && !string.IsNullOrEmpty(wwwAuthParams.Nonce))
62+
{
63+
cachedNonce = wwwAuthParams.Nonce;
64+
}
65+
}
66+
catch (Exception ex)
67+
{
68+
System.Diagnostics.Debug.WriteLine($"AuthenticationHandler: Failed to extract PoP nonce: {ex.Message}");
69+
// Don't throw - nonce extraction failure shouldn't break the response
70+
}
5971
}
6072

6173
// Check if response is a 401 & is not a streamed body (is buffered)
@@ -76,17 +88,48 @@ private async Task AuthenticateRequestAsync(HttpRequestMessage httpRequestMessag
7688
{
7789
if (AuthenticationProvider != null)
7890
{
91+
// Determine if this is an API request that should use PoP (when enabled)
92+
// vs an authentication request that should always use Bearer
93+
bool isApiRequest = IsApiRequest(httpRequestMessage.RequestUri);
94+
bool shouldUsePoP = GraphSession.Instance.GraphOption.EnableATPoPForMSGraph && isApiRequest;
95+
96+
// Debug logging for flow routing
7997
if (GraphSession.Instance.GraphOption.EnableATPoPForMSGraph)
8098
{
81-
popRequest.Method = RequestMethod.Parse(httpRequestMessage.Method.Method.ToUpper());
82-
popRequest.Uri.Reset(httpRequestMessage.RequestUri);
83-
foreach (var header in httpRequestMessage.Headers)
99+
var requestType = isApiRequest ? "API" : "Auth";
100+
var tokenType = shouldUsePoP ? "PoP" : "Bearer";
101+
System.Diagnostics.Debug.WriteLine($"AuthenticationHandler: {requestType} request to {httpRequestMessage.RequestUri?.Host} using {tokenType} token");
102+
}
103+
104+
if (shouldUsePoP)
105+
{
106+
// API Request with PoP enabled - use PoP tokens ONLY
107+
try
108+
{
109+
// Create proper TokenRequestContext for PoP
110+
// Note: cachedNonce may be null for initial requests - this is expected
111+
popTokenRequestContext = new TokenRequestContext(
112+
scopes: GraphSession.Instance.AuthContext.Scopes,
113+
parentRequestId: null,
114+
claims: additionalAuthenticationContext?.ContainsKey(ClaimsKey) == true ? additionalAuthenticationContext[ClaimsKey]?.ToString() : null,
115+
tenantId: null,
116+
isCaeEnabled: false,
117+
isProofOfPossessionEnabled: true,
118+
proofOfPossessionNonce: cachedNonce // May be null for initial requests
119+
);
120+
121+
// Get TokenCredential from existing AuthenticationProvider
122+
var tokenCredential = await AuthenticationHelpers.GetTokenCredentialAsync(
123+
GraphSession.Instance.AuthContext, cancellationToken).ConfigureAwait(false);
124+
125+
var accessToken = await tokenCredential.GetTokenAsync(popTokenRequestContext, cancellationToken).ConfigureAwait(false);
126+
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token);
127+
}
128+
catch (Exception ex) when (!(ex is OperationCanceledException))
84129
{
85-
popRequest.Headers.Add(header.Key, header.Value.First());
130+
// Re-throw with context for PoP-specific failures
131+
throw new AuthenticationException($"Failed to acquire PoP token for {httpRequestMessage.RequestUri}: {ex.Message}", ex);
86132
}
87-
88-
var accessToken = await GraphSession.Instance.GraphRequestPopContext.PopInteractiveBrowserCredential.GetTokenAsync(popTokenRequestContext, cancellationToken).ConfigureAwait(false);
89-
httpRequestMessage.Headers.Authorization = new AuthenticationHeaderValue(PopAuthenticationScheme, accessToken.Token);
90133
}
91134
else
92135
{
@@ -156,5 +199,52 @@ private static async Task DrainAsync(HttpResponseMessage response)
156199
}
157200
response.Dispose();
158201
}
202+
203+
/// <summary>
204+
/// Determines if the request is an API request that should use PoP when enabled,
205+
/// vs an authentication/token request that should always use Bearer tokens.
206+
/// This method implements the core routing logic for AT-PoP:
207+
/// - Graph API endpoints → PoP tokens (when enabled)
208+
/// - Authentication endpoints → Bearer tokens (always)
209+
/// - Unknown endpoints → Bearer tokens (safe default)
210+
/// </summary>
211+
/// <param name="requestUri">The request URI to evaluate</param>
212+
/// <returns>True if this is an API request, false if it's an authentication request</returns>
213+
private static bool IsApiRequest(Uri requestUri)
214+
{
215+
if (requestUri == null) return false;
216+
217+
var host = requestUri.Host?.ToLowerInvariant();
218+
var path = requestUri.AbsolutePath?.ToLowerInvariant();
219+
220+
// Microsoft Graph API endpoints that should use PoP
221+
if (host?.Contains("graph.microsoft.com") == true ||
222+
host?.Contains("graph.microsoft.us") == true ||
223+
host?.Contains("microsoftgraph.chinacloudapi.cn") == true ||
224+
host?.Contains("graph.microsoft.de") == true)
225+
{
226+
// Exclude authentication/token endpoints - these should always use Bearer
227+
if (path?.Contains("/oauth2/") == true ||
228+
path?.Contains("/token") == true ||
229+
path?.Contains("/authorize") == true ||
230+
path?.Contains("/devicecode") == true)
231+
{
232+
return false; // Authentication request
233+
}
234+
return true; // API request
235+
}
236+
237+
// Azure AD/authentication endpoints - never use PoP
238+
if (host?.Contains("login.microsoftonline.com") == true ||
239+
host?.Contains("login.microsoft.com") == true ||
240+
host?.Contains("login.chinacloudapi.cn") == true ||
241+
host?.Contains("login.microsoftonline.de") == true ||
242+
host?.Contains("login.microsoftonline.us") == true)
243+
{
244+
return false; // Authentication request
245+
}
246+
247+
return false; // Default to authentication request for unknown endpoints
248+
}
159249
}
160250
}

src/Authentication/Authentication/Microsoft.Graph.Authentication.nuspec

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -50,14 +50,12 @@
5050
<file src="artifacts\Dependencies\Core\runtimes\win-arm64\native\msalruntime_arm64.dll" target="Dependencies" />
5151
<!-- Core-->
5252
<file src="artifacts\Dependencies\Core\Azure.Core.dll" target="Dependencies\Core" />
53-
<file src="artifacts\Dependencies\Core\Azure.Core.Experimental.dll" target="Dependencies\Core" />
5453
<file src="artifacts\Dependencies\Core\Microsoft.Graph.Core.dll" target="Dependencies\Core" />
5554
<file src="artifacts\Dependencies\Core\Microsoft.Identity.Client.dll" target="Dependencies\Core" />
5655
<file src="artifacts\Dependencies\Core\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies\Core" />
5756
<file src="artifacts\Dependencies\Core\Newtonsoft.Json.dll" target="Dependencies\Core" />
5857
<!-- Desktop -->
5958
<file src="artifacts\Dependencies\Desktop\Azure.Core.dll" target="Dependencies\Desktop" />
60-
<file src="artifacts\Dependencies\Desktop\Azure.Core.Experimental.dll" target="Dependencies\Desktop" />
6159
<file src="artifacts\Dependencies\Desktop\Microsoft.Graph.Core.dll" target="Dependencies\Desktop" />
6260
<file src="artifacts\Dependencies\Desktop\Microsoft.Identity.Client.dll" target="Dependencies\Desktop" />
6361
<file src="artifacts\Dependencies\Desktop\Microsoft.Identity.Client.Extensions.Msal.dll" target="Dependencies\Desktop" />

src/Authentication/Authentication/Models/GraphRequestPopContext.cs

Lines changed: 0 additions & 23 deletions
This file was deleted.

0 commit comments

Comments
 (0)