Skip to content

Commit 1ed1076

Browse files
authored
Update to use Web API scopes from a different client
Update to project to use Web API scopes
2 parents f28594f + 3c2d335 commit 1ed1076

10 files changed

Lines changed: 193 additions & 100 deletions

File tree

NativeClient-DotNet.sln

Lines changed: 10 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11

22
Microsoft Visual Studio Solution File, Format Version 12.00
3-
# Visual Studio 2013
4-
VisualStudioVersion = 12.0.21005.1
3+
# Visual Studio 15
4+
VisualStudioVersion = 15.0.27130.2027
55
MinimumVisualStudioVersion = 10.0.40219.1
66
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoListService", "TodoListService\TodoListService.csproj", "{047CDEF7-D5BA-4B1E-9748-910B610CCA51}"
77
EndProject
88
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "TodoListClient", "TodoListClient\TodoListClient.csproj", "{005E6BB1-F422-4366-B548-1BDE150F0073}"
99
EndProject
10+
Project("{2150E333-8FDC-42A3-9474-1A3956D46DE8}") = "Solution Items", "Solution Items", "{45D9F709-B54B-446B-9837-0052C0B5E75F}"
11+
ProjectSection(SolutionItems) = preProject
12+
README.md = README.md
13+
EndProjectSection
14+
EndProject
1015
Global
1116
GlobalSection(SolutionConfigurationPlatforms) = preSolution
1217
Debug|Any CPU = Debug|Any CPU
@@ -25,4 +30,7 @@ Global
2530
GlobalSection(SolutionProperties) = preSolution
2631
HideSolutionNode = FALSE
2732
EndGlobalSection
33+
GlobalSection(ExtensibilityGlobals) = postSolution
34+
SolutionGuid = {AFF3D3FB-F054-4729-802D-F148E1224E7D}
35+
EndGlobalSection
2836
EndGlobal

README.md

