Skip to content

Instantly share code, notes, and snippets.

@PawelHaracz
Last active June 5, 2019 10:11
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save PawelHaracz/9177b1b1ac7e45d3713eb108aeb07e37 to your computer and use it in GitHub Desktop.
Save PawelHaracz/9177b1b1ac7e45d3713eb108aeb07e37 to your computer and use it in GitHub Desktop.
the validator checks whitelist and decodes token and compare this with appId claim. An AsyncHelper invoke asynchronous method synchronously. A startup class is a normal .net core startup class
// Copyright (c) Microsoft Corporation, Inc. All rights reserved.
// Licensed under the MIT License, Version 2.0. See License.txt in the project root for license information.
/// <summary>
/// Helper created by microsoft
/// https://github.com/aspnet/AspNetIdentity/blob/master/src/Microsoft.AspNet.Identity.Core/AsyncHelper.cs
/// </summary>
public class AsyncHelper
{
private static readonly TaskFactory _myTaskFactory = new TaskFactory(CancellationToken.None,
TaskCreationOptions.None, TaskContinuationOptions.None, TaskScheduler.Default);
public static TResult RunSync<TResult>(Func<Task<TResult>> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
return _myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
public static void RunSync(Func<Task> func)
{
var cultureUi = CultureInfo.CurrentUICulture;
var culture = CultureInfo.CurrentCulture;
_myTaskFactory.StartNew(() =>
{
Thread.CurrentThread.CurrentCulture = culture;
Thread.CurrentThread.CurrentUICulture = cultureUi;
return func();
}).Unwrap().GetAwaiter().GetResult();
}
}
public class Startup
{
private readonly IConfiguration _configuration;
public Startup(IConfiguration configuration)
{
_configuration = configuration;
}
/// <summary>
/// whitelist variable gets a section called Whitelist and takes all values
/// AzureAd:Instance common is a https://login.microsoftonline.com/
/// AzureAd:TenantId is tenant Id or tenant name, or if it is a public azure ad application that can be common too. more details here : https://docs.microsoft.com/en-us/azure/active-directory/develop/howto-convert-app-to-be-multi-tenant
/// </summary>
public void ConfigureServices(IServiceCollection services)
{
services.AddMvc().SetCompatibilityVersion(CompatibilityVersion.Version_2_2);
services.AddAuthentication(cfg =>
{
cfg.DefaultAuthenticateScheme = JwtBearerDefaults.AuthenticationScheme;
cfg.DefaultChallengeScheme = JwtBearerDefaults.AuthenticationScheme;
})
.AddJwtBearer(options =>
{
_configuration.Bind("AzureAd", options);
options.TokenValidationParameters = new TokenValidationParameters()
{
ValidateIssuerSigningKey = true,
ValidateIssuer = false
};
//
var whiteList = _configuration.
GetSection("Whitelist")
.AsEnumerable()
.Where(s => string.IsNullOrEmpty(s.Value) == false)
.Select(s =>
{
if (Guid.TryParse(s.Value, out var g))
{
return g;
}
return Guid.Empty;
})
.Where(g => g != Guid.Empty)
.ToArray();
options.SecurityTokenValidators.Add(new WhitelistAppTokenValidation(_configuration["AzureAd:Instance"], _configuration["AzureAd:TenantId"], whiteList));
options.Validate();
});
}
public void Configure(IApplicationBuilder app, IHostingEnvironment env)
{
app.UseAuthentication();
app.UseMvc();
}
}
public class WhitelistAppTokenValidation : ISecurityTokenValidator
{
private readonly string _tenatId;
private readonly string _authority;
private readonly Guid[] _whitelist;
public WhitelistAppTokenValidation(string authority, string tenatId, params Guid[] whiteList)
{
_tenatId = string.IsNullOrWhiteSpace(tenatId) == false ? tenatId : throw new ArgumentException(nameof(tenatId));
_authority = string.IsNullOrWhiteSpace(authority) == false ? authority : throw new ArgumentException(nameof(authority));
_whitelist = whiteList != null && whiteList.Any() ? whiteList : throw new ArgumentException(nameof(ArgumentException));
}
public bool CanReadToken(string securityToken) => true;
public ClaimsPrincipal ValidateToken(string securityToken, TokenValidationParameters validationParameters, out SecurityToken validatedToken)
{
string stsDiscoveryEndpoint = $"{_authority}/{_tenatId}/v2.0/.well-known/openid-configuration";
var configManager = new ConfigurationManager<OpenIdConnectConfiguration>(stsDiscoveryEndpoint, new OpenIdConnectConfigurationRetriever());
var config = AsyncHelper.RunSync(configManager.GetConfigurationAsync);
validationParameters.IssuerSigningKeys = config.SigningKeys;
var validationIssuerOldValue = validationParameters.ValidateIssuer;
var validateAudiencedOldValue = validationParameters.ValidateAudience;
validationParameters.ValidateIssuer = false;
validationParameters.ValidateAudience = false;
var tokenHandler = new JwtSecurityTokenHandler();
var result = tokenHandler.ValidateToken(securityToken, validationParameters, out validatedToken);
var claimValue = result.Claims.GetEnumerator();
var appId = Guid.Empty;
if (claimValue != null)
{
while (claimValue.MoveNext())
{
if (claimValue.Current.Type == "appid")
{
var aId = claimValue.Current.Value;
if (Guid.TryParse(aId, out appId) == false)
{
throw new InvalidCastException($"Cannot cast string {aId} to guid");
}
break;
}
}
}
if (appId == Guid.Empty)
{
throw new UnauthorizedException($"Token doesn't have application Id");
}
if (_whitelist.Contains(appId) == false)
{
throw new UnauthorizedException($"An application Id {appId} isn't being in whitelist");
}
validationParameters.ValidateIssuer = validationIssuerOldValue;
validationParameters.ValidateAudience = validateAudiencedOldValue;
return result;
}
public bool CanValidateToken { get; } = true;
public int MaximumTokenSizeInBytes { get; set; }
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment