Skip to content

Commit 31de333

Browse files
author
Kalyan Krishna
committed
merged
2 parents aabf5e7 + 37e4dfa commit 31de333

File tree

14 files changed

+173
-107
lines changed

14 files changed

+173
-107
lines changed

README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -205,7 +205,7 @@ Open the project in your IDE (like Visual Studio or Visual Studio Code) to confi
205205
1. Open the `TodoListClient\App.Config` file.
206206
1. Find the key `ida:Tenant` and replace the existing value with your Azure AD tenant name.
207207
1. Find the key `ida:ClientId` and replace the existing value with the application ID (clientId) of `TodoListClient-ManualJwt` app copied from the Azure portal.
208-
1. Find the key `todo:TodoListResourceId` and replace the existing value with the App ID URI you registered earlier, when exposing an API. For instance use `api://<application_id>`.
208+
1. Find the key `todo:TodoListResourceId` and replace the value with the App ID URI you registered earlier, when exposing an API. For instance use `api://<application_id>`.
209209
1. Find the key `todo:TodoListBaseAddress` and replace the existing value with the base address of `TodoListService-ManualJwt` (by default `https://localhost:44324`).
210210

211211
## Running the sample

TodoListClient/TodoItem.cs renamed to Shared/Models/TodoItem.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -22,9 +22,9 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2222
SOFTWARE.
2323
*/
2424

25-
namespace TodoListClient
25+
namespace Shared.Models
2626
{
27-
internal class TodoItem
27+
public class TodoItem
2828
{
2929
public string Title { get; set; }
3030
public string Owner { get; set; }

Shared/Properties/AssemblyInfo.cs

Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
using System.Reflection;
2+
using System.Runtime.CompilerServices;
3+
using System.Runtime.InteropServices;
4+
5+
// General Information about an assembly is controlled through the following
6+
// set of attributes. Change these attribute values to modify the information
7+
// associated with an assembly.
8+
[assembly: AssemblyTitle("Shared")]
9+
[assembly: AssemblyDescription("")]
10+
[assembly: AssemblyConfiguration("")]
11+
[assembly: AssemblyCompany("")]
12+
[assembly: AssemblyProduct("Shared")]
13+
[assembly: AssemblyCopyright("Copyright © 2021")]
14+
[assembly: AssemblyTrademark("")]
15+
[assembly: AssemblyCulture("")]
16+
17+
// Setting ComVisible to false makes the types in this assembly not visible
18+
// to COM components. If you need to access a type in this assembly from
19+
// COM, set the ComVisible attribute to true on that type.
20+
[assembly: ComVisible(false)]
21+
22+
// The following GUID is for the ID of the typelib if this project is exposed to COM
23+
[assembly: Guid("f4e8b876-762e-4eec-99bc-7ef12b01799d")]
24+
25+
// Version information for an assembly consists of the following four values:
26+
//
27+
// Major Version
28+
// Minor Version
29+
// Build Number
30+
// Revision
31+
//
32+
// You can specify all the values or you can default the Build and Revision Numbers
33+
// by using the '*' as shown below:
34+
// [assembly: AssemblyVersion("1.0.*")]
35+
[assembly: AssemblyVersion("1.0.0.0")]
36+
[assembly: AssemblyFileVersion("1.0.0.0")]

Shared/Shared.csproj

Lines changed: 49 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,49 @@
1+
<?xml version="1.0" encoding="utf-8"?>
2+
<Project ToolsVersion="15.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
3+
<Import Project="$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props" Condition="Exists('$(MSBuildExtensionsPath)\$(MSBuildToolsVersion)\Microsoft.Common.props')" />
4+
<PropertyGroup>
5+
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
6+
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
7+
<ProjectGuid>{F4E8B876-762E-4EEC-99BC-7EF12B01799D}</ProjectGuid>
8+
<OutputType>Library</OutputType>
9+
<AppDesignerFolder>Properties</AppDesignerFolder>
10+
<RootNamespace>Shared</RootNamespace>
11+
<AssemblyName>Shared</AssemblyName>
12+
<TargetFrameworkVersion>v4.5</TargetFrameworkVersion>
13+
<FileAlignment>512</FileAlignment>
14+
<Deterministic>true</Deterministic>
15+
<TargetFrameworkProfile />
16+
</PropertyGroup>
17+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
18+
<DebugSymbols>true</DebugSymbols>
19+
<DebugType>full</DebugType>
20+
<Optimize>false</Optimize>
21+
<OutputPath>bin\Debug\</OutputPath>
22+
<DefineConstants>DEBUG;TRACE</DefineConstants>
23+
<ErrorReport>prompt</ErrorReport>
24+
<WarningLevel>4</WarningLevel>
25+
</PropertyGroup>
26+
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
27+
<DebugType>pdbonly</DebugType>
28+
<Optimize>true</Optimize>
29+
<OutputPath>bin\Release\</OutputPath>
30+
<DefineConstants>TRACE</DefineConstants>
31+
<ErrorReport>prompt</ErrorReport>
32+
<WarningLevel>4</WarningLevel>
33+
</PropertyGroup>
34+
<ItemGroup>
35+
<Reference Include="System" />
36+
<Reference Include="System.Core" />
37+
<Reference Include="System.Xml.Linq" />
38+
<Reference Include="System.Data.DataSetExtensions" />
39+
<Reference Include="Microsoft.CSharp" />
40+
<Reference Include="System.Data" />
41+
<Reference Include="System.Net.Http" />
42+
<Reference Include="System.Xml" />
43+
</ItemGroup>
44+
<ItemGroup>
45+
<Compile Include="Properties\AssemblyInfo.cs" />
46+
<Compile Include="Models\TodoItem.cs" />
47+
</ItemGroup>
48+
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
49+
</Project>

TodoListClient/App.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -7,8 +7,8 @@
77
<add key="ida:Tenant" value="[Enter tenant name, e.g. contoso.onmicrosoft.com]" />
88
<add key="ida:ClientId" value="[Enter client ID of the TodoListClient-ManualJwt as obtained from Azure Portal, e.g. 82692da5-a86f-44c9-9d53-2f88d52b478b]" />
99
<add key="todo:TodoListResourceId" value="[Enter App ID URI of TodoListService-ManualJwt, e.g. api://{clientID}]" />
10+
<add key="todo:TodoListScope" value="access_as_user" />
1011
<add key="ida:AADInstance" value="https://login.microsoftonline.com/{0}/v2.0" />
1112
<add key="todo:TodoListBaseAddress" value="https://localhost:44324" />
12-
<add key="todo:TodoListScope" value="access_as_user" />
1313
</appSettings>
1414
</configuration>

TodoListClient/MainWindow.xaml.cs

Lines changed: 10 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -23,6 +23,7 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
*/
2424

2525
using Microsoft.Identity.Client;
26+
using Shared.Models;
2627
using System;
2728
using System.Collections.Generic;
2829
using System.Configuration;
@@ -49,20 +50,17 @@ public partial class MainWindow : Window
4950
// The Redirect URI is the URI where Azure AD will return OAuth responses.
5051
// The Authority is the sign-in URL of the tenant.
5152
//
52-
private static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
53-
54-
private static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
55-
private static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
56-
57-
private static string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
53+
private static string authority =
54+
String.Format(
55+
CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["ida:AADInstance"], ConfigurationManager.AppSettings["ida:Tenant"]);
5856

5957
//
6058
// To authenticate to the To Do list service, the client needs to know the service's App ID URI.
6159
// To contact the To Do list service we need it's URL as well.
6260
//
61+
private static string todoListBaseAddress = ConfigurationManager.AppSettings["todo:TodoListBaseAddress"];
6362
private static string todoListResourceId = ConfigurationManager.AppSettings["todo:TodoListResourceId"];
6463

65-
private static string todoListBaseAddress = ConfigurationManager.AppSettings["todo:TodoListBaseAddress"];
6664
public static string[] scopes = { $"{todoListResourceId}/{ConfigurationManager.AppSettings["todo:TodoListScope"]}" };
6765

6866
private HttpClient httpClient = new HttpClient();
@@ -76,7 +74,7 @@ public partial class MainWindow : Window
7674
public MainWindow()
7775
{
7876
InitializeComponent();
79-
_app = PublicClientApplicationBuilder.Create(clientId)
77+
_app = PublicClientApplicationBuilder.Create(ConfigurationManager.AppSettings["ida:ClientId"])
8078
.WithAuthority(authority)
8179
.WithDefaultRedirectUri()
8280
.Build();
@@ -85,10 +83,7 @@ public MainWindow()
8583
GetTodoList();
8684
}
8785

88-
private void GetTodoList()
89-
{
90-
GetTodoList(SignInButton.Content.ToString() != clearCacheString);
91-
}
86+
private void GetTodoList() => GetTodoList(SignInButton.Content.ToString() != clearCacheString);
9287

9388
private async void GetTodoList(bool isAppStarting)
9489
{
@@ -156,14 +151,12 @@ private async void GetTodoList(bool isAppStarting)
156151
Dispatcher.Invoke(() =>
157152
{
158153
TodoList.ItemsSource = toDoArray.Select(t => new { t.Title });
159-
});
154+
});
160155
}
161156
else
162157
{
163158
MessageBox.Show("An error occurred : " + response.ReasonPhrase);
164159
}
165-
166-
return;
167160
}
168161

