Skip to content

Commit

Permalink
Added support for ClaimsPrincipal input converter
Browse files Browse the repository at this point in the history
  • Loading branch information
smokedlinq committed Sep 14, 2022
1 parent 07d180b commit 81bb934
Show file tree
Hide file tree
Showing 6 changed files with 103 additions and 91 deletions.
28 changes: 28 additions & 0 deletions src/Http.Authentication.JwtBearer/ClaimsPrincipalConverter.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,28 @@
using Microsoft.Azure.Functions.Worker.Converters;
using System.Security.Claims;

namespace Microsoft.Azure.Functions.Worker;

internal class ClaimsPrincipalConverter : IInputConverter
{
public ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
{
if (context.TargetType != typeof(ClaimsPrincipal))
{
return ValueTask.FromResult(ConversionResult.Unhandled());
}

try
{
var principal = context.FunctionContext.GetClaimsPrincipal();

return principal is null
? ValueTask.FromResult(ConversionResult.Unhandled())
: ValueTask.FromResult(ConversionResult.Success(principal));
}
catch (Exception ex)
{
return ValueTask.FromResult(ConversionResult.Failed(ex));
}
}
}
21 changes: 0 additions & 21 deletions src/Http.Authentication.JwtBearer/FunctionContextExtensions.cs

This file was deleted.

Original file line number Diff line number Diff line change
Expand Up @@ -9,9 +9,9 @@
</PropertyGroup>

<ItemGroup>
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />
</ItemGroup>

</Project>
137 changes: 70 additions & 67 deletions src/Http.Authentication.JwtBearer/JwtBearerAuthenticationMiddleware.cs
Original file line number Diff line number Diff line change
Expand Up @@ -22,26 +22,26 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next

if (authentication is not null)
{
var request = context.GetHttpRequestData();
var request = await context.GetHttpRequestDataAsync().ConfigureAwait(false);

if (request is not null)
{
var principal = await AuthenticateAsync(context, request).ConfigureAwait(false);

if (principal is null)
{
context.SetHttpResponseData(request.CreateResponse(HttpStatusCode.Unauthorized));
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
context.GetInvocationResult().Value = response;
return;
}
else
{
context.Items[nameof(ClaimsPrincipal)] = principal;

if (!authentication.IsAuthorized(principal))
{
context.SetHttpResponseData(request.CreateResponse(HttpStatusCode.Forbidden));
return;
}
context.Items[nameof(ClaimsPrincipal)] = principal;

if (!authentication.IsAuthorized(principal))
{
var response = request.CreateResponse(HttpStatusCode.Forbidden);
context.GetInvocationResult().Value = response;
return;
}
}
}
Expand All @@ -58,84 +58,87 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next

try
{
if (!request.Headers.TryGetValues("Authorization", out var values) || !values.Any())
{
logger.LogTrace("No Authorization header present on the request.");
return null;
}

var authorization = values.Single();
var value = AuthenticationHeaderValue.Parse(authorization);

if (!string.Equals(JwtBearerDefaults.AuthenticationScheme, value.Scheme, StringComparison.InvariantCultureIgnoreCase))
{
logger.LogTrace($"Authorizatrion header scheme {JwtBearerDefaults.AuthenticationScheme} is not supported.");
return null;
}

var options = context.InstanceServices.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().CurrentValue;

if (_configuration is null && options.ConfigurationManager != null)
{
_configuration = await options.ConfigurationManager.GetConfigurationAsync(CancellationToken.None).ConfigureAwait(false);
}

if (request.Headers.TryGetValues("Authorization", out var values))
{
var authorization = values.SingleOrDefault();

if (authorization is not null)
{
var value = AuthenticationHeaderValue.Parse(authorization);
var token = value.Parameter;
var validationParameters = options.TokenValidationParameters.Clone();

if (string.Equals(JwtBearerDefaults.AuthenticationScheme, value.Scheme, StringComparison.InvariantCultureIgnoreCase))
{
var token = value.Parameter;
var validationParameters = options.TokenValidationParameters.Clone();
if (_configuration is not null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;

if (_configuration is not null)
{
var issuers = new[] { _configuration.Issuer };
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
}

validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
?? _configuration.SigningKeys;
}
List<Exception>? validationFailures = null;
SecurityToken? validatedToken = null;
foreach (var validator in options.SecurityTokenValidators)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
logger.LogTrace(ex, "Token validation failed: {Message}", ex.Message);

List<Exception>? validationFailures = null;
SecurityToken? validatedToken = null;
foreach (var validator in options.SecurityTokenValidators)
// 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)
{
if (validator.CanReadToken(token))
{
ClaimsPrincipal principal;
try
{
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
}
catch (Exception ex)
{
logger.LogInformation(ex, "Token validation failed: {Message}", ex.Message);

// 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.LogTrace("Token validation succeeded for principal: {Identity}", principal.Identity?.Name ?? "[null]");

return principal;
}
options.ConfigurationManager.RequestRefresh();
}

if (validationFailures is not null)
if (validationFailures == null)
{
var exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures);
logger.LogTrace(exception, "Token validation failed: {Message}", exception.Message);
}
else
{
logger.LogTrace("No SecurityTokenValidator available for token: {Token}", token ?? "[null]");
validationFailures = new List<Exception>(1);
}
validationFailures.Add(ex);
continue;
}

logger.LogTrace("Token validation succeeded for principal: {Identity}", principal.Identity?.Name ?? "[null]");

return principal;
}
}

if (validationFailures is not null)
{
var exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures);
logger.LogTrace(exception, "Token validation failed: {Message}", exception.Message);
}
else
{
logger.LogTrace("No SecurityTokenValidator available for token: {Token}", token ?? "[null]");
}

return null;
}
catch (Exception ex)
Expand Down
2 changes: 2 additions & 0 deletions src/Http.Authentication.JwtBearer/JwtExtensions.cs
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ public static IFunctionsWorkerApplicationBuilder AddJwtBearerAuthentication(this

builder.UseMiddleware<JwtBearerAuthenticationMiddleware>();

builder.Services.Configure<WorkerOptions>(options => options.InputConverters.Register<ClaimsPrincipalConverter>());

return builder;
}

Expand Down
2 changes: 1 addition & 1 deletion src/Http/Http.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@

<ItemGroup>
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.4.0" />
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.6.0" />
</ItemGroup>

</Project>

0 comments on commit 81bb934

Please sign in to comment.