Last active
June 5, 2019 10:11
-
-
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
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// 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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); | |
} | |
} |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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