169162
private async void AddTodoItem(object sender, RoutedEventArgs e)
@@ -305,9 +298,6 @@ private async void SignIn(object sender = null, RoutedEventArgs args = null)
305298

306299
MessageBox.Show(message);
307300
}
308-
309-
UserName.Content = Properties.Resources.UserNotSignedIn;
310-
//SignInButton.Content = signInString;
311301
}
312302
}
313303

@@ -322,7 +312,9 @@ private void SetUserName(IAccount userInfo)
322312
}
323313

324314
if (userName == null)
315+
{
325316
userName = Properties.Resources.UserNotIdentified;
317+
}
326318

327319
UserName.Content = userName;
328320
}

TodoListClient/TodoListClient-ManualJwt.csproj

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -34,8 +34,8 @@
3434
<WarningLevel>4</WarningLevel>
3535
</PropertyGroup>
3636
<ItemGroup>
37-
<Reference Include="Microsoft.Identity.Client, Version=4.8.2.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae, processorArchitecture=MSIL">
38-
<HintPath>..\packages\Microsoft.Identity.Client.4.8.2\lib\net45\Microsoft.Identity.Client.dll</HintPath>
37+
<Reference Include="Microsoft.Identity.Client, Version=4.35.1.0, Culture=neutral, PublicKeyToken=0a613f4dd989e8ae, processorArchitecture=MSIL">
38+
<HintPath>..\packages\Microsoft.Identity.Client.4.35.1\lib\net45\Microsoft.Identity.Client.dll</HintPath>
3939
</Reference>
4040
<Reference Include="System" />
4141
<Reference Include="System.Configuration" />
@@ -64,7 +64,6 @@
6464
<SubType>Designer</SubType>
6565
</ApplicationDefinition>
6666
<Compile Include="FileCache.cs" />
67-
<Compile Include="TodoItem.cs" />
6867
<Page Include="MainWindow.xaml">
6968
<Generator>MSBuild:Compile</Generator>
7069
<SubType>Designer</SubType>
@@ -108,7 +107,12 @@
108107
<SubType>Designer</SubType>
109108
</None>
110109
</ItemGroup>
111-
<ItemGroup />
110+
<ItemGroup>
111+
<ProjectReference Include="..\Shared\Shared.csproj">
112+
<Project>{f4e8b876-762e-4eec-99bc-7ef12b01799d}</Project>
113+
<Name>Shared</Name>
114+
</ProjectReference>
115+
</ItemGroup>
112116
<Import Project="$(MSBuildToolsPath)\Microsoft.CSharp.targets" />
113117
<!-- To modify your build process, add your task inside one of the targets below and uncomment it.
114118
Other similar extension points exist, see Microsoft.Common.targets.

