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),
public class JsonWebTokenHttpModule : IHttpModule { private static readonly Lazy<string> _securityKey = new Lazy<string>(() => GetSecurityKey()); private static string _authority = ConfigurationManager.AppSettings["Authority"]; public void Init(HttpApplication context) { context.AuthenticateRequest += new EventHandler(JsonWebTokenHandler); } private void JsonWebTokenHandler(Object source, EventArgs e) { var context = HttpContext.Current; var request = context.Request; var authorizationHeader = request.Headers["Authorization"]; if (string.IsNullOrWhiteSpace(authorizationHeader) || !authorizationHeader.StartsWith("Bearer ")) { return; } var token = authorizationHeader.Substring(7); try { ValidateTokenAndSetIdentity(token); } catch (SecurityTokenValidationException ex) { // log error here } catch { // log error here } } private void ValidateTokenAndSetIdentity(string token) { var tokenHandler = new JwtSecurityTokenHandler(); var validationParameters = GetValidationParameters(); SecurityToken validToken; var principal = tokenHandler.ValidateToken(token, validationParameters, out validToken); Thread.CurrentPrincipal = principal; HttpContext.Current.User = principal; } private static TokenValidationParameters GetValidationParameters() { var bytes = Convert.FromBase64String(_securityKey.Value); var token = new X509SecurityToken(new X509Certificate2(bytes)); return new TokenValidationParameters { ValidAudience = _authority + "/resources", ValidIssuer = _authority, IssuerSigningKeyResolver = (arbitrarily, declaring, these, parameters) => { return token.SecurityKeys.First(); }, IssuerSigningToken = token }; } private static string GetSecurityKey() { var webClient = new WebClient(); var endpoint = _authority + "/.well-known/openid-configuration"; var json = webClient.DownloadString(endpoint); dynamic metadata = JsonConvert.DeserializeObject<dynamic>(json); var jwksUri = metadata.jwks_uri.Value; json = webClient.DownloadString(jwksUri); var key = JsonConvert.DeserializeObject<dynamic>(json).keys[0]; return (string)key.x5c[0]; } public void Dispose() { } }
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,
<system.webServer> <modules> <add name="JsonWebTokenHttpModule" type="AspNetWebFormsWithIdentityServer3.JsonWebTokenHttpModule" /> </modules> </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.