Skip to content

Instantly share code, notes, and snippets.

@michaelsteuer
Forked from kbajpai/CustomJwtHandler.cs
Created November 1, 2024 06:20
Show Gist options
  • Save michaelsteuer/090ecc8890e4e29149966ff3e0d00833 to your computer and use it in GitHub Desktop.
Save michaelsteuer/090ecc8890e4e29149966ff3e0d00833 to your computer and use it in GitHub Desktop.
Google JWT Token Validation
using System;
using System.Collections.Generic;
using System.IdentityModel.Tokens;
using System.Linq;
using System.Net.Http;
using System.Web;
using System.Web.Configuration;
using Newtonsoft.Json;
using System.Net;
using System.Threading.Tasks;
using System.Threading;
using Services.Models;
using System.Security.Claims;
namespace Services
{
/// <summary>
/// This is an implementation of Google JWT verification that
/// demonstrates:
/// - JWT validation
/// </summary>
/// @author [email protected] (Kunal Bajpai)
public class CustomJwtHandler : DelegatingHandler
{
private const string URL_GOOGLE_TOKEN_INFO = "https://www.googleapis.com/oauth2/v3/tokeninfo";
/// <summary>
///
/// </summary>
/// <param name="request"></param>
/// <param name="cancellationToken"></param>
/// <returns></returns>
protected override Task<HttpResponseMessage> SendAsync(HttpRequestMessage request, CancellationToken cancellationToken)
{
HttpStatusCode statusCode;
string token;
var authHeader = request.Headers.Authorization;
if (authHeader == null)
{
// Missing authorization header
return base.SendAsync(request, cancellationToken);
}
if (!TryRetrieveToken(request, out token))
{
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(HttpStatusCode.Unauthorized));
}
try
{
ValidateToken(token);
return base.SendAsync(request, cancellationToken);
}
catch (SecurityTokenInvalidAudienceException)
{
statusCode = HttpStatusCode.Unauthorized;
}
catch (SecurityTokenValidationException)
{
statusCode = HttpStatusCode.Unauthorized;
}
catch (Exception)
{
statusCode = HttpStatusCode.InternalServerError;
}
return Task<HttpResponseMessage>.Factory.StartNew(() => new HttpResponseMessage(statusCode));
}
/// <summary>
/// Validates JWT Token
/// </summary>
/// <param name="JwtToken"></param>
private void ValidateToken(string JwtToken)
{
try
{
using (WebClient wc = new WebClient())
{
TokenInfo tokenInfo = JsonConvert.DeserializeObject<TokenInfo>(wc.DownloadString(URL_GOOGLE_TOKEN_INFO + "?id_token=" + JwtToken));
ClaimsPrincipal claimsPrincipal = new ClaimsPrincipal(new ClaimsIdentity(ExtractClaims(tokenInfo), tokenInfo.Issuer));
Thread.CurrentPrincipal = claimsPrincipal;
HttpContext.Current.User = claimsPrincipal;
}
}
catch (WebException e)
{
HttpStatusCode statusCode = ((HttpWebResponse)e.Response).StatusCode;
if (statusCode == HttpStatusCode.BadRequest)
{
throw new SecurityTokenValidationException();
}
else
{
throw new Exception();
}
}
}
/// <summary>
/// Tries to retrieve Token
/// </summary>
/// <param name="request"></param>
/// <param name="token"></param>
/// <returns></returns>
private static bool TryRetrieveToken(HttpRequestMessage request, out string token)
{
token = null;
IEnumerable<string> authorizationHeaders;
if (!request.Headers.TryGetValues("Authorization", out authorizationHeaders) ||
authorizationHeaders.Count() > 1)
{
return false;
}
var bearerToken = authorizationHeaders.ElementAt(0);
token = bearerToken.StartsWith("Bearer ") ? bearerToken.Substring(7) : bearerToken;
return true;
}
private List<Claim> ExtractClaims(TokenInfo tokenInfo)
{
List<Claim> claims = new List<Claim> {
new Claim(ClaimTypes.Name, tokenInfo.Name),
new Claim(ClaimTypes.Email, tokenInfo.Email),
new Claim(ClaimTypes.GivenName, tokenInfo.GivenName),
new Claim(ClaimTypes.Surname, tokenInfo.FamilyName),
new Claim(ApplicationUser.CLAIM_TYPE_LOCALE, tokenInfo.Locale),
new Claim(ClaimTypes.NameIdentifier, tokenInfo.ProviderKey, ClaimValueTypes.String, tokenInfo.Issuer),
new Claim(ApplicationUser.CLAIM_TYPE_EMAIL_CONFIRMED, tokenInfo.IsEmailVerifed.ToString(), ClaimValueTypes.Boolean)
};
return claims;
}
}
}
using Microsoft.AspNet.Identity.EntityFramework;
using Newtonsoft.Json;
namespace Services.Models
{
public class TokenInfo
{
[JsonProperty("iss")]
public string Issuer { get; set; }
[JsonProperty("aud")]
public string AudienceClientId { get; set; }
[JsonProperty("sub")]
public string ProviderKey { get; set; }
[JsonProperty("email_verified")]
public bool IsEmailVerifed { get; set; }
[JsonProperty("azp")]
public string AndroidClientId { get; set; }
[JsonProperty("email")]
public string Email { get; set; }
[JsonProperty("iat")]
public long IssuedAt { get; set; }
[JsonProperty("exp")]
public long ExpiresAt { get; set; }
[JsonProperty("name")]
public string Name { get; set; }
[JsonProperty("picture")]
public string Picture { get; set; }
[JsonProperty("given_name")]
public string GivenName { get; set; }
[JsonProperty("family_name")]
public string FamilyName { get; set; }
[JsonProperty("locale")]
public string Locale { get; set; }
[JsonProperty("alg")]
public string Algorithm { get; set; }
[JsonProperty("kid")]
public string kid { get; set; }
public override bool Equals(object obj)
{
if (obj.GetType() != typeof(ApplicationUser))
{
return false;
}
ApplicationUser user = (ApplicationUser)obj;
bool hasLogin = false;
foreach (IdentityUserLogin login in user.Logins)
{
if (login.ProviderKey == ProviderKey)
{
hasLogin = true;
break;
}
}
if (!hasLogin) { return false; }
if (user.FirstName != GivenName) { return false; }
if (user.LastName != FamilyName) { return false; }
if (user.Locale != Locale) { return false; }
return base.Equals(obj);
}
}
}
using System;
using System.Collections.Generic;
using System.Linq;
using System.Net.Http;
using System.Web.Http;
using Microsoft.Owin.Security.OAuth;
using Newtonsoft.Json.Serialization;
namespace Services
{
public static class WebApiConfig
{
public static void Register(HttpConfiguration config)
{
// Web API configuration and services
// Configure Web API to use only bearer token authentication.
config.SuppressDefaultHostAuthentication();
config.Filters.Add(new HostAuthenticationFilter(OAuthDefaults.AuthenticationType));
// Web API routes
config.MapHttpAttributeRoutes();
config.Routes.MapHttpRoute(
name: "DefaultApi",
routeTemplate: "api/{controller}/{id}",
defaults: new { id = RouteParameter.Optional }
);
config.MessageHandlers.Add(new CustomJwtHandler());
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment