Skip to content

Task 1924572 Sample 4-1 basher update #602

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Merged
merged 69 commits into from
Aug 26, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
69 commits
Select commit Hold shift + click to select a range
2599ef8
updated readme and ps1 with new wording
aremo-ms Jun 3, 2022
7289a0d
Added BASHER logic to Service controller and made some refactoring
aremo-ms Jun 8, 2022
3b794e6
update
aremo-ms Jun 8, 2022
fd8ba1a
changed to getting Owner as Object Id
aremo-ms Jun 9, 2022
55e3a1c
fixed bug for readme and added app permissions
aremo-ms Jun 13, 2022
6f2465f
some review comments were addressed
aremo-ms Jun 23, 2022
6a8c470
updated README and configure.ps1
aremo-ms Jun 30, 2022
06cce15
replaced Newtonsoft by JsonSerializer
aremo-ms Jun 30, 2022
56ac49c
addressed most of comments. The last thing to address is token valida…
aremo-ms Jul 11, 2022
a57b73e
comments addressed
aremo-ms Jul 12, 2022
cd33fce
Added tenant validation on Service
aremo-ms Jul 13, 2022
a2bd757
Kalyan's edits to polish the code and readme
kalyankrishna1 Jul 14, 2022
dff53f9
merge fixed
kalyankrishna1 Jul 14, 2022
148968e
extended token validation code
kalyankrishna1 Jul 14, 2022
8a9f3bc
extended token validation code discussed
kalyankrishna1 Jul 14, 2022
516b438
secrets accidently added secrets removed
kalyankrishna1 Jul 14, 2022
bab24f2
removed a bunch of redundant files
kalyankrishna1 Jul 14, 2022
6f8dc6c
empty but shows changed !
kalyankrishna1 Jul 14, 2022
f866d5a
added optional claim
aremo-ms Jul 18, 2022
727f74f
processing idtyp claim
aremo-ms Jul 18, 2022
0242a69
updated readme with latest updates
aremo-ms Jul 21, 2022
2e3d9e4
added an ignored folder
Jul 21, 2022
6f2ec1d
local branch merged
Jul 21, 2022
4180373
done and approved for BASHER
Jul 22, 2022
e6760af
enabled logging by default with disclaimers
Jul 22, 2022
7df60d9
added more missing material
Jul 22, 2022
1b56aa7
highlighted using certificates
Jul 22, 2022
5d4d4b6
update for certificate, updated link
aremo-ms Jul 26, 2022
831f51f
merge from master
aremo-ms Aug 4, 2022
5c5188c
Configuration scripts generated with updated sample.json and Code Gen…
aremo-ms Aug 10, 2022
21a6f1c
updated appsettings.json with instruction for local and keyvault ceri…
aremo-ms Aug 10, 2022
a5a8507
updated configuration.ps1 and appsettings.json
aremo-ms Aug 10, 2022
ed95a14
updated appsettings.json
aremo-ms Aug 10, 2022
32b9827
updated sample with latest Readme
aremo-ms Aug 11, 2022
a273a6b
Added instructions to use local certificate
aremo-ms Aug 11, 2022
ba2bfb4
reset appsettings.json
aremo-ms Aug 11, 2022
40c86aa
nit
aremo-ms Aug 11, 2022
ab93e0f
nit
aremo-ms Aug 11, 2022
de8a786
added security warning to appsetting.json
aremo-ms Aug 12, 2022
49a3a1c
reset appsettings.json
aremo-ms Aug 12, 2022
180aad8
removed data
aremo-ms Aug 12, 2022
3b6d228
removed UseNewSetup flag as redundant
aremo-ms Aug 15, 2022
949d23b
updated ceritifcate redme name
aremo-ms Aug 16, 2022
fa8bbe0
updated comments on appsettings.json
aremo-ms Aug 16, 2022
fde05ea
Readme with updated warning about using client secrets
aremo-ms Aug 18, 2022
d8a4208
updated readme with correct certificate readme file path
aremo-ms Aug 18, 2022
cdb0b62
updated scopes comment
aremo-ms Aug 18, 2022
afbd2e8
removed redundant package
aremo-ms Aug 18, 2022
afc68bb
updated certificate part to be less confusiong
aremo-ms Aug 18, 2022
4c7ad15
Compilation issues and some formatting addressed
Aug 19, 2022
cd752a1
More fixes
Aug 19, 2022
ab5a60c
updated the certs steps a bit
Aug 20, 2022
49abddc
updated the referenced code
Aug 21, 2022
164a302
reverting certificates CsharpConfigurations support, Automated Deply …
aremo-ms Aug 23, 2022
9fa8f01
merge
aremo-ms Aug 23, 2022
5a18734
typo fixed
Aug 23, 2022
2f8e0f4
minor working updates
aremo-ms Aug 23, 2022
67ee023
minor fix
Aug 23, 2022
957dbf8
Merge branch 'aremo-ms/Task-1924572-4-1-basher-update' of https://git…
Aug 23, 2022
f1c4601
removed PasswordCredentials key
aremo-ms Aug 23, 2022
68caf90
another round of minor edits
Aug 24, 2022
da47245
Updated readme file with deloyment steps
aremo-ms Aug 24, 2022
a74abf9
Merge branch 'aremo-ms/Task-1924572-4-1-basher-update' of https://git…
aremo-ms Aug 24, 2022
dd39fd5
Minor last minute edits
Aug 25, 2022
5f0986c
Minor edits and deployment related updates
Aug 25, 2022
49476ae
merge conflicts resolved
Aug 25, 2022
0f7e06b
renamed Delete to DeleteItem (action didn't work for Delete for some …
aremo-ms Aug 25, 2022
f9599f4
and this is done ..
Aug 26, 2022
e03d1a7
Merge branch 'master' into aremo-ms/Task-1924572-4-1-basher-update
Aug 26, 2022
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
Expand Up @@ -136,3 +136,4 @@ packages
/4-WebApp-your-API/4-1-MyOrg/.vscode/settings.json
/5-WebApp-AuthZ/5-1-Roles/.vscode/settings.json
/.vscode
/2-WebApp-graph-user/2-1-Call-MSGraph/Properties/PublishProfiles
Empty file.
57 changes: 57 additions & 0 deletions 2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/CAE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
### Process the CAE challenge from Microsoft Graph

To process the CAE challenge from Microsoft Graph, the controller actions need to extract it from the `wwwAuthenticate` header. It is returned when MS Graph rejects a seemingly valid Access tokens for MS Graph. For this you need to:

1. Inject and instance of `MicrosoftIdentityConsentAndConditionalAccessHandler` in the controller constructor. The beginning of the HomeController becomes:

```CSharp
public class HomeController : Controller
{
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphServiceClient;
private readonly MicrosoftIdentityConsentAndConditionalAccessHandler _consentHandler;
private string[] _graphScopes = new[] { "user.read" };
public HomeController(ILogger<HomeController> logger,
IConfiguration configuration,
GraphServiceClient graphServiceClient,
MicrosoftIdentityConsentAndConditionalAccessHandler consentHandler)
{
_logger = logger;
_graphServiceClient = graphServiceClient;
this._consentHandler = consentHandler;
// Capture the Scopes for Graph that were used in the original request for an Access token (AT) for MS Graph as
// they'd be needed again when requesting a fresh AT for Graph during claims challenge processing
_graphScopes = configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');
}

// more code here
```
1. The process to handle CAE challenges from MS Graph comprises of the following steps:
1. Catch a Microsoft Graph SDK's `ServiceException` and extract the required `claims`. This is done by wrapping the call to Microsoft Graph into a try/catch block that processes the challenge:
```CSharp
currentUser = await _graphServiceClient.Me.Request().GetAsync();
```
1. Then redirect the user back to Azure AD with the new requested `claims`. Azure AD will use this `claims` payload to discern what or if any additional processing is required, example being the user needs to sign-in again or do multi-factor authentication.
```CSharp
try
{
currentUser = await _graphServiceClient.Me.Request().GetAsync();
}
// Catch CAE exception from Graph SDK
catch (ServiceException svcex) when (svcex.Message.Contains("Continuous access evaluation resulted in claims challenge"))
{
try
{
Console.WriteLine($"{svcex}");
string claimChallenge = WwwAuthenticateParameters.GetClaimChallengeFromResponseHeaders(svcex.ResponseHeaders);
_consentHandler.ChallengeUser(_graphScopes, claimChallenge);
return new EmptyResult();
}
catch (Exception ex2)
{
_consentHandler.HandleException(ex2);
}
}
```

The `AuthenticationHeaderHelper` class is available from the `Helpers\AuthenticationHeaderHelper.cs file`.
210 changes: 210 additions & 0 deletions 2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/Deployment.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,210 @@
## Deployment

### Deploying web app to Azure App Services

There is one web app in this sample. To deploy it to **Azure App Services**, you'll need to:

- create an **Azure App Service**
- publish the projects to the **App Services**, and
- update its client(s) to call the website instead of the local environment.

#### Publish your files

##### Publish using Visual Studio

Follow the link to [Publish with Visual Studio](https://docs.microsoft.com/visualstudio/deployment/quickstart-deploy-to-azure).

##### Publish using Visual Studio Code

1. Open an instance of Visual Studio code set to the `WebApp-OpenIDConnect-DotNet-graph-v2` project folder.
1. Install the VS Code extension [Azure App Service](https://marketplace.visualstudio.com/items?itemName=ms-azuretools.vscode-azureappservice).
1. Using the extension you just installed, sign in to **Azure App Service** using your Azure AD account.
1. Choose `Terminal > New Terminal` from the VS Code menu to open a new terminal window in the project directory.
1. Run the following command

```console
dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
```

1. A `publish` folder is created within the following folder: `bin/Release/netcoreapp3.1/`.
1. From the VS Code file explorer, right-click on the **publish** folder and select **Deploy to Web App**.
1. Select **Create New Web App**.
1. Enter a unique name for the app, for example, `WebApp-OpenIDConnect-DotNet-graph-v2`. If you chose `example-domain` for your app name, your app's domain name will be `https://example-domain.azurewebsites.net`.
1. Select **Windows** as the OS. Press Enter.
1. Select **.NET Core 3.1 (LTS)** as runtime stack.
1. Select `Free` or any other option for your pricing tier.

#### Update the Azure AD app registration (WebApp-OpenIDConnect-DotNet-graph-v2)

1. Navigate back to to the [Azure portal](https://portal.azure.com).
1. Go to the **Azure Active Directory** section, and then select **App registrations**.
1. In the resulting screen, select the `WebApp-OpenIDConnect-DotNet-graph-v2` application.
1. In the app's registration screen, select **Authentication** in the menu.
- In the **Redirect URIs** section, update both of the reply URLs to match the site URL of your Azure deployment. Using the following examples as a guide, **replace** the text `example-domain` with the app name you created while deploying, for example:
- `https://example-domain.azurewebsites.net/`
- `https://example-domain.azurewebsites.net/signin-oidc`
1. Update the **Front-channel logout URL** fields with the address of your service, for example `https://example-domain.azurewebsites.net`.

> :warning: If your app is using *in-memory* storage, **Azure App Services** will spin down your web site if it is inactive, and any records that your app was keeping will emptied. In addition, if you increase the instance count of your website, requests will be distributed among the instances. Your app's records, therefore, will not be the same on each instance.

### Enabling your code to get secrets from Key Vault using Managed Identity

One of the uber principals of security and **Zero Trust** is to place credentials out of your code and use in a manner that allows for credentials to be replaced or rotated without incurring a downtime.

To achieve this we'd place our application's credentials in [Azure Key Vault](https://azure.microsoft.com/services/key-vault/) and access it via [managed Identities for Azure resources](https://docs.microsoft.com/azure/active-directory/managed-identities-azure-resources/overview).

We will follow the steps broadly outlined in the guide: [Use Key Vault from App Service with Azure Managed Identity](https://github.com/Azure-Samples/app-service-msi-keyvault-dotnet/blob/master/README.md)

#### Set up your Managed Identity

1. Navigate to [Azure portal](https://portal.azure.com) and select the **Azure App Service**.
1. Find and select the App Service you've created previously.
1. On App Service portal, select **Identity**.
1. Within the **System assigned** tab, switch **Status** to **On**. Click **Save**.
1. Record the **Object Id** that will appear, as you will need it in the next step.

For more information, see [Add a system-assigned identity](https://docs.microsoft.com/azure/app-service/overview-managed-identity?tabs=dotnet#add-a-system-assigned-identity)

#### Set up your Key vault

Before starting here, make sure:

- You have an [Azure Subscription](https://azure.microsoft.com/free/).
- You have a working and deployed application as an Azure App Service following the steps listed at [Deploying web app to Azure App Services](#deploying-web-app-to-azure-app-services) above.
- Follow the guide to [create an Azure Key Vault](https://docs.microsoft.com/azure/key-vault/general/quick-create-portal).

##### Upload your secret to KeyVault

1. Navigate to your new key vault in the Azure portal.
1. On the Key Vault settings pages, select **Secrets**.
1. Click on **Generate/Import**.
1. On the **Create a secret** screen choose the following values:
- **Upload options**: Manual.
- **Name**: Type a name for the secret. The secret name must be unique within a Key Vault. For example, `myClientSecret`
- **Value**: Copy and paste the value for the `ClientSecret` property (without quotes!) from your `appsettings.json` file.
- Leave the other values to their defaults. Click **Create**.

##### Provide the managed identity access to Key Vault

1. Navigate to your Key Vault in the portal.
1. Select **Overview** > **Access policies**.
1. Click on **Add Access Policy**.
1. In the input box for **Secret permissions**, select **Get**.
1. Click on **Select Principal**, add the **system-assigned managed identity** that you have created in the [steps before](#set-up-your-managed-identity). You can use the **Object Id** you have recorded previously to search for it.
1. Click on **OK** to add the new Access Policy, then click **Save** to save the Access Policy.

#### Modify your code to connect to Key Vault

1. In the `appsettings.json` file, find and delete the `ClientSecret` property and its value.
1. In the `Properties\launchSettings.json` file, find the string `ENTER_YOUR_KEY_VAULT_URI` and replace it with the URI of your Key Vault, for example: `https://example-vault.vault.azure.net/`
1. Add the `Azure.Identity` NuGet package to the solution. This sample project has already added this package.
1. Add the following directives to your `startup.cs`.
</p>
:information_source: this has been already added in the sample project.

```CSharp
using Azure;
using Azure.Identity;
using Azure.Security.KeyVault.Secrets;
```

5. In your `Startup.cs` file, you must create a `GetSecretFromKeyVault` method. This method sets up the Azure Key Vault client and returns the secret that is required.
</p>
:information_source: this has already been added in the sample project.

```CSharp
private string GetSecretFromKeyVault(string tenantId, string secretName)
{
// this should point to your vault's URI, like https://<yourkeyvault>.vault.azure.net/
string uri = Environment.GetEnvironmentVariable("KEY_VAULT_URI");
DefaultAzureCredentialOptions options = new DefaultAzureCredentialOptions();

// Specify the tenant ID to use the dev credentials when running the app locally
options.VisualStudioTenantId = tenantId;
options.SharedTokenCacheTenantId = tenantId;
SecretClient client = new SecretClient(new Uri(uri), new DefaultAzureCredential(options));

// The secret name, for example if the full url to the secret is https://<yourkeyvault>.vault.azure.net/secrets/Graph-App-Secret
Response<KeyVaultSecret> secret = client.GetSecretAsync(secretName).Result;

return secret.Value.Value;
}
```

6. In your `Startup.cs` file, find the `ConfigureServices` method. Add the following code to call the GetSecretFromKeyVault method, right after `services.AddAuthentication`.
</p>
:information_source: In the sample project, this code is present but commented out by default. Uncomment it.
</p>
:warning: Replace the string `ENTER_YOUR_SECRET_NAME_HERE` with the name of the client secret you entered into Azure Key Vault, for example `myClientSecret`.

```CSharp
// uncomment the following 3 lines to get ClientSecret from KeyVault
string tenantId = Configuration.GetValue<string>("AzureAd:TenantId");
services.Configure<MicrosoftIdentityOptions>(
options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "ENTER_YOUR_SECRET_NAME_HERE"); });
```

7. Your `ConfigureServices` method should now look like the following snippet:

```CSharp
public void ConfigureServices(IServiceCollection services)
{
string[] initialScopes = Configuration.GetValue<string>("DownstreamApi:Scopes")?.Split(' ');

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration)
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();

// uncomment the following 3 lines to get ClientSecret from KeyVault
string tenantId = Configuration.GetValue<string>("AzureAd:TenantId");
services.Configure<MicrosoftIdentityOptions>(
options => { options.ClientSecret = GetSecretFromKeyVault(tenantId, "myClientSecret"); });

// ... more method code continues below
}
```

8. Add an environment variable to your App Service so your web app can find its key vault.

1. Go to the [Azure portal](https://portal.azure.com). Search for and select **App Service**, and then select your app.
1. Select **Configuration** blade on the left, then select **New Application Settings**.
1. Add a new variable, naming it **KEY_VAULT_URI**. Populate the value with the URI of your key vault, for example: `https://example-vault.vault.azure.net/`

1. Re-deploy your project to Azure App Service.

1. Run the following command:

```console
dotnet publish WebApp-OpenIDConnect-DotNet-graph.csproj --configuration Release
```

1. Then, from the VS Code file explorer, right-click on the **bin/Release/netcoreapp3.1/publish** folder and select **Deploy to Web App**. If you are prompted to select an app, select one you created during this sample.

1. The deployment status is available from the output window. Within a few minutes you'll be able to visit your now-secure app and sign in.

## Optional - Handle Continuous Access Evaluation (CAE) challenge from Microsoft Graph

Continuous access evaluation (CAE) enables web APIs to do just-in time token validation, for instance enforcing user session revocation in the case of password change/reset but there are other benefits. For details, see [Continuous access evaluation](https://docs.microsoft.com/azure/active-directory/conditional-access/concept-continuous-access-evaluation).

Microsoft Graph is now CAE-enabled in Preview. This means that it can ask its clients for more claims when conditional access policies require it. Your can enable your application to be ready to consume CAE-enabled APIs by:

1. Declaring that the client app is capable of handling claims challenges from the web API.
2. Processing these challenges when thrown.

### Declare the CAE capability in the configuration

This sample declares that it's CAE-capable by adding a `ClientCapabilities` property in the configuration, whose value is `[ "cp1" ]`.

```Json
{
"AzureAd": {
// ...
// the following is required to handle Continuous Access Evaluation challenges
"ClientCapabilities": [ "cp1" ],
// ...
},
// ...
}
```
86 changes: 86 additions & 0 deletions 2-WebApp-graph-user/2-1-Call-MSGraph/ReadmeFiles/ExploreSample.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,86 @@
## Explore the sample

1. Open your web browser and make a request to the app at url `https://localhost:44321`. The app immediately attempts to authenticate you via the Microsoft identity platform. Sign in with a work or school account.
2. Provide consent to the screen presented.
3. Click on the **Profile** link on the top menu. The web app will make a call to the Microsoft Graph `/me` endpoint. You should see information about the signed-in user's account, as well as its picture, if these values are set in the account's profile.

> Did the sample not work for you as expected? Did you encounter issues trying this sample? Then please reach out to us using the [GitHub Issues](../../../../issues) page.

> [Consider taking a moment to share your experience with us.](https://forms.office.com/Pages/ResponsePage.aspx?id=v4j5cvGGr0GRqy180BHbRz0h_jLR5HNJlvkZAewyoWxUNEFCQ0FSMFlPQTJURkJZMTRZWVJRNkdRMC4u)

## About The code

1. In this aspnetcore web project, first the packages `Microsoft.Identity.Web`, `Microsoft.Identity.Web.UI` and `Microsoft.Identity.Web.MicrosoftGraph` were added from NuGet. These libraries are used to simplify the process of signing-in a user and acquiring tokens for Microsoft Graph.

2. Starting with the **Startup.cs** file :

- at the top of the file, the following two using directives were added:

```CSharp
using Microsoft.Identity.Web;
using Microsoft.Identity.Web.UI;
```

- in the `ConfigureServices` method, the following code was added, replacing any existing `AddAuthentication()` code:

```CSharp

services.AddAuthentication(OpenIdConnectDefaults.AuthenticationScheme)
.AddMicrosoftIdentityWebApp(Configuration.GetSection("AzureAd"))
.EnableTokenAcquisitionToCallDownstreamApi(initialScopes)
.AddMicrosoftGraph(Configuration.GetSection("DownstreamApi"))
.AddInMemoryTokenCaches();

```

`AddMicrosoftIdentityWebApp()` enables your application to sign-in a user with the Microsoft identity platform endpoint. This endpoint is capable of signing-in users both with their Work and School and Microsoft Personal accounts (if required).

`EnableTokenAcquisitionToCallDownstreamApi()` and `AddMicrosoftGraph` adds support to call Microsoft Graph. This lines ensures that the GraphAPIService benefits from the optimized `HttpClient` management by ASP.NET Core.

3. In the `Controllers\HomeController.cs` file, the following code is added to allow calling MS Graph:

```CSharp
private readonly ILogger<HomeController> _logger;
private readonly GraphServiceClient _graphServiceClient;

private readonly GraphServiceClient _graphServiceClient;
public HomeController(ILogger<HomeController> logger,
IConfiguration configuration,
GraphServiceClient graphServiceClient)
{
_logger = logger;
_graphServiceClient = graphServiceClient;
this._consentHandler = consentHandler;
}
```

4. In the `Profile()` action we make a call to the Microsoft Graph `/me` endpoint. In case a token cannot be acquired, a challenge is attempted to re-sign-in the user, and have them consent to the requested scopes. This is expressed declaratively by the `AuthorizeForScopes`attribute. This attribute is part of the `Microsoft.Identity.Web` project and automatically manages incremental consent.

```CSharp
[AuthorizeForScopes(ScopeKeySection = "DownstreamApi:Scopes")]
public async Task<IActionResult> Profile()
{
var me = await _graphServiceClient.Me.Request().GetAsync();
ViewData["Me"] = me;

try
{
// Get user photo
using (var photoStream = await _graphServiceClient.Me.Photo.Content.Request().GetAsync())
{
byte[] photoByte = ((MemoryStream)photoStream).ToArray();
ViewData["Photo"] = Convert.ToBase64String(photoByte);
}
}
catch (System.Exception)
{
ViewData["Photo"] = null;
}

return View();
}
```

5. Update `launchSetting.json`. Change the following values in the `Properties\launchSettings.json` file to ensure that you start your web app from `https://localhost:44321`:
- update the `sslPort` of the `iisSettings` section to be `44321`
- update the `applicationUrl` property to `https://localhost:44321`
Loading