Skip to content

Commit

Permalink
adding "-AT PoP" option to "Set-MgGraphOptions"
Browse files Browse the repository at this point in the history
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
  • Loading branch information
FehintolaObafemi committed Jul 10, 2024
1 parent ad299d5 commit b724ded
Show file tree
Hide file tree
Showing 19 changed files with 293 additions and 26 deletions.
79 changes: 79 additions & 0 deletions docs/AT-Pop.md
Original file line number Diff line number Diff line change
@@ -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/).
20 changes: 20 additions & 0 deletions docs/authentication.md
Original file line number Diff line number Diff line change
Expand Up @@ -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.
Expand Down
5 changes: 5 additions & 0 deletions src/Authentication/Authentication.Core/Common/GraphSession.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,11 @@ public class GraphSession : IGraphSession
/// </summary>
public IGraphOption GraphOption { get; set; }

/// <summary>
/// Temporarily stores the user's Graph request details such as Method and Uri. Essential as part of the Proof of Possession efforts.
/// </summary>
public IGraphRequestPopContext GraphRequestPopContext { get; set; }

/// <summary>
/// Represents a collection of Microsoft Graph PowerShell meta-info.
/// </summary>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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; }
}
}
Original file line number Diff line number Diff line change
@@ -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; }
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -12,5 +12,6 @@ public interface IGraphSession
IDataStore DataStore { get; set; }
IRequestContext RequestContext { get; set; }
IGraphOption GraphOption { get; set; }
IGraphRequestPopContext GraphRequestPopContext { get; set; }
}
}
Original file line number Diff line number Diff line change
@@ -1,19 +1,21 @@
<Project Sdk="Microsoft.NET.Sdk">
<Project Sdk="Microsoft.NET.Sdk">
<Import Project="$(MSBuildThisFileDirectory)..\..\..\Repo.props" />
<PropertyGroup>
<LangVersion>9.0</LangVersion>
<TargetFrameworks>netstandard2.0;net6.0;net472</TargetFrameworks>
<RootNamespace>Microsoft.Graph.PowerShell.Authentication.Core</RootNamespace>
<Version>2.18.0</Version>
<Version>2.12.0</Version>
</PropertyGroup>
<PropertyGroup>
<EnableNETAnalyzers>true</EnableNETAnalyzers>
<EnforceCodeStyleInBuild>true</EnforceCodeStyleInBuild>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Azure.Identity" Version="1.11.4" />
<PackageReference Include="Azure.Identity.Broker" Version="1.1.0" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.1.13" />
<PackageReference Include="Azure.Identity" Version="1.12.0-beta.3" />
<PackageReference Include="Azure.Identity.Broker" Version="1.2.0-beta.1" />
<PackageReference Include="Microsoft.Graph.Core" Version="3.1.12" />
<PackageReference Include="Microsoft.Identity.Client" Version="4.61.3" />
<PackageReference Include="Microsoft.Identity.Client.Broker" Version="4.61.3" />
<PackageReference Include="Newtonsoft.Json" Version="13.0.3" />
</ItemGroup>
<Target Name="CopyFiles" AfterTargets="Build">
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 All @@ -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;
Expand Down Expand Up @@ -113,23 +116,43 @@ private static async Task<InteractiveBrowserCredential> 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
{
Expand Down Expand Up @@ -446,5 +469,34 @@ public static Task DeleteAuthRecordAsync()
File.Delete(Constants.AuthRecordPath);
return Task.CompletedTask;
}

private static async Task<PopTokenRequestContext> 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
{
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -1023,6 +1023,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))
Expand Down
8 changes: 8 additions & 0 deletions src/Authentication/Authentication/Cmdlets/SetMgGraphOption.cs
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,9 @@ public class SetMgGraphOption : PSCmdlet
{
[Parameter]
public bool EnableLoginByWAM { get; set; }

[Parameter]
public bool EnableATPoP { get; set; }

protected override void BeginProcessing()
{
Expand All @@ -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));
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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()
};
}
/// <summary>
Expand Down
Loading

0 comments on commit b724ded

Please sign in to comment.