Skip to content

Commit a8b35ac

Browse files
committed
Update 2018 to add and check scopes
1 parent f28594f commit a8b35ac

9 files changed

Lines changed: 89 additions & 99 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

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
}

TodoListService/Controllers/TodoListController.cs

Lines changed: 30 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -36,14 +36,40 @@ public class TodoListController : ApiController
3636
//
3737
static ConcurrentBag<TodoItem> todoBag = new ConcurrentBag<TodoItem>();
3838

39+
private ClaimsIdentity userClaims;
40+
41+
public TodoListController()
42+
{
43+
userClaims = User.Identity as ClaimsIdentity;
44+
}
45+
46+
/// <summary>
47+
/// Assure the presence of a scope claim containing a specific scope (i.e. access_as_user)
48+
/// </summary>
49+
/// <param name="scopeName">The name of the scope</param>
50+
private void CheckAccessTokenScope(string scopeName)
51+
{
52+
// Make sure access_as_user scope is present
53+
string scopeClaimValue = userClaims.FindFirst("http://schemas.microsoft.com/identity/claims/scope")?.Value;
54+
if (!string.Equals(scopeClaimValue, scopeName, StringComparison.InvariantCultureIgnoreCase))
55+
{
56+
throw new HttpResponseException(new HttpResponseMessage(HttpStatusCode.Forbidden)
57+
{
58+
ReasonPhrase = @"Please request an access token to scope '{scopeName}'"
59+
});
60+
}
61+
}
62+
3963
// GET api/todolist
4064
public IEnumerable<TodoItem> Get()
4165
{
66+
CheckAccessTokenScope("access_as_user");
67+
4268
// You can use the ClaimsPrincipal to access information about the
4369
// user making the call. In this case, we use the 'sub' or
4470
// NameIdentifier claim to serve as a key for the tasks in the data store.
45-
// the NameIdentififier claim contains an immutable, unique identifier for the use
46-
71+
// the NameIdentififier claim contains an immutable, unique identifier for the use
72+
4773
Claim subject = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier);
4874

4975
return from todo in todoBag
@@ -54,6 +80,8 @@ public IEnumerable<TodoItem> Get()
5480
// POST api/todolist
5581
public void Post(TodoItem todo)
5682
{
83+
CheckAccessTokenScope("access_as_user");
84+
5785
if (null != todo && !string.IsNullOrWhiteSpace(todo.Title))
5886
{
5987
todoBag.Add(new TodoItem { Title = todo.Title, Owner = ClaimsPrincipal.Current.FindFirst(ClaimTypes.NameIdentifier).Value });

TodoListService/Controllers/ValuesController.cs

Lines changed: 0 additions & 40 deletions
This file was deleted.

TodoListService/TodoListService.csproj

Lines changed: 1 addition & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -143,7 +143,7 @@
143143
<ItemGroup>
144144
<Compile Include="App_Start\BundleConfig.cs" />
145145
<Compile Include="App_Start\FilterConfig.cs" />
146-
<Compile Include="App_Start\OpenIdConnectCachingSecurityTokenProvider.cs" />
146+
<Compile Include="App_Start\OpenIdConnectSecurityTokenProvider.cs" />
147147
<Compile Include="App_Start\RouteConfig.cs" />
148148
<Compile Include="App_Start\Startup.Auth.cs" />
149149
<Compile Include="App_Start\WebApiConfig.cs" />
@@ -177,7 +177,6 @@
177177
<Compile Include="Areas\HelpPage\XmlDocumentationProvider.cs" />
178178
<Compile Include="Controllers\HomeController.cs" />
179179
<Compile Include="Controllers\TodoListController.cs" />
180-
<Compile Include="Controllers\ValuesController.cs" />
181180
<Compile Include="Global.asax.cs">
182181
<DependentUpon>Global.asax</DependentUpon>
183182
</Compile>

TodoListService/Web.config

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@
99
<add key="webpages:Enabled" value="false" />
1010
<add key="ClientValidationEnabled" value="true" />
1111
<add key="UnobtrusiveJavaScriptEnabled" value="true" />
12-
<add key="ida:Audience" value="{Enter the Application Id that you copied from the App Registration Portal.}" />
12+
<add key="ida:ClientId" value="{Enter the Application Id that you copied from the App Registration Portal.}" />
1313
</appSettings>
1414
<system.web>
1515
<compilation debug="true" targetFramework="4.5" />

0 commit comments

Comments
 (0)