feat (protect-api): [contoso.service] protect endpoints (#5)

This commit is contained in:
Fernando Antivero 2022-07-01 16:22:17 -03:00 коммит произвёл GitHub
Родитель e5e7b48bf2
Коммит caf5ff7d8a
Не найден ключ, соответствующий данной подписи
Идентификатор ключа GPG: 4AEE18F83AFDEB23
14 изменённых файлов: 254 добавлений и 132 удалений

Просмотреть файл

@ -23,6 +23,7 @@
// ---------------------------------------------------------------------------------
using System.IO;
using System.Threading.Tasks;
using Microsoft.EntityFrameworkCore;
using Microsoft.UI.Xaml;
using Microsoft.UI.Xaml.Media.Animation;
@ -34,7 +35,6 @@ using Contoso.App.Views;
using Contoso.Repository;
using Contoso.Repository.Rest;
using Contoso.Repository.Sql;
using System;
namespace Contoso.App
{
@ -125,7 +125,11 @@ namespace Contoso.App
/// Configures the app to use the REST data source. For convenience, a read-only source is provided.
/// You can also deploy your own copy of the REST service locally or to Azure. See the README for details.
/// </summary>
public static void UseRest() =>
Repository = new RestContosoRepository($"{Constants.ApiUrl}/api/");
public static void UseRest()
{
var accessToken = Task.Run(async () => await MsalHelper.GetTokenAsync(Constants.WebApiScopes)).Result;
Repository = new RestContosoRepository($"{Constants.ApiUrl}/api/", accessToken);
}
}
}

118
ContosoApp/MsalHelper.cs Normal file
Просмотреть файл

@ -0,0 +1,118 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
// The MIT License (MIT)
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Threading.Tasks;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
namespace Contoso.App
{
/// <summary>
/// Handles user authentication and getting user info from the Microsoft Graph API.
/// </summary>
public class MsalHelper
{
private static readonly IPublicClientApplication _msalPublicClientApp;
static MsalHelper()
{
_msalPublicClientApp = PublicClientApplicationBuilder
.Create(Repository.Constants.AccountClientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithDefaultRedirectUri()
.Build();
Task.Run(ConfigureCachingAsync);
}
private static async void ConfigureCachingAsync()
{
// Configuring the token cache
var storageProperties =
new StorageCreationPropertiesBuilder(Repository.Constants.CacheFileName, MsalCacheHelper.UserRootDirectory)
.Build();
// This hooks up the cache into MSAL
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_msalPublicClientApp.UserTokenCache);
}
public static async Task<IEnumerable<IAccount>> GetAccountsAsync()
=> await _msalPublicClientApp.GetAccountsAsync();
/// <summary>
/// Gets an auth token for the user, which can be used to call the Microsoft Graph API.
/// </summary>
public static async Task<string> GetTokenAsync(IEnumerable<String> scopes)
{
AuthenticationResult msalAuthenticationResult = null;
// Acquire a cached access token for Microsoft Graph if one is available from a prior
// execution of this process.
var accounts = await _msalPublicClientApp.GetAccountsAsync();
if (accounts.Any())
{
try
{
// Will return a cached access token if available, refreshing if necessary.
msalAuthenticationResult = await _msalPublicClientApp.AcquireTokenSilent(
scopes,
accounts.First())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Nothing in cache for this account + scope, and interactive experience required.
}
}
if (msalAuthenticationResult == null)
{
// This is likely the first authentication request in the application, so calling
// this will launch the user's default browser and send them through a login flow.
// After the flow is complete, the rest of this method will continue to execute.
msalAuthenticationResult = await _msalPublicClientApp.AcquireTokenInteractive(
scopes)
.ExecuteAsync();
// TODO: [feat] when user cancel the authN flow, the UX will be as if the login had failed. This can be improved with a more friendly UI experience on top of this.
}
return msalAuthenticationResult.AccessToken;
}
public static async Task RemoveCachedTokens()
{
// All cached tokens will be removed.
// The next token request will require the user to sign in.
foreach (var account in (await MsalHelper.GetAccountsAsync()).ToList())
{
await _msalPublicClientApp.RemoveAsync(account);
}
}
}
}

