Add project files.

This commit is contained in:
David Lebee 2020-07-23 12:50:46 -04:00
parent 09e99a3f96
commit d06df142e8
6 changed files with 282 additions and 0 deletions

View File

@ -0,0 +1,25 @@

Microsoft Visual Studio Solution File, Format Version 12.00
# Visual Studio Version 16
VisualStudioVersion = 16.0.29503.13
MinimumVisualStudioVersion = 10.0.40219.1
Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "PoweredSoft.DynamicJwtBearer", "PoweredSoft.DynamicJwtBearer\PoweredSoft.DynamicJwtBearer.csproj", "{0A15F002-66C4-44D1-8162-563F860C49E4}"
EndProject
Global
GlobalSection(SolutionConfigurationPlatforms) = preSolution
Debug|Any CPU = Debug|Any CPU
Release|Any CPU = Release|Any CPU
EndGlobalSection
GlobalSection(ProjectConfigurationPlatforms) = postSolution
{0A15F002-66C4-44D1-8162-563F860C49E4}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{0A15F002-66C4-44D1-8162-563F860C49E4}.Debug|Any CPU.Build.0 = Debug|Any CPU
{0A15F002-66C4-44D1-8162-563F860C49E4}.Release|Any CPU.ActiveCfg = Release|Any CPU
{0A15F002-66C4-44D1-8162-563F860C49E4}.Release|Any CPU.Build.0 = Release|Any CPU
EndGlobalSection
GlobalSection(SolutionProperties) = preSolution
HideSolutionNode = FALSE
EndGlobalSection
GlobalSection(ExtensibilityGlobals) = postSolution
SolutionGuid = {9D366086-7F19-4F1E-A5B5-DAEF1EA56D90}
EndGlobalSection
EndGlobal

View File

@ -0,0 +1,22 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.DependencyInjection;
using Microsoft.Extensions.DependencyInjection.Extensions;
using Microsoft.Extensions.Options;
using System;
namespace PoweredSoft.DynamicJwtBearer
{
public static class DynamicJwtBearerExtensions
{
public static AuthenticationBuilder AddDynamicJwtBearer(this AuthenticationBuilder builder, string authenticationScheme, Action<JwtBearerOptions> action = null)
{
builder.Services.TryAddEnumerable(ServiceDescriptor.Singleton<IPostConfigureOptions<JwtBearerOptions>, JwtBearerPostConfigureOptions>());
if (action != null)
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, action);
return builder.AddScheme<JwtBearerOptions, DynamicJwtBearerHandler>(authenticationScheme, null, _ => { });
}
}
}

View File