TodoListClient/packages.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
11
<?xml version="1.0" encoding="utf-8"?>
22
<packages>
3-
<package id="Microsoft.Identity.Client" version="4.8.2" targetFramework="net45" />
3+
<package id="Microsoft.Identity.Client" version="4.35.1" targetFramework="net45" />
44
</packages>

TodoListService-ManualJwt/Controllers/TodoListController.cs

Lines changed: 11 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -23,14 +23,14 @@ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
2323
*/
2424

2525
// The following using statements were added for this sample.
26+
using Shared.Models;
2627
using System.Collections.Concurrent;
2728
using System.Collections.Generic;
2829
using System.Linq;
2930
using System.Net;
3031
using System.Net.Http;
3132
using System.Security.Claims;
3233
using System.Web.Http;
33-
using TodoListService_ManualJwt.Models;
3434

3535
namespace TodoListService_ManualJwt.Controllers
3636
{
@@ -42,7 +42,7 @@ public class TodoListController : ApiController
4242
// GET api/todolist
4343
public IEnumerable<TodoItem> Get()
4444
{
45-
this.CheckExpectedClaim();
45+
CheckExpectedClaim();
4646

4747
// A user's To Do list is keyed off of the NameIdentifier claim, which contains an immutable, unique identifier for the user.
4848
Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
@@ -55,15 +55,19 @@ public IEnumerable<TodoItem> Get()
5555
// POST api/todolist
5656
public void Post(TodoItem todo)
5757
{
58-
this.CheckExpectedClaim();
58+
CheckExpectedClaim();
5959

6060
if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
6161
{
6262
todoBag.Add(new TodoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });
6363
}
6464
}
6565

66-
/// <summary>Checks that the expected claim that proves that the Api was provisioned in a target tenant and consented by an admin/user.</summary>
66+
/// <summary>
67+
/// Checks that the expected claim that proves that the Api was provisioned in a target tenant and consented by an admin/user.
68+
/// </summary>
69+
///
70+
// Although we've already checked for the scope in Global.asax.cs, usually you may check the scope in the controller as explained below.
6771
private void CheckExpectedClaim()
6872
{
6973
//
@@ -72,7 +76,9 @@ private void CheckExpectedClaim()
7276

7377
if (!ClaimsPrincipal.Current.HasClaim(ClaimConstants.ScopeClaimType, ClaimConstants.ScopeClaimValue))
7478
{
75-
throw new HttpResponseException(new HttpResponseMessage { StatusCode = HttpStatusCode.Unauthorized, ReasonPhrase = $"The Scope claim does not contain '{ClaimConstants.ScopeClaimValue}' or scope claim not found" });
79+
throw new HttpResponseException(
80+
new HttpResponseMessage {
81+
StatusCode = HttpStatusCode.Unauthorized, ReasonPhrase = $"The Scope claim does not contain '{ClaimConstants.ScopeClaimValue}' or scope claim not found" });
7682
}
7783
}
7884
}

TodoListService-ManualJwt/Global.asax.cs

Lines changed: 21 additions & 20 deletions
Original file line numberDiff line numberDiff line change
@@ -68,22 +68,15 @@ internal class TokenValidationHandler : DelegatingHandler
6868
// The Authority is the sign-in URL of the tenant.
6969
// The Audience is the value of one of the 'aud' claims the service expects to find in token to assure the token is addressed to it.
7070

71-
private string _audience;
72-
private string _authority;
73-
private string _clientId;
74-
private ConfigurationManager<OpenIdConnectConfiguration> _configManager;
75-
private string _tenant;
71+
private string _audience = ConfigurationManager.AppSettings["ida:Audience"];
72+
private string _clientId = ConfigurationManager.AppSettings["ida:ClientId"];
73+
private string _tenant = ConfigurationManager.AppSettings["ida:TenantId"];
7674
private ISecurityTokenValidator _tokenValidator;
75+
private string _authority;
7776