Просмотреть файл

@ -31,8 +31,6 @@ using System.Net.Http.Headers;
using System.Reflection;
using System.Threading.Tasks;
using Microsoft.Graph;
using Microsoft.Identity.Client;
using Microsoft.Identity.Client.Extensions.Msal;
using Microsoft.UI.Dispatching;
using Microsoft.UI.Xaml.Controls;
using Microsoft.UI.Xaml.Media.Imaging;
@ -44,22 +42,11 @@ namespace Contoso.App.ViewModels
/// </summary>
public class AuthenticationViewModel : BindableBase
{
// Generally, your MSAL client will have a lifecycle that matches the lifecycle
// of the user's session in the application. In this sample, the lifecycle of the
// MSAL client to the lifecycle of this form.
private readonly IPublicClientApplication _msalPublicClientApp;
/// <summary>
/// Creates a new AuthenticationViewModel for logging users in and getting their info.
/// </summary>
public AuthenticationViewModel()
{
_msalPublicClientApp = PublicClientApplicationBuilder
.Create(Repository.Constants.AccountClientId)
.WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
.WithDefaultRedirectUri()
.Build();
Task.Run(PrepareAsync);
}
@ -178,16 +165,7 @@ namespace Contoso.App.ViewModels
/// </summary>
public async Task PrepareAsync()
{
// Configuring the token cache
var storageProperties =
new StorageCreationPropertiesBuilder(Repository.Constants.CacheFileName, MsalCacheHelper.UserRootDirectory)
.Build();
// This hooks up the cache into MSAL
var cacheHelper = await MsalCacheHelper.CreateAsync(storageProperties);
cacheHelper.RegisterCache(_msalPublicClientApp.UserTokenCache);
var accounts = await _msalPublicClientApp.GetAccountsAsync();
var accounts = await MsalHelper.GetAccountsAsync();
if (accounts.Any())
{
await LoginAsync();
@ -207,7 +185,7 @@ namespace Contoso.App.ViewModels
try
{
SetVisible(vm => vm.ShowLoading);
string token = await GetTokenAsync();
string token = await MsalHelper.GetTokenAsync(Repository.Constants.GraphpApiScopes);
if (token != null)
{
await SetUserInfoAsync(token);
@ -226,53 +204,12 @@ namespace Contoso.App.ViewModels
}
}
/// <summary>
/// Gets an auth token for the user, which can be used to call the Microsoft Graph API.
/// </summary>
private async Task<string> GetTokenAsync()
{
AuthenticationResult? msalAuthenticationResult = null;
// Acquire a cached access token for Microsoft Graph if one is available from a prior
// execution of this process.
var accounts = await _msalPublicClientApp.GetAccountsAsync();
if (accounts.Any())
{
try
{
// Will return a cached access token if available, refreshing if necessary.
msalAuthenticationResult = await _msalPublicClientApp.AcquireTokenSilent(
Repository.Constants.Scopes,
accounts.First())
.ExecuteAsync();
}
catch (MsalUiRequiredException)
{
// Nothing in cache for this account + scope, and interactive experience required.
}
}
if (msalAuthenticationResult == null)
{
// This is likely the first authentication request in the application, so calling
// this will launch the user's default browser and send them through a login flow.
// After the flow is complete, the rest of this method will continue to execute.
msalAuthenticationResult = await _msalPublicClientApp.AcquireTokenInteractive(
Repository.Constants.Scopes)
.ExecuteAsync();
// TODO: [feat] when user cancel the authN flow, the UX will be as if the login had failed. This can be improved with a more friendly UI experience on top of this.
}
return msalAuthenticationResult.AccessToken;
}
/// <summary>
/// Gets and processes the user's info from the Microsoft Graph API.
/// </summary>
private async Task SetUserInfoAsync(string token)
{
var accounts = await _msalPublicClientApp.GetAccountsAsync();
var accounts = await MsalHelper.GetAccountsAsync();
var domain = accounts?.First().Username.Split('@')[1] ?? string.Empty;
var graph = new GraphServiceClient(new DelegateAuthenticationProvider(message =>
@ -347,12 +284,7 @@ namespace Contoso.App.ViewModels
};
signoutDialog.PrimaryButtonClick += async (_, _) =>
{
// All cached tokens will be removed.
// The next token request will require the user to sign in.
foreach (var account in (await _msalPublicClientApp.GetAccountsAsync()).ToList())
{
await _msalPublicClientApp.RemoveAsync(account);
}
await MsalHelper.RemoveCachedTokens();
SetVisible(vm => vm.ShowWelcome);
};
signoutDialog.XamlRoot = App.Window.Content.XamlRoot;

Просмотреть файл

@ -43,6 +43,11 @@ namespace Contoso.Repository
/// The Azure Active Directory (AAD) client id.
/// </summary>
public const string AccountClientId = "<TODO: Insert Azure client Id>";
/// <summary>
/// The Azure Active Directory (AAD) rest api client id.
/// </summary>
public const string WebApiClientId = "< TODO: Insert Azure client Id>";
/// <summary>
/// Connection string for a server-side SQL database.
@ -52,7 +57,11 @@ namespace Contoso.Repository
// Cache settings
public const string CacheFileName = "contosoapp_msal_cache.txt";
// App settings
public static readonly string[] Scopes = new[] { "https://graph.microsoft.com/User.Read" };
// Graph Api Scopes
public static readonly string[] GraphpApiScopes = new[] { "https://graph.microsoft.com/User.Read" };
// Downstream Api Scopes
public static readonly string[] WebApiScopes = new[] { $"api://{WebApiClientId}/Contoso.ReadWrite" };
}
}

Просмотреть файл

@ -25,6 +25,7 @@
using Newtonsoft.Json;
using System;
using System.Net.Http;
using System.Net.Http.Headers;
using System.Text;
using System.Threading.Tasks;
@ -48,10 +49,16 @@ namespace Contoso.Repository.Rest
/// <summary>
/// Makes an HTTP GET request to the given controller and returns the deserialized response content.
/// </summary>
public async Task<TResult> GetAsync<TResult>(string controller)
{
public async Task<TResult> GetAsync<TResult>(string controller, string accessToken)
{
if (accessToken is null)
{
throw new ArgumentNullException(nameof(accessToken));
}
using (var client = BaseClient())
{
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.GetAsync(controller);
string json = await response.Content.ReadAsStringAsync();
TResult obj = JsonConvert.DeserializeObject<TResult>(json);
@ -63,10 +70,16 @@ namespace Contoso.Repository.Rest
/// Makes an HTTP POST request to the given controller with the given object as the body.
/// Returns the deserialized response content.
/// </summary>
public async Task<TResult> PostAsync<TRequest, TResult>(string controller, TRequest body)
{
public async Task<TResult> PostAsync<TRequest, TResult>(string controller, TRequest body, string accessToken)
{
if (accessToken is null)
{
throw new ArgumentNullException(nameof(accessToken));
}
using (var client = BaseClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
var response = await client.PostAsync(controller, new JsonStringContent(body));
string json = await response.Content.ReadAsStringAsync();
TResult obj = JsonConvert.DeserializeObject<TResult>(json);
@ -78,10 +91,16 @@ namespace Contoso.Repository.Rest
/// Makes an HTTP DELETE request to the given controller and includes all the given
/// object's properties as URL parameters. Returns the deserialized response content.
/// </summary>
public async Task DeleteAsync(string controller, Guid objectId)
{
public async Task DeleteAsync(string controller, Guid objectId, string accessToken)
{
if (accessToken is null)
{
throw new ArgumentNullException(nameof(accessToken));
}
using (var client = BaseClient())
{
client.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", accessToken);
await client.DeleteAsync($"{controller}/{objectId}");
}
}

Просмотреть файл

@ -32,16 +32,18 @@ namespace Contoso.Repository.Rest
public class RestContosoRepository : IContosoRepository
{
private readonly string _url;
public RestContosoRepository(string url)
private readonly string _accessToken;
public RestContosoRepository(string url, string accessToken)
{
_url = url;
_url = url;
_accessToken = accessToken;
}
public ICustomerRepository Customers => new RestCustomerRepository(_url);
public ICustomerRepository Customers => new RestCustomerRepository(_url, _accessToken);
public IOrderRepository Orders => new RestOrderRepository(_url);
public IOrderRepository Orders => new RestOrderRepository(_url, _accessToken);
public IProductRepository Products => new RestProductRepository(_url);
public IProductRepository Products => new RestProductRepository(_url, _accessToken);
}
}

Просмотреть файл

@ -36,25 +36,27 @@ namespace Contoso.Repository.Rest
public class RestCustomerRepository : ICustomerRepository
{
private readonly HttpHelper _http;
public RestCustomerRepository(string baseUrl)
private readonly string _accessToken;
public RestCustomerRepository(string baseUrl, string accessToken)
{
_http = new HttpHelper(baseUrl);
_http = new HttpHelper(baseUrl);
_accessToken = accessToken;
}
public async Task<IEnumerable<Customer>> GetAsync() =>
await _http.GetAsync<IEnumerable<Customer>>("customer");
await _http.GetAsync<IEnumerable<Customer>>("customer", _accessToken);
public async Task<IEnumerable<Customer>> GetAsync(string search) =>
await _http.GetAsync<IEnumerable<Customer>>($"customer/search?value={search}");
await _http.GetAsync<IEnumerable<Customer>>($"customer/search?value={search}", _accessToken);
public async Task<Customer> GetAsync(Guid id) =>
await _http.GetAsync<Customer>($"customer/{id}");
await _http.GetAsync<Customer>($"customer/{id}", _accessToken);
public async Task<Customer> UpsertAsync(Customer customer) =>
await _http.PostAsync<Customer, Customer>("customer", customer);
await _http.PostAsync<Customer, Customer>("customer", customer, _accessToken);
public async Task DeleteAsync(Guid customerId) =>
await _http.DeleteAsync("customer", customerId);
await _http.DeleteAsync("customer", customerId, _accessToken);
}
}

Просмотреть файл

@ -36,28 +36,30 @@ namespace Contoso.Repository.Rest
public class RestOrderRepository : IOrderRepository
{
private readonly HttpHelper _http;
private readonly string _accessToken;
public RestOrderRepository(string baseUrl)
public RestOrderRepository(string baseUrl, string accessToken)
{
_http = new HttpHelper(baseUrl);
_accessToken = accessToken;
}
public async Task<IEnumerable<Order>> GetAsync() =>
await _http.GetAsync<IEnumerable<Order>>("order");
await _http.GetAsync<IEnumerable<Order>>("order", _accessToken);
public async Task<Order> GetAsync(Guid id) =>
await _http.GetAsync<Order>($"order/{id}");
await _http.GetAsync<Order>($"order/{id}", _accessToken);
public async Task<IEnumerable<Order>> GetForCustomerAsync(Guid customerId) =>
await _http.GetAsync<IEnumerable<Order>>($"order/customer/{customerId}");
await _http.GetAsync<IEnumerable<Order>>($"order/customer/{customerId}", _accessToken);
public async Task<IEnumerable<Order>> GetAsync(string search) =>
await _http.GetAsync<IEnumerable<Order>>($"order/search?value={search}");
await _http.GetAsync<IEnumerable<Order>>($"order/search?value={search}", _accessToken);
public async Task<Order> UpsertAsync(Order order) =>
await _http.PostAsync<Order, Order>("order", order);
await _http.PostAsync<Order, Order>("order", order, _accessToken);
public async Task DeleteAsync(Guid orderId) =>
await _http.DeleteAsync("order", orderId);
await _http.DeleteAsync("order", orderId, _accessToken);
}
}

Просмотреть файл

@ -35,19 +35,21 @@ namespace Contoso.Repository.Rest
public class RestProductRepository : IProductRepository
{
private readonly HttpHelper _http;
private readonly string _accessToken;
public RestProductRepository(string baseUrl)
public RestProductRepository(string baseUrl, string accessToken)
{
_http = new HttpHelper(baseUrl);
_accessToken = accessToken;
}
public async Task<IEnumerable<Product>> GetAsync() =>
await _http.GetAsync<IEnumerable<Product>>("product");
await _http.GetAsync<IEnumerable<Product>>("product", _accessToken);
public async Task<Product> GetAsync(Guid id) =>
await _http.GetAsync<Product>($"product/{id}");
await _http.GetAsync<Product>($"product/{id}", _accessToken);
public async Task<IEnumerable<Product>> GetAsync(string search) =>
await _http.GetAsync<IEnumerable<Product>>($"product/search?value={search}");
await _http.GetAsync<IEnumerable<Product>>($"product/search?value={search}", _accessToken);
}
}

Просмотреть файл

@ -1,4 +1,4 @@
<Project Sdk="Microsoft.NET.Sdk.Web">
<Project Sdk="Microsoft.NET.Sdk.Web">
<PropertyGroup>
<TargetFramework>net6.0</TargetFramework>
@ -7,6 +7,7 @@
<ItemGroup>
<PackageReference Include="Microsoft.EntityFrameworkCore.SqlServer" Version="6.0.5" />
<PackageReference Include="Microsoft.Identity.Web" Version="1.*" />
</ItemGroup>
<ItemGroup>

Просмотреть файл

@ -24,7 +24,8 @@
using Microsoft.AspNetCore.Mvc;
using Contoso.Models;
using Microsoft.AspNetCore.Authorization;
namespace Contoso.Service.Controllers
{
namespace Contoso.Service.Controllers
@ -32,7 +33,8 @@ namespace Contoso.Service.Controllers
/// <summary>
/// Contains methods for interacting with customer data.
/// </summary>
[ApiController]
[ApiController]
[Authorize(Policy = "AuthZPolicy")]
[Route("api/[controller]")]
public class CustomerController : ControllerBase
{

Просмотреть файл

@ -1,18 +1,18 @@
// ---------------------------------------------------------------------------------
// Copyright (c) Microsoft Corporation. All rights reserved.
//
//
// The MIT License (MIT)
//
//
// Permission is hereby granted, free of charge, to any person obtaining a copy
// of this software and associated documentation files (the "Software"), to deal
// in the Software without restriction, including without limitation the rights
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
// copies of the Software, and to permit persons to whom the Software is
// furnished to do so, subject to the following conditions:
//
//
// The above copyright notice and this permission notice shall be included in
// all copies or substantial portions of the Software.
//
//
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
@ -22,11 +22,14 @@
// THE SOFTWARE.
// ---------------------------------------------------------------------------------
using Microsoft.EntityFrameworkCore;
using Contoso.Models;
using Contoso.Repository;
using Contoso.Repository.Sql;
using System.Text.Json.Serialization;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.EntityFrameworkCore;
using Microsoft.Identity.Web;
using Contoso.Models;
using Constants = Contoso.Repository.Constants;
using Contoso.Repository.Sql;
var builder = WebApplication.CreateBuilder(args);
@ -42,6 +45,8 @@ builder.Services.AddControllers().AddJsonOptions(x =>
var app = builder.Build();
app.UseAuthorization();
if (app.Environment.IsDevelopment())
{
app.UseDeveloperExceptionPage();
@ -49,4 +54,4 @@ if (app.Environment.IsDevelopment())
app.MapControllers();
app.Run(Constants.ApiUrl);
app.Run(Constants.ApiUrl);

Просмотреть файл

@ -1,4 +1,10 @@
{
"AzureAd": {
"Instance": "https://login.microsoftonline.com/",
"ClientId": "[Enter_your_application_client_ID_from_Azure_Portal, _e.g._2ec40e65-ba09-4853-bcde-bcb60029e596]",
"TenantId": "common",
"Scepes": "Contoso.ReadWrite"
},
"Logging": {
"IncludeScopes": false,
"Debug": {

Просмотреть файл

@ -123,7 +123,7 @@ Set your startup project as **Contoso.App**, the architecture to x86 or x64, and
To fully explore the sample, you'll need to connect to your own Azure Active Directory and data source. Values you need to fill
are in [Constants.cs](ContosoRepository/Constants.cs).
* **Client Id**: Set the *AccountClientId* field to your Azure account client Id.
* **Deskptop app Client Id**: Set the *AccountClientId* field to your Azure account client Id.
* **API endpoint**: Set the value of the *BaseUrl* constant to match the url the backing service is running on.
* **Set a database connection string**: Set the connection string to one of your own local or remote databases.
* **Associate this sample with the Store**: Authentication requires store association. To associate the app with the Store,
@ -133,22 +133,31 @@ right click the project in Visual Studio and select **Store** -> *Associate App
You can then either start the service running locally, or deploy it to Azure.
### Register the Azure Active Directory app
### Register the Azure Active Directory app (Web api)
First, complete the steps in [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) to register the application.
First, complete the steps in [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) to register the web application.
Use these settings in your app registration.
| App registration <br/> setting | Value for this sample app | Notes |
|-------------------------------:|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------|
| **Name** | `active-directory-contoso-customer-oders-protected-api` | Suggested value for this sample. <br/> You can change the app name at any time. |
| **Supported account types** | **Accounts in this organizational directory only (Any Azure AD directory - Multitenant)** | Required for this sample. <br/> Support for the Single tenant. |
| **Expose an API** | **Scope name**: `Contoso.ReadWrite`<br/>**Who can consent?**: **Admins and users**<br/>**Admin consent display name**: `Act on behalf of the user`<br/>**Admin consent description**: `Allows the API to act on behalf of the user.`<br/>**User consent display name**: `Act on your behalf`<br/>**User consent description**: `Allows the API to act on your behalf.`<br/>**State**: **Enabled** | Add a new scope that reads as follows `api://{clientId}/Contoso.ReadWrite`. Required value for this sample. |
| App registration <br/> setting | Value for this sample app | Notes |
|--------------------------------:|:-------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
| **Name** | `active-directory-contoso-customer-oders-winui3` | Suggested value for this sample. <br/> You can change the app name at any time. |
| **Supported account types** | **Accounts in any organizational directory (Any Azure AD directory - Multitenant)** | Suggested value for this sample. |
| **Platform type** | **Mobile and desktop applications** | Required value for this sample |
| **Redirect URIs** | `https://login.microsoftonline.com/common/oauth2/nativeclient` | Required value for this sample
### Register the Azure Active Directory app (Client desktop app)
Then, complete the steps in [Register an application with the Microsoft identity platform](https://docs.microsoft.com/azure/active-directory/develop/quickstart-register-app) to register the deskptop application.
| App registration <br/> setting | Value for this sample app | Notes |
|--------------------------------:|:-------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------|
| **Name** | `active-directory-contoso-customer-oders-winui3` | Suggested value for this sample. <br/> You can change the app name at any time. |
| **Supported account types** | **Accounts in any organizational directory (Any Azure AD directory - Multitenant)** | Suggested value for this sample. |
| **Platform type** | **Mobile and desktop applications** | Required value for this sample |
| **Redirect URIs** | `https://login.microsoftonline.com/common/oauth2/nativeclient` | Required value for this sample |
| **API Permissions** | `active-directory-contoso-customer-oders-protected-api` <br/> ` Contoso.ReadWrite` | Add a new delegated permission for `api://<application-id>/Contoso.ReadWrite`. Required value for this sample. |
### Run locally (SQLite)
1. Navigate to [Constants.cs](ContosoRepository/Constants.cs) and complete the following values:
1. Navigate to [Constants.cs](ContosoRepository/Constants.cs) and complete the following value using the `active-directory-contoso-customer-oders-winui3` Application (client) ID from Azure Portal:
```csharp
...
@ -191,12 +200,21 @@ Use these settings in your app registration.
```
1. Right-click the solution, choose *Properties*, and choose to start both **Contoso.App** and **Contoso.Service** at the same time.
1. Open the [appsettings.json](ContosoService/appsettings.json) file and modify the following field using the `active-directory-contoso-customer-oders-protected-api` Application (client) ID from Azure Portal:
```json
...
"ClientId": "Enter_the_Application_Id_here"
...
```
1. Navigate to [Constants.cs](ContosoRepository/Constants.cs) and complete the following values:
```csharp
public const string ApiUrl = @"http://localhost:65027";
...
public const string AccountClientId = "Application_Client_ID_From_Azure_Portal";
public const string WebApiClientId = "Application_Client_ID_From_Azure_Portal";
...
public const string SqlAzureConnectionString = "Data Source=(LocalDB)\\ContosoDb;Initial Catalog=CONTOSODB;Integrated Security=True";
...