-
Notifications
You must be signed in to change notification settings - Fork 65
/
Global.asax.cs
186 lines (163 loc) · 8.2 KB
/
Global.asax.cs
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
//----------------------------------------------------------------------------------------------
// Copyright 2014 Microsoft Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//----------------------------------------------------------------------------------------------
using System;
using System.Collections.Generic;
using System.Linq;
using System.Web;
using System.Web.Http;
using System.Web.Mvc;
using System.Web.Optimization;
using System.Web.Routing;
// The following using statements were added for this sample.
using System.Net.Http;
using System.IdentityModel.Tokens;
using System.Threading.Tasks;
using System.Threading;
using System.Net;
using System.IdentityModel.Selectors;
using System.Security.Claims;
using System.Net.Http.Headers;
using System.IdentityModel.Metadata;
using System.ServiceModel.Security;
using System.Xml;
using System.Security.Cryptography.X509Certificates;
using System.Globalization;
using System.Configuration;
using Microsoft.IdentityModel.Protocols;
namespace TodoListService_ManualJwt
{
public class WebApiApplication : System.Web.HttpApplication
{
protected void Application_Start()
{
AreaRegistration.RegisterAllAreas();
GlobalConfiguration.Configure(WebApiConfig.Register);
FilterConfig.RegisterGlobalFilters(GlobalFilters.Filters);
RouteConfig.RegisterRoutes(RouteTable.Routes);
BundleConfig.RegisterBundles(BundleTable.Bundles);
GlobalConfiguration.Configuration.MessageHandlers.Add(new TokenValidationHandler());
}
}
internal class TokenValidationHandler : DelegatingHandler
{
//
// The AAD Instance is the instance of Azure, for example public Azure or Azure China.
// The Tenant is the name of the tenant in which this application is registered.
// The Authority is the sign-in URL of the tenant.
// The Audience is the value the service expects to see in tokens that are addressed to it.
//
static string aadInstance = ConfigurationManager.AppSettings["ida:AADInstance"];
static string tenant = ConfigurationManager.AppSettings["ida:Tenant"];
static string audience = ConfigurationManager.AppSettings["ida:Audience"];
static string clientId = ConfigurationManager.AppSettings["ida:ClientId"];
string authority = String.Format(CultureInfo.InvariantCulture, aadInstance, tenant);
static string _issuer = string.Empty;
static List<SecurityToken> _signingTokens = null;
static DateTime _stsMetadataRetrievalTime = DateTime.MinValue;
static string scopeClaimType = "http://schemas.microsoft.com/identity/claims/scope";
//
// SendAsync checks that incoming requests have a valid access token, and sets the current user identity using that access token.
//
protected async override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
// Get the jwt bearer token from the authorization header
string jwtToken = null;
AuthenticationHeaderValue authHeader = request.Headers.Authorization;
if (authHeader != null)
{
jwtToken = authHeader.Parameter;
}
if (jwtToken == null)
{
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
return response;
}
string issuer;
List<SecurityToken> signingTokens;
try
{
// The issuer and signingTokens are cached for 24 hours. They are updated if any of the conditions in the if condition is true.
if (DateTime.UtcNow.Subtract(_stsMetadataRetrievalTime).TotalHours > 24
|| string.IsNullOrEmpty(_issuer)
|| _signingTokens == null)
{
// Get tenant information that's used to validate incoming jwt tokens
string stsDiscoveryEndpoint = string.Format("{0}/.well-known/openid-configuration", authority);
ConfigurationManager<OpenIdConnectConfiguration> configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint);
OpenIdConnectConfiguration config = await configManager.GetConfigurationAsync();
_issuer = config.Issuer;
_signingTokens = config.SigningTokens.ToList();
_stsMetadataRetrievalTime = DateTime.UtcNow;
}
issuer = _issuer;
signingTokens = _signingTokens;
}
catch (Exception)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
JwtSecurityTokenHandler tokenHandler = new JwtSecurityTokenHandler();
TokenValidationParameters validationParameters = new TokenValidationParameters
{
// We accept both the App Id URI and the AppId of this service application
ValidAudiences = new[] { audience, clientId },
// Supports both the Azure AD V1 and V2 endpoint
ValidIssuers = new [] { issuer, $"{issuer}/v2.0" },
IssuerSigningTokens = signingTokens,
CertificateValidator = X509CertificateValidator.None // Certificate validation does not make sense since AAD's metadata document is signed with a self-signed certificate.
};
try
{
// Validate token.
SecurityToken validatedToken = new JwtSecurityToken();
ClaimsPrincipal claimsPrincipal = tokenHandler.ValidateToken(jwtToken, validationParameters, out validatedToken);
// Set the ClaimsPrincipal on the current thread.
Thread.CurrentPrincipal = claimsPrincipal;
// Set the ClaimsPrincipal on HttpContext.Current if the app is running in web hosted environment.
if (HttpContext.Current != null)
{
HttpContext.Current.User = claimsPrincipal;
}
// If the token is scoped, verify that required permission is set in the scope claim.
if (ClaimsPrincipal.Current.FindFirst(scopeClaimType) != null && ClaimsPrincipal.Current.FindFirst(scopeClaimType).Value != "user_impersonation")
{
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Forbidden);
return response;
}
return await base.SendAsync(request, cancellationToken);
}
catch (SecurityTokenValidationException)
{
HttpResponseMessage response = BuildResponseErrorMessage(HttpStatusCode.Unauthorized);
return response;
}
catch (Exception)
{
return new HttpResponseMessage(HttpStatusCode.InternalServerError);
}
}
private HttpResponseMessage BuildResponseErrorMessage(HttpStatusCode statusCode)
{
HttpResponseMessage response = new HttpResponseMessage(statusCode);
//
// The Scheme should be "Bearer", authorization_uri should point to the tenant url and resource_id should point to the audience.
//
AuthenticationHeaderValue authenticateHeader = new AuthenticationHeaderValue("Bearer", "authorization_uri=\"" + authority + "\"" + "," + "resource_id=" + audience);
response.Headers.WwwAuthenticate.Add(authenticateHeader);
return response;
}
}
}