ASP.NET Web Forms and IdentityServer3
Introduction:
We have lot of applications that uses awesome IdentityServer3 as our identity provider(or sso server). We also have some applications that we built using the legacy ASP.NET Web Forms. In our ASP.NET Web Forms, we have lot of web.config authorization checks. Our Identity-Server issues self-contained JWT tokens for authenticated users. Some of our mobile application clients get the token from identity server and send the token to our Web Forms application. In our Web Forms application, we just need to validate the JWT token. Fortunately, the implemenation was very simple. In this article, I will show you how to validate the self-contained JWT tokens in Web Forms and set the thread/request identity of authenticated users on each request.
Description:
HttpApplication.AuthenticateRequest is the place where both IIS and ASP.NET participate in authenticating the request. In this place, we will hook our authentication logic and set request identity of authenticated users. We can add this event in global.asax.cs file or hook this event in a custom HttpModule. I will go with HttpModule. We need to, 1) Add a refernce of System.IdentityModel.dll assembly. 2) Add System.IdentityModel.Tokens.Jwt and Newtonsoft.Json nuget packages. Let add a JsonWebTokenHttpModule class(note that I am expecting authorization-header is set in request to Web Forms but the same trick will work for cookies as well. You can also call FormsAuthentication.RedirectFromLoginPage or FormsAuthentication.SetAuthCookie after validating the token. Cookie v/s Token),
01 |
public
class
JsonWebTokenHttpModule : IHttpModule
|
02 |
{
|
03 |
private
static
readonly
Lazy<string> _securityKey = new
Lazy<string>(() => GetSecurityKey());
|
04 |
05 |
private
static
string
_authority =
ConfigurationManager.AppSettings["Authority"];
|
06 |
07 |
public
void
Init(HttpApplication context)
|
08 |
{
|
09 |
context.AuthenticateRequest += new
EventHandler(JsonWebTokenHandler);
|
10 |
}
|
11 |
12 |
private
void
JsonWebTokenHandler(Object source, EventArgs
e)
|
13 |
{
|
14 |
var context = HttpContext.Current;
|
15 |
var request = context.Request;
|
16 |
var authorizationHeader =
request.Headers["Authorization"];
|
17 |
if
(string.IsNullOrWhiteSpace(authorizationHeader) ||
!authorizationHeader.StartsWith("Bearer "))
|
18 |
{
|
19 |
return;
|
20 |
}
|
21 |
var token =
authorizationHeader.Substring(7);
|
22 |
try
|
23 |
{
|
24 |
ValidateTokenAndSetIdentity(token);
|
25 |
}
|
26 |
catch
(SecurityTokenValidationException ex)
|
27 |
{
|
28 |
// log error here
|
29 |
}
|
30 |
catch
|
31 |
{
|
32 |
// log error here
|
33 |
}
|
34 |
}
|
35 |
36 |
private
void
ValidateTokenAndSetIdentity(string
token)
|
37 |
{
|
38 |
var tokenHandler = new
JwtSecurityTokenHandler();
|
39 |
var validationParameters =
GetValidationParameters();
|
40 |
SecurityToken validToken;
|
41 |
var principal =
tokenHandler.ValidateToken(token,
validationParameters, out
validToken);
|
42 |
Thread.CurrentPrincipal = principal;
|
43 |
HttpContext.Current.User = principal;
|
44 |
}
|
45 |
46 |
private
static
TokenValidationParameters
GetValidationParameters()
|
47 |
{
|
48 |
var bytes =
Convert.FromBase64String(_securityKey.Value);
|
49 |
var token = new
X509SecurityToken(new
X509Certificate2(bytes));
|
50 |
return
new
TokenValidationParameters
|
51 |
{
|
52 |
ValidAudience = _authority + "/resources",
|
53 |
ValidIssuer = _authority,
|
54 |
IssuerSigningKeyResolver = (arbitrarily,
declaring, these, parameters) => { return
token.SecurityKeys.First(); },
|
55 |
IssuerSigningToken = token
|
56 |
};
|
57 |
}
|
58 |
59 |
private
static
string
GetSecurityKey()
|
60 |
{
|
61 |
var webClient = new
WebClient();
|
62 |
var endpoint = _authority + "/.well-known/openid-configuration";
|
63 |
var json =
webClient.DownloadString(endpoint);
|
64 |
dynamic metadata =
JsonConvert.DeserializeObject<dynamic>(json);
|
65 |
var jwksUri = metadata.jwks_uri.Value;
|
66 |
json =
webClient.DownloadString(jwksUri);
|
67 |
var key =
JsonConvert.DeserializeObject<dynamic>(json).keys[0];
|
68 |
return
(string)key.x5c[0];
|
69 |
}
|
70 |
71 |
public
void
Dispose()
|
72 |
{
|
73 |
|
74 |
}
|
75 |
}
|
The above HttpModule class will work with any open-id connect provider that can issue self-contained JWT including IdentityServer3. I have set Authority in web.config. We get the key from open-id provider configuration information url. Then we will use this key to validate the token on every request. We used Microsoft System.IdentityModel.Tokens.Jwt package to validate the JWT token. Finally, we just need to register this HttpModule in web.config,
1 |
<system.webServer>
|
2 |
<modules>
|
3 |
<add
name="JsonWebTokenHttpModule"
type="AspNetWebFormsWithIdentityServer3.JsonWebTokenHttpModule"
/>
|
4 |
</modules>
|
5 |
</system.webServer>
|
After adding and registering the above HttpModule, all of our web.config authorization works as expected. In some applications we used Microsoft.Owin.Security.OpenIdConnect and IdentityServer3.AccessTokenValidation for validating access token.
Summary:
In this article, I showed you how easily we validate self-contained Json Web Tokens and set the user identity in ASP.NET Web Forms. You can find the source at github.