Skip to content

Commit

Permalink
Adding AT PoP skeleton (#2511)
Browse files Browse the repository at this point in the history
* adding "-AT PoP" option to "Set-MgGraphOptions"

* Adding AT PoP skeleton

---------

Co-authored-by: Tim <[email protected]>
Co-authored-by: Peter Ombwa <[email protected]>
Co-authored-by: Peter Ombwa <[email protected]>
Co-authored-by: Mustafa Zengin <[email protected]>
Co-authored-by: Clément Notin <[email protected]>
Co-authored-by: Microsoft Graph DevX Tooling <[email protected]>
Co-authored-by: Vincent Biret <[email protected]>
Co-authored-by: Vincent Biret <[email protected]>
Co-authored-by: Subhajit Ray (from Dev Box) <[email protected]>
  • Loading branch information
10 people committed Feb 5, 2024
1 parent 0e53ceb commit 21ed0a9
Show file tree
Hide file tree
Showing 14 changed files with 14,510 additions and 8 deletions.
2 changes: 2 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -116,6 +116,8 @@ Before using the provided `-AccessToken` to get Microsoft Graph resources, custo

AT PoP is a security mechanism that binds an access token to a cryptographic key that only the intended recipient 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 WAM to function.

Microsoft Graph PowerShell module supports AT PoP in the following scenario:

- To enable AT PoP on supported devices
Expand Down
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(MSBuildThisFileDirectory)..\..\..\Repo.props" />
<PropertyGroup>
<LangVersion>9.0</LangVersion>
Expand All @@ -11,9 +11,10 @@
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.10.3" />
<PackageReference Include="Azure.Identity.Broker" Version="1.0.0-beta.5" />
<PackageReference Include="Azure.Identity.BrokeredAuthentication" Version="1.0.0-beta.3" />
<PackageReference Include="Azure.Identity" Version="1.11.0-alpha.20240122.2" />
<PackageReference Include="Azure.Core" Version="1.38.0-alpha.20240122.1" />
<PackageReference Include="Azure.Core.Experimental" Version="0.1.0-preview.32" />
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0-alpha.20240122.2" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.0.9" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.56.0" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.56.0" />
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down Expand Up @@ -86,6 +87,12 @@ private static bool IsWamSupported()
return GraphSession.Instance.GraphOption.EnableWAMForMSGraph && SharedUtilities.IsWindowsPlatform();
}

//Check to see if ATPoP is Supported
private static bool IsATPoPSupported()
{
return GraphSession.Instance.GraphOption.EnableATPoPForMSGraph;
}

private static async Task<TokenCredential> GetClientSecretCredentialAsync(IAuthContext authContext)
{
if (authContext is null)
Expand Down Expand Up @@ -125,11 +132,29 @@ private static async Task<InteractiveBrowserCredential> GetInteractiveBrowserCre
var interactiveBrowserCredential = new InteractiveBrowserCredential(interactiveOptions);
if (IsWamSupported())
{
authRecord = await Task.Run(() =>
// Adding a scenario to account for Access Token Proof of Possession
if (IsATPoPSupported())
{
// Run the thread in MTA.
return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken);
});
// Logic to implement ATPoP Authentication
var client = new PopClient(interactiveBrowserCredential, authContext, new PopClientOptions()
{
Diagnostics =
{
IsLoggingContentEnabled = true,
LoggedHeaderNames = { "Authorization" }
}
});
//var response = client.Get(new Uri("https://20.190.132.47/beta/me"), CancellationToken.None);
authRecord = client.GetAuthRecord();
}
else
{
authRecord = await Task.Run(() =>
{
// Run the thread in MTA.
return interactiveBrowserCredential.Authenticate(new TokenRequestContext(authContext.Scopes), cancellationToken);
});
}
}
else
{
Expand Down
88 changes: 88 additions & 0 deletions src/Authentication/Authentication.Core/Utilities/PopClient.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,88 @@
using System;
using System.IdentityModel;
using System.Threading;
using System.Threading.Tasks;
using Azure;
using Azure.Core;
using Azure.Core.Pipeline;
using Azure.Identity;
using Azure.Identity.Broker;
using Microsoft.Identity.Client.NativeInterop;

namespace Microsoft.Graph.PowerShell.Authentication.Core.Utilities
{
public class PopClient
{
private readonly HttpPipeline _pipeline;
private AuthenticationRecord _authenticationRecord;
private readonly InteractiveBrowserCredential _interactiveBrowserCredential;

public PopClient(TokenCredential credential, IAuthContext authContext, ClientOptions options = null)
{
//_interactiveBrowserCredential = (InteractiveBrowserCredential)credential;
_interactiveBrowserCredential = new InteractiveBrowserCredential(new InteractiveBrowserCredentialBrokerOptions(WindowHandleUtlities.GetConsoleOrTerminalWindow()));

if (!(credential is ISupportsProofOfPossession))
{
throw new ArgumentException("The provided TokenCredential does not support proof of possession.", nameof(credential));
}

var pipelineOptions = new HttpPipelineOptions(options);
pipelineOptions.PerRetryPolicies.Add(new InteractivePopTokenAuthenticationPolicy(_interactiveBrowserCredential, "https://graph.microsoft.com/.default", () => _authenticationRecord));

_pipeline = HttpPipelineBuilder.Build(pipelineOptions);
}

public async ValueTask<Response> GetAsync(Uri uri, CancellationToken cancellationToken = default)
{
using var request = _pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(uri);
return await _pipeline.SendRequestAsync(request, cancellationToken).ConfigureAwait(false);
}

public Response Get(Uri uri, CancellationToken cancellationToken = default)
{
using var request = _pipeline.CreateRequest();
request.Method = RequestMethod.Get;
request.Uri.Reset(uri);
return _pipeline.SendRequest(request, cancellationToken);
}

public async ValueTask<AuthenticationRecord> GetAuthRecordAsync()
{
_authenticationRecord ??= await _interactiveBrowserCredential.AuthenticateAsync();
return _authenticationRecord;
}

public AuthenticationRecord GetAuthRecord()
{
_authenticationRecord ??= _interactiveBrowserCredential.Authenticate();
return _authenticationRecord;
}
}

public class InteractivePopTokenAuthenticationPolicy : PopTokenAuthenticationPolicy
{
private readonly InteractiveBrowserCredential _interactiveBrowserCredential;
private readonly Func<AuthenticationRecord> _getAuthRecord;

public InteractivePopTokenAuthenticationPolicy(InteractiveBrowserCredential credential, string scope, Func<AuthenticationRecord> getAuthRecord)
: base(credential, scope)
{
_interactiveBrowserCredential = credential;
_getAuthRecord = getAuthRecord;
}

protected override ValueTask AuthorizeRequestAsync(HttpMessage message)
{
var authRecord = _getAuthRecord();
if (authRecord != null)
{
_interactiveBrowserCredential.AuthenticateAsync(new TokenRequestContext(new[] { "https://graph.microsoft.com/.default" })).ConfigureAwait(false);
}

return base.AuthorizeRequestAsync(message);
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
using Azure.Core;
public class PopClientOptions : ClientOptions { }
Binary file not shown.
Loading

0 comments on commit 21ed0a9

Please sign in to comment.