diff --git a/ContosoApp/App.xaml.cs b/ContosoApp/App.xaml.cs
index 19ed720..d1a30ef 100644
--- a/ContosoApp/App.xaml.cs
+++ b/ContosoApp/App.xaml.cs
@@ -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.
///
- 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);
+ }
}
}
\ No newline at end of file
diff --git a/ContosoApp/MsalHelper.cs b/ContosoApp/MsalHelper.cs
new file mode 100644
index 0000000..4aef8f7
--- /dev/null
+++ b/ContosoApp/MsalHelper.cs
@@ -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
+{
+ ///
+ /// Handles user authentication and getting user info from the Microsoft Graph API.
+ ///
+ 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> GetAccountsAsync()
+ => await _msalPublicClientApp.GetAccountsAsync();
+
+ ///
+ /// Gets an auth token for the user, which can be used to call the Microsoft Graph API.
+ ///
+ public static async Task GetTokenAsync(IEnumerable 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);
+ }
+ }
+ }
+}
diff --git a/ContosoApp/ViewModels/AuthenticationViewModel.cs b/ContosoApp/ViewModels/AuthenticationViewModel.cs
index ba2f408..173e4fb 100644
--- a/ContosoApp/ViewModels/AuthenticationViewModel.cs
+++ b/ContosoApp/ViewModels/AuthenticationViewModel.cs
@@ -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
///
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;
-
///
/// Creates a new AuthenticationViewModel for logging users in and getting their info.
///
public AuthenticationViewModel()
{
- _msalPublicClientApp = PublicClientApplicationBuilder
- .Create(Repository.Constants.AccountClientId)
- .WithAuthority(AadAuthorityAudience.AzureAdMultipleOrgs)
- .WithDefaultRedirectUri()
- .Build();
-
Task.Run(PrepareAsync);
}
@@ -178,16 +165,7 @@ namespace Contoso.App.ViewModels
///
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
}
}
- ///
- /// Gets an auth token for the user, which can be used to call the Microsoft Graph API.
- ///
- private async Task 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;
- }
-
///
/// Gets and processes the user's info from the Microsoft Graph API.
///
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;
diff --git a/ContosoRepository/Constants.cs b/ContosoRepository/Constants.cs
index 745e758..b42284a 100644
--- a/ContosoRepository/Constants.cs
+++ b/ContosoRepository/Constants.cs
@@ -43,6 +43,11 @@ namespace Contoso.Repository
/// The Azure Active Directory (AAD) client id.
///
public const string AccountClientId = "";
+
+ ///
+ /// The Azure Active Directory (AAD) rest api client id.
+ ///
+ public const string WebApiClientId = "< TODO: Insert Azure client Id>";
///
/// 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" };
+
}
}
diff --git a/ContosoRepository/Rest/HttpHelper.cs b/ContosoRepository/Rest/HttpHelper.cs
index 4acab3b..29411be 100644
--- a/ContosoRepository/Rest/HttpHelper.cs
+++ b/ContosoRepository/Rest/HttpHelper.cs
@@ -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
///
/// Makes an HTTP GET request to the given controller and returns the deserialized response content.
///
- public async Task GetAsync(string controller)
- {
+ public async Task GetAsync(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(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.
///
- public async Task PostAsync(string controller, TRequest body)
- {
+ public async Task PostAsync(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(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.
///
- 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}");
}
}
diff --git a/ContosoRepository/Rest/RestContosoRepository.cs b/ContosoRepository/Rest/RestContosoRepository.cs
index 9cd3b4e..3c273dd 100644
--- a/ContosoRepository/Rest/RestContosoRepository.cs
+++ b/ContosoRepository/Rest/RestContosoRepository.cs
@@ -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);
}
}
diff --git a/ContosoRepository/Rest/RestCustomerRepository.cs b/ContosoRepository/Rest/RestCustomerRepository.cs
index 9e79957..68ce6c8 100644
--- a/ContosoRepository/Rest/RestCustomerRepository.cs
+++ b/ContosoRepository/Rest/RestCustomerRepository.cs
@@ -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> GetAsync() =>
- await _http.GetAsync>("customer");
+ await _http.GetAsync>("customer", _accessToken);
public async Task> GetAsync(string search) =>
- await _http.GetAsync>($"customer/search?value={search}");
+ await _http.GetAsync>($"customer/search?value={search}", _accessToken);
public async Task GetAsync(Guid id) =>
- await _http.GetAsync($"customer/{id}");
+ await _http.GetAsync($"customer/{id}", _accessToken);
public async Task UpsertAsync(Customer customer) =>
- await _http.PostAsync("customer", customer);
+ await _http.PostAsync("customer", customer, _accessToken);
public async Task DeleteAsync(Guid customerId) =>
- await _http.DeleteAsync("customer", customerId);
+ await _http.DeleteAsync("customer", customerId, _accessToken);
}
}
diff --git a/ContosoRepository/Rest/RestOrderRepository.cs b/ContosoRepository/Rest/RestOrderRepository.cs
index 6ac9c1e..a557859 100644
--- a/ContosoRepository/Rest/RestOrderRepository.cs
+++ b/ContosoRepository/Rest/RestOrderRepository.cs
@@ -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> GetAsync() =>
- await _http.GetAsync>("order");
+ await _http.GetAsync>("order", _accessToken);
public async Task GetAsync(Guid id) =>
- await _http.GetAsync($"order/{id}");
+ await _http.GetAsync($"order/{id}", _accessToken);
public async Task> GetForCustomerAsync(Guid customerId) =>
- await _http.GetAsync>($"order/customer/{customerId}");
+ await _http.GetAsync>($"order/customer/{customerId}", _accessToken);
public async Task> GetAsync(string search) =>
- await _http.GetAsync>($"order/search?value={search}");
+ await _http.GetAsync>($"order/search?value={search}", _accessToken);
public async Task UpsertAsync(Order order) =>
- await _http.PostAsync("order", order);
+ await _http.PostAsync("order", order, _accessToken);
public async Task DeleteAsync(Guid orderId) =>
- await _http.DeleteAsync("order", orderId);
+ await _http.DeleteAsync("order", orderId, _accessToken);
}
}
diff --git a/ContosoRepository/Rest/RestProductRepository.cs b/ContosoRepository/Rest/RestProductRepository.cs
index 136d1c3..adc0790 100644
--- a/ContosoRepository/Rest/RestProductRepository.cs
+++ b/ContosoRepository/Rest/RestProductRepository.cs
@@ -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> GetAsync() =>
- await _http.GetAsync>("product");
+ await _http.GetAsync>("product", _accessToken);
public async Task GetAsync(Guid id) =>
- await _http.GetAsync($"product/{id}");
+ await _http.GetAsync($"product/{id}", _accessToken);
public async Task> GetAsync(string search) =>
- await _http.GetAsync>($"product/search?value={search}");
+ await _http.GetAsync>($"product/search?value={search}", _accessToken);
}
}
diff --git a/ContosoService/Contoso.Service.csproj b/ContosoService/Contoso.Service.csproj
index 1c3f16f..be4655a 100644
--- a/ContosoService/Contoso.Service.csproj
+++ b/ContosoService/Contoso.Service.csproj
@@ -1,4 +1,4 @@
-
+
net6.0
@@ -7,6 +7,7 @@
+
diff --git a/ContosoService/Controllers/CustomerController.cs b/ContosoService/Controllers/CustomerController.cs
index 77eb6d8..12a9e52 100644
--- a/ContosoService/Controllers/CustomerController.cs
+++ b/ContosoService/Controllers/CustomerController.cs
@@ -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
///
/// Contains methods for interacting with customer data.
///
- [ApiController]
+ [ApiController]
+ [Authorize(Policy = "AuthZPolicy")]
[Route("api/[controller]")]
public class CustomerController : ControllerBase
{
diff --git a/ContosoService/Program.cs b/ContosoService/Program.cs
index d499e76..f69ed2e 100644
--- a/ContosoService/Program.cs
+++ b/ContosoService/Program.cs
@@ -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);
\ No newline at end of file
+app.Run(Constants.ApiUrl);
diff --git a/ContosoService/appsettings.json b/ContosoService/appsettings.json
index 26bb0ac..f7afe2c 100644
--- a/ContosoService/appsettings.json
+++ b/ContosoService/appsettings.json
@@ -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": {
diff --git a/README.md b/README.md
index 34c0bd4..0a31e94 100644
--- a/README.md
+++ b/README.md
@@ -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
setting | Value for this sample app | Notes |
+|-------------------------------:|:--------------------------------------------------------------------|:------------------------------------------------------------------------------------------------------------|
+| **Name** | `active-directory-contoso-customer-oders-protected-api` | Suggested value for this sample.
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.
Support for the Single tenant. |
+| **Expose an API** | **Scope name**: `Contoso.ReadWrite`
**Who can consent?**: **Admins and users**
**Admin consent display name**: `Act on behalf of the user`
**Admin consent description**: `Allows the API to act on behalf of the user.`
**User consent display name**: `Act on your behalf`
**User consent description**: `Allows the API to act on your behalf.`
**State**: **Enabled** | Add a new scope that reads as follows `api://{clientId}/Contoso.ReadWrite`. Required value for this sample. |
-| App registration
setting | Value for this sample app | Notes |
-|--------------------------------:|:-------------------------------------------------------------------------------------|:--------------------------------------------------------------------------------|
-| **Name** | `active-directory-contoso-customer-oders-winui3` | Suggested value for this sample.
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
setting | Value for this sample app | Notes |
+|--------------------------------:|:-------------------------------------------------------------------------------------|:------------------------------------------------------------------------------------------------|
+| **Name** | `active-directory-contoso-customer-oders-winui3` | Suggested value for this sample.
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`
` Contoso.ReadWrite` | Add a new delegated permission for `api:///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";
...