Lines changed: 104 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,104 @@
1-
# AppModelv2-NativeClient-DotNet
1+
---
2+
services: active-directory
3+
platforms: dotnet
4+
author: jmprieur
5+
level: 200
6+
client: Windows Desktop WPF
7+
service: ASP.NET Web API
8+
endpoint: AAD V2
9+
---
10+
11+
# Calling an ASP.NET Web API protected by the Azure AD V2 endpoint from an Windows Desktop (WPF) application
12+
13+
## About this sample
14+
15+
### Scenario
16+
17+
You expose a Web API and you want to protect it so that only authenticated user can access it. This sample shows how to expose a ASP.NET Web API so it can accept tokens issued by personal accounts (including outlook.com, live.com, and others) as well as work and school accounts from any company or organization that has integrated with Azure Active Directory.
18+
19+
The sample also include a Windows Desktop application (WPF) that demonstrate how you can request an access token to access a Web APIs.
20+
21+
## How to run this sample
22+
23+
> Pre-requisites: This sample requires Visual Studio 2017. If you don't have it, download [Visual Studio 2017 for free](https://www.visualstudio.com/downloads/).
24+
25+
### Step 1: Download or clone this sample
26+
27+
You can clone this sample from your shell or command line:
28+
29+
```console
30+
git clone https://github.com/AzureADQuickStarts/AppModelv2-NativeClient-DotNet.git
31+
```
32+
33+
### Step 2: Register your Web API - *TodoListService* in the *Application registration portal*
34+
35+
1. Sign in to the [Application registration portal](https://apps.dev.microsoft.com/portal/register-app) either using a personal Microsoft account (live.com or hotmail.com) or work or school account.
36+
1. Give a name to your Application, such as `AppModelv2-NativeClient-DotNet-TodoListService`. Make sure that the *Guided Setup* option is **Unchecked** then press **Create**. The portal will assign your app a globally unique *Application ID* that you'll use later in your code.
37+
1. Click **Add Platform**, and select **Web API**
38+
1. Click **Save**
39+
40+
> Note: When you add a *Web API* the Application registration portal, it adds a pre-defined App Id URI and Scope, using the format *api://{Application Id}/{Scope Name}* named **access_as_user** (you can review it by clicking 'Edit' button). This sample code uses this default scope.
41+
42+
### Step 3: Configure your *TodoListService* and *TodoListClient* projects to match the Web API you just registered
43+
44+
1. Open the solution in Visual Studio and then open the **Web.config** file under the root of **TodoListService** project.
45+
1. Replace the value of `ida:ClientId` parameter with the **Application Id** from the application you just registered in the Application Registration Portal.
46+
47+
#### Step 3.1: Add the new scope to the *TodoListClient*`s app.config
48+
49+
1. Open the **app.config** file located in **TodoListClient** project's root folder and then paste **Application Id** from the application you just registered for your *TodoListService* under `TodoListServiceScope` parameter, replacing the string `{Enter the Application Id of your TodoListService from the app registration portal}`.
50+
51+
> Note: Make sure it uses has the format `api://{TodoListService-Application-Id}/access_as_user` (where {TodoListService-Application-Id} is the Guid representing the Application Id for your TodoListService).
52+
53+
### Step 4: Register the *TodoListClient* application in the *Application registration portal*
54+
55+
In this step, you configure your *TodoListClient* project by registering a new application in the Application registration portal. In the cases where the client and server are considered *the same application* you may also just reuse the same application registered in the 'Step 2.'.
56+
57+
1. Go back to [Application registration portal](https://apps.dev.microsoft.com/portal/register-app) to register a new application
58+
1. Give a name to your Application, such as `NativeClient-DotNet-TodoListClient`, make sure that the *Guided Setup* option is **Unchecked** then press **Create**.
59+
1. Click **Add Platform**, and select **Native**.
60+
1. Click **Save**
61+
62+
### Step 5: Configure your *TodoListClient* project
63+
64+
1. In the *Application registration portal*, copy the value of the **Application Id**
65+
1. Open the **app.config** file located in the **TodoListClient** project's root folder and then paste the value in the `ida:ClientId` parameter value
66+
67+
### Step 6: Run your project
68+
69+
1. Press `<F5>` to run your project. Your *TodoListClient* should open.
70+
1. Select **Sign in** in the top right and sign in with the same user you have used to register your aplication, or a user in the same directory.
71+
1. At this point, if you are signing in for the first time, you may be prompted to consent to *TodoListService* Web Api.
72+
1. The sign-in also request the access token to the *access_as_user* scope to access *TodoListService* Web Api and manipulate the *To-Do* list.
73+
74+
### Step 7: Pre-authorize your client application
75+
76+
One of the ways to allow users from other directories to acces your Web API is by *pre-authorizing* the client applications to access your Web API by adding the Application Ids from client applications in the list of *pre-authorized* applications for your Web API. By adding a pre-authorized client, you will not require user to consent to use your Web API. Follow the steps below to pre-authorize your Web Application::
77+
78+
1. Go back to the *Application registration portal* and open the properties of your **TodoListService**.
79+
1. In the **Web API platform**, click on **Add application** under the *Pre-authorized applications* section.
80+
1. In the *Application ID* field, paste the application ID of the `TodoListClient` application.
81+
1. In the *Scope* field, click on the **Select** combo box and select the scope for this Web API `api://<Application ID>/access_as_user`.
82+
1. Press the **Save** button at the bottom of the page.
83+
84+
### Step 8: Run your project
85+
86+
1. Press `<F5>` to run your project. Your *TodoListClient* should open.
87+
1. Select **Sign in** in the top right (or Clear Cache/Sign-in) and then sign-in either using a personal Microsoft account (live.com or hotmail.com) or work or school account.
88+
89+
## Optional: Restrict sign-in access to your application
90+
91+
By default, when you download this code sample and configure the application to use the Azure Active Directory v2 endpoint following the preceeding steps, both personal accounts - like outlook.com, live.com, and others - as well as Work or school accounts from any organizations that are integrated with Azure AD can request tokens and access your Web API.
92+
93+
To restrict who can sign in to your application, use one of the options:
94+
95+
### Option 1: Restrict access to a single organization (single-tenant)
96+
97+
You can restrict sign-in access for your application to only user accounts that are in a single Azure AD tenant - including *guest accounts* of that tenant. This scenario is a common for *line-of-business applications*:
98+
99+
1. In the **web.config** file of your **TodoListService**, change the value for the `Tenant` parameter from `Common` to the tenant name of the organization, such as `contoso.onmicrosoft.com` or the *Tenant Id*.
100+
2. Open **App_Start\Startup.Auth** file and set the `ValidateIssuer` argument to `true`.
101+
102+
#### Option 2: Use a custom method to validate issuers
103+
104+
You can implement a custom method to validate issuers by using the **IssuerValidator** parameter. For more information about how to use this parameter, read about the [TokenValidationParameters class](https://msdn.microsoft.com/library/system.identitymodel.tokens.tokenvalidationparameters.aspx) on MSDN.

TodoListClient/App.config

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -6,9 +6,10 @@
66
<appSettings>
77
<!-- ida:Client is a GUID representing the Application Id for the TodoListClient app that you copied from
88
the App Registration Portal (https://apps.dev.microsoft.com) -->
9+
910
<add key="ida:ClientId" value="{Enter the Application Id that you copied from the App Registration Portal.}" />
1011

11-
<!-- todo:Scope is either:
12+
<!-- TodoListServiceScope is either:
1213
- the same as ida:ClientId, as V2 apps enable several platforms for a same application (a GUID)
1314
- or otherwise the scope of the Web API created with aht App Registration portal, for instance api://[V2-WebApi-AppId]/access_as_user
1415
where [V2-WebApi-AppId] is a GUID representing the Application ID of the Web API.
@@ -17,8 +18,9 @@
1718
In that case (V1 app), the Authority used to build the PubliClientApplication in MainWindow.xaml.cs should be set to
1819
"https://login.microsoftonline.com/organizations/" instead of "https://login.microsoftonline.com/common/"
1920
-->
20-
<add key="todo:Scope" value="{Enter the scope of the Web API, as copied from the App Registration Portal, for instance api://[WebApi-AppId]/access_as_user where [WebApi-AppId] is a GUID" />
21-
22-
<add key="todo:TodoListBaseAddress" value="https://localhost:44321/" />
21+
22+
<add key="TodoListServiceScope" value="{Enter the Application Id of your TodoListService from the app registration portal}" />
23+
<add key="TodoListServiceBaseAddress" value="https://localhost:44321/" />
24+
2325
</appSettings>
2426
</configuration>

TodoListClient/MainWindow.xaml.cs

Lines changed: 12 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -39,28 +39,26 @@ public partial class MainWindow : Window
3939
// The Authority is the sign-in URL.
4040

4141

42-
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
42+
private string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
43+
44+
// The todoListServiceBaseAddress is the address of your Web API
45+
private string todoListServiceBaseAddress = ConfigurationManager.AppSettings["TodoListServiceBaseAddress"];
46+
private string todoListServiceScope = ConfigurationManager.AppSettings["TodoListServiceScope"];
4347

44-
// The todoListBaseAddress is the address of your Web API
45-
private static string todoListBaseAddress = ConfigurationManager.AppSettings["todo:TodoListBaseAddress"];
46-
private static string todoListScope = ConfigurationManager.AppSettings["todo:Scope"];
4748

4849
private HttpClient httpClient = new HttpClient();
4950
private PublicClientApplication app = null;
5051

51-
private string[] Scopes
52-
{
53-
get
54-
{
55-
return new string[] { todoListScope };
56-
}
57-
}
52+
private string[] Scopes = null;
53+
5854
protected override async void OnInitialized(EventArgs e)
5955
{
6056
base.OnInitialized(e);
6157

58+
Scopes = new string[] {todoListServiceScope};
59+
6260
// Initialize the PublicClientApplication
63-
app = new PublicClientApplication(clientId, "https://login.microsoftonline.com/common/", TokenCacheHelper.GetUserCache());
61+
app = new PublicClientApplication(clientId, "https://login.microsoftonline.com/common/v2.0", TokenCacheHelper.GetUserCache());
6462
AuthenticationResult result = null;
6563

6664
// TODO: Check if the user is already signed in.
@@ -155,7 +153,7 @@ private async void GetTodoList()
155153
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
156154

157155
// Call the To Do list service.
158-
HttpResponseMessage response = await httpClient.GetAsync(todoListBaseAddress + "/api/todolist");
156+
HttpResponseMessage response = await httpClient.GetAsync(todoListServiceBaseAddress + "/api/todolist");
159157

160158
if (response.IsSuccessStatusCode)
161159
{
@@ -211,7 +209,7 @@ private async void AddTodoItem(object sender, RoutedEventArgs e)
211209
httpClient.DefaultRequestHeaders.Authorization = new AuthenticationHeaderValue("Bearer", result.AccessToken);
212210

213211
HttpContent content = new FormUrlEncodedContent(new[] { new KeyValuePair<string, string>("Title", TodoText.Text) });
214-
HttpResponseMessage response = await httpClient.PostAsync(todoListBaseAddress + "/api/todolist", content);
212+
HttpResponseMessage response = await httpClient.PostAsync(todoListServiceBaseAddress + "/api/todolist", content);
215213

216214
if (response.IsSuccessStatusCode)
217215
{

TodoListService/App_Start/OpenIdConnectCachingSecurityTokenProvider.cs renamed to TodoListService/App_Start/OpenIdConnectSecurityTokenProvider.cs

Lines changed: 13 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,28 +1,27 @@
1-
using Microsoft.IdentityModel.Protocols;
2-
using Microsoft.Owin.Security.Jwt;
3-
using System;
4-
using System.Collections.Generic;
5-
using System.Linq;
6-
using System.Text;
7-
using System.Threading;
8-
using System.Threading.Tasks;
1+
using System.Collections.Generic;
92
using System.IdentityModel.Tokens;
3+
using System.Threading;
4+
using Microsoft.IdentityModel.Protocols;
5+
using Microsoft.Owin.Security.Jwt;
106

11-
namespace TodoListService.App_Start
7+
namespace TodoListService
128
{
13-
public class OpenIdConnectCachingSecurityTokenProvider : IIssuerSecurityTokenProvider
9+
// This class is necessary because the OAuthBearer Middleware does not leverage
10+
// the OpenID Connect metadata endpoint exposed by the STS by default.
11+
12+
public class OpenIdConnectSecurityTokenProvider : IIssuerSecurityTokenProvider
1413
{
15-
public ConfigurationManager<OpenIdConnectConfiguration> _configManager;
14+
public ConfigurationManager<OpenIdConnectConfiguration> ConfigManager;
1615
private string _issuer;
1716
private IEnumerable<SecurityToken> _tokens;
1817
private readonly string _metadataEndpoint;
1918

2019
private readonly ReaderWriterLockSlim _synclock = new ReaderWriterLockSlim();
2120

22-
public OpenIdConnectCachingSecurityTokenProvider(string metadataEndpoint)
21+
public OpenIdConnectSecurityTokenProvider(string metadataEndpoint)
2322
{
2423
_metadataEndpoint = metadataEndpoint;
25-
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint);
24+
ConfigManager = new ConfigurationManager<OpenIdConnectConfiguration>(metadataEndpoint);
2625

2726
RetrieveMetadata();
2827
}
@@ -78,7 +77,7 @@ private void RetrieveMetadata()
7877
_synclock.EnterWriteLock();
7978
try
8079
{
81-
OpenIdConnectConfiguration config = _configManager.GetConfigurationAsync().Result;
80+
OpenIdConnectConfiguration config = ConfigManager.GetConfigurationAsync().Result;
8281
_issuer = config.Issuer;
8382
_tokens = config.SigningTokens;
8483
}

TodoListService/App_Start/Startup.Auth.cs

Lines changed: 16 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -5,40 +5,36 @@
55
using Microsoft.Owin.Security;
66
using Owin;
77
using System.IdentityModel.Tokens;
8-
using TodoListService.App_Start;
98
using Microsoft.Owin.Security.Jwt;
109
using Microsoft.Owin.Security.OAuth;
1110

1211
namespace TodoListService
1312
{
1413
public partial class Startup
1514
{
16-
private static string audience = ConfigurationManager.AppSettings["ida:Audience"];
15+
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
1716

1817
public void ConfigureAuth(IAppBuilder app)
1918
{
20-
var tvps = new TokenValidationParameters
21-
{
22-
ValidAudience = audience,
23-
24-
// In a real applicaiton, you might use issuer validation to
25-
// verify that the user's organization (if applicable) has
26-
// signed up for the app. Here, we'll just turn it off.
27-
ValidateIssuer = false,
28-
};
29-
30-
// Set up the OWIN auth pipeline to use OAuth 2.0 Bearer authentication.
31-
// The options provided here tell the middleware about the type of tokens
32-
// that will be recieved, which are JWTs for the v2.0 endpoint.
33-
34-
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthenticaitonMiddleware uses a
19+
// NOTE: The usual WindowsAzureActiveDirectoryBearerAuthentication middleware uses a
3520
// metadata endpoint which is not supported by the v2.0 endpoint. Instead, this
36-
// OpenIdConenctCachingSecurityTokenProvider can be used to fetch & use the OpenIdConnect
37-
// metadata document.
21+
// OpenIdConnectSecurityTokenProvider implementation can be used to fetch & use the OpenIdConnect
22+
// metadata document - which for the v2 endpoint is https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration
3823

3924
app.UseOAuthBearerAuthentication(new OAuthBearerAuthenticationOptions
4025
{
41-
AccessTokenFormat = new Microsoft.Owin.Security.Jwt.JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")),
26+
AccessTokenFormat = new JwtFormat(
27+
new TokenValidationParameters
28+
{
29+
// Check if the audience is intended to be this application
30+
ValidAudience = clientId,
31+
32+
// Change below to 'true' if you want this Web API to accept tokens issued to one Azure AD tenant only (single-tenant)
33+
ValidateIssuer = false,
34+
35+
},
36+
new OpenIdConnectSecurityTokenProvider("https://login.microsoftonline.com/common/v2.0/.well-known/openid-configuration")
37+
),
4238
});
4339
}
4440
}

0 commit comments

Comments
 (0)