@ -0,0 +1,174 @@
using Microsoft.AspNetCore.Authentication;
using Microsoft.AspNetCore.Authentication.JwtBearer;
using Microsoft.Extensions.Logging;
using Microsoft.Extensions.Options;
using Microsoft.IdentityModel.Tokens;
using Microsoft.Net.Http.Headers;
using System;
using System.Collections.Generic;
using System.Linq;
using System.Security.Claims;
using System.Text.Encodings.Web;
using System.Threading.Tasks;
namespace PoweredSoft.DynamicJwtBearer
{
public class DynamicJwtBearerHandler : JwtBearerHandler
{
private readonly IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver;
public DynamicJwtBearerHandler(IOptionsMonitor<JwtBearerOptions> options, ILoggerFactory logger, UrlEncoder encoder, ISystemClock clock, IDynamicJwtBearerHanderConfigurationResolver dynamicJwtBearerHanderConfigurationResolver) : base(options, logger, encoder, clock)
{
this.dynamicJwtBearerHanderConfigurationResolver = dynamicJwtBearerHanderConfigurationResolver;
}
/// <summary>
/// Searches the 'Authorization' header for a 'Bearer' token. If the 'Bearer' token is found, it is validated using <see cref="TokenValidationParameters"/> set in the options.
/// </summary>
/// <returns></returns>
protected override async Task<AuthenticateResult> HandleAuthenticateAsync()
{
string token = null;
try
{
// Give application opportunity to find from a different location, adjust, or reject token
var messageReceivedContext = new MessageReceivedContext(Context, Scheme, Options);
// event can set the token
await Events.MessageReceived(messageReceivedContext);
if (messageReceivedContext.Result != null)
{
return messageReceivedContext.Result;
}
// If application retrieved token from somewhere else, use that.
token = messageReceivedContext.Token;
if (string.IsNullOrEmpty(token))
{
string authorization = Request.Headers[HeaderNames.Authorization];
// If no authorization header found, nothing to process further
if (string.IsNullOrEmpty(authorization))
{
return AuthenticateResult.NoResult();
}
if (authorization.StartsWith("Bearer ", StringComparison.OrdinalIgnoreCase))
{
token = authorization.Substring("Bearer ".Length).Trim();
}
// If no token found, no further work possible
if (string.IsNullOrEmpty(token))
{
return AuthenticateResult.NoResult();
}
}
var currentConfiguration = await this.dynamicJwtBearerHanderConfigurationResolver.ResolveCurrentOpenIdConfiguration(Context);
var validationParameters = Options.TokenValidationParameters.Clone();
if (currentConfiguration != null)
{
var issuers = new[] { currentConfiguration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(currentConfiguration.SigningKeys)
?? currentConfiguration.SigningKeys;
}
List<Exception> validationFailures = null;
SecurityToken validatedToken;
foreach (var validator in Options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
Logger.TokenValidationFailed(ex);
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
if (Options.RefreshOnIssuerKeyNotFound && Options.ConfigurationManager != null
&& ex is SecurityTokenSignatureKeyNotFoundException)
{
Options.ConfigurationManager.RequestRefresh();
}
if (validationFailures == null)
{
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}
Logger.TokenValidationSucceeded();
var tokenValidatedContext = new TokenValidatedContext(Context, Scheme, Options)
{
Principal = principal,
SecurityToken = validatedToken
};
await Events.TokenValidated(tokenValidatedContext);
if (tokenValidatedContext.Result != null)
{
return tokenValidatedContext.Result;
}
if (Options.SaveToken)
{
tokenValidatedContext.Properties.StoreTokens(new[]
{
new AuthenticationToken { Name = "access_token", Value = token }
});
}
tokenValidatedContext.Success();
return tokenValidatedContext.Result;
}
}
if (validationFailures != null)
{
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures)
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
return AuthenticateResult.Fail(authenticationFailedContext.Exception);
}
return AuthenticateResult.Fail("No SecurityTokenValidator available for token: " + token ?? "[null]");
}
catch (Exception ex)
{
Logger.ErrorProcessingMessage(ex);
var authenticationFailedContext = new AuthenticationFailedContext(Context, Scheme, Options)
{
Exception = ex
};
await Events.AuthenticationFailed(authenticationFailedContext);
if (authenticationFailedContext.Result != null)
{
return authenticationFailedContext.Result;
}
throw;
}
}
}
}

View File

@ -0,0 +1,11 @@
using Microsoft.AspNetCore.Http;
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
using System.Threading.Tasks;
namespace PoweredSoft.DynamicJwtBearer
{
public interface IDynamicJwtBearerHanderConfigurationResolver
{
Task<OpenIdConnectConfiguration> ResolveCurrentOpenIdConfiguration(HttpContext context);
}
}

View File

@ -0,0 +1,38 @@
using Microsoft.Extensions.Logging;
using System;
namespace PoweredSoft.DynamicJwtBearer
{
internal static class LoggingExtensions
{
private static Action<ILogger, Exception> _tokenValidationFailed;
private static Action<ILogger, Exception> _tokenValidationSucceeded;
private static Action<ILogger, Exception> _errorProcessingMessage;
static LoggingExtensions()
{
_tokenValidationFailed = LoggerMessage.Define(
eventId: new EventId(1, "TokenValidationFailed"),
logLevel: LogLevel.Information,
formatString: "Failed to validate the token.");
_tokenValidationSucceeded = LoggerMessage.Define(
eventId: new EventId(2, "TokenValidationSucceeded"),
logLevel: LogLevel.Information,
formatString: "Successfully validated the token.");
_errorProcessingMessage = LoggerMessage.Define(
eventId: new EventId(3, "ProcessingMessageFailed"),
logLevel: LogLevel.Error,
formatString: "Exception occurred while processing message.");
}
public static void TokenValidationFailed(this ILogger logger, Exception ex)
=> _tokenValidationFailed(logger, ex);
public static void TokenValidationSucceeded(this ILogger logger)
=> _tokenValidationSucceeded(logger, null);
public static void ErrorProcessingMessage(this ILogger logger, Exception ex)
=> _errorProcessingMessage(logger, ex);
}
}

View File

@ -0,0 +1,12 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netcoreapp3.0</TargetFramework>
</PropertyGroup>
<ItemGroup>
<PackageReference Include="Microsoft.Extensions.Logging.Abstractions" Version="3.0.0" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="3.0.0" />
</ItemGroup>
</Project>