7877
public TokenValidationHandler()
7978
{
80-
_audience = ConfigurationManager.AppSettings["ida:Audience"];
81-
_clientId = ConfigurationManager.AppSettings["ida:ClientId"];
82-
var aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
83-
_tenant = ConfigurationManager.AppSettings["ida:TenantId"];
84-
_authority = string.Format(CultureInfo.InvariantCulture, aadInstance, _tenant);
85-
// The ConfigurationManager class holds properties to control the metadata refresh interval. For more details, https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.protocols.configurationmanager-1?view=azure-dotnet
86-
_configManager = new ConfigurationManager<OpenIdConnectConfiguration>($"{_authority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
79+
_authority = string.Format(CultureInfo.InvariantCulture, ConfigurationManager.AppSettings["ida:AADInstance"], _tenant);
8780
_tokenValidator = new JwtSecurityTokenHandler();
8881
}
8982

@@ -107,7 +100,7 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
107100
OpenIdConnectConfiguration config = null;
108101
try
109102
{
110-
config = await _configManager.GetConfigurationAsync(cancellationToken).ConfigureAwait(false);
103+
config = await GetConfigurationManager().GetConfigurationAsync(cancellationToken).ConfigureAwait(false);
111104
}
112105
catch (Exception ex)
113106
{
@@ -170,11 +163,11 @@ protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage
170163
if (HttpContext.Current != null)
171164
HttpContext.Current.User = claimsPrincipal;
172165

173-
// If the token is scoped, verify that required permission is set in the scope claim. This could be done later at the controller level as well
174-
if (ClaimsPrincipal.Current.FindFirst(ClaimConstants.ScopeClaimType).Value != ClaimConstants.ScopeClaimValue)
175-
return BuildResponseErrorMessage(HttpStatusCode.Forbidden);
176-
177-
return await base.SendAsync(request, cancellationToken);
166+
// If the token is scoped, verify that required permission is set in the scope claim.
167+
// This could be done later at the controller level as well
168+
return ClaimsPrincipal.Current.FindFirst(ClaimConstants.ScopeClaimType).Value != ClaimConstants.ScopeClaimValue
169+
? BuildResponseErrorMessage(HttpStatusCode.Forbidden)
170+
: await base.SendAsync(request, cancellationToken);
178171
}
179172
catch (SecurityTokenValidationException stex)
180173
{
@@ -199,9 +192,17 @@ private HttpResponseMessage BuildResponseErrorMessage(HttpStatusCode statusCode,
199192
var response = new HttpResponseMessage(statusCode);
200193

201194
// The Scheme should be "Bearer", authorization_uri should point to the tenant url and resource_id should point to the audience.
202-
var authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + _authority + "\"" + "," + "resource_id=" + _audience + $",error_description={error_description}");
203-
response.Headers.WwwAuthenticate.Add(authenticateHeader);
195+
response.Headers.WwwAuthenticate.Add(
196+
new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + _authority + "\"" + "," + "resource_id=" + _audience + $",error_description={error_description}"));
204197
return response;
205198
}
199+
200+
/// <summary>
201+
/// The ConfigurationManager class holds properties to control the metadata refresh interval.
202+
/// For more details, https://docs.microsoft.com/en-us/dotnet/api/microsoft.identitymodel.protocols.configurationmanager-1?view=azure-dotnet
203+
/// </summary>
204+
/// <returns></returns>
205+
private ConfigurationManager<OpenIdConnectConfiguration> GetConfigurationManager() =>
206+
new ConfigurationManager<OpenIdConnectConfiguration>($"{_authority}/.well-known/openid-configuration", new OpenIdConnectConfigurationRetriever());
206207
}
207208
}

0 commit comments

Comments
 (0)