Skip to content

Commit 81bb934

Browse files
committed
Added support for ClaimsPrincipal input converter
1 parent 07d180b commit 81bb934

File tree

6 files changed

+103
-91
lines changed

6 files changed

+103
-91
lines changed
Lines changed: 28 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,28 @@
1+
using Microsoft.Azure.Functions.Worker.Converters;
2+
using System.Security.Claims;
3+
4+
namespace Microsoft.Azure.Functions.Worker;
5+
6+
internal class ClaimsPrincipalConverter : IInputConverter
7+
{
8+
public ValueTask<ConversionResult> ConvertAsync(ConverterContext context)
9+
{
10+
if (context.TargetType != typeof(ClaimsPrincipal))
11+
{
12+
return ValueTask.FromResult(ConversionResult.Unhandled());
13+
}
14+
15+
try
16+
{
17+
var principal = context.FunctionContext.GetClaimsPrincipal();
18+
19+
return principal is null
20+
? ValueTask.FromResult(ConversionResult.Unhandled())
21+
: ValueTask.FromResult(ConversionResult.Success(principal));
22+
}
23+
catch (Exception ex)
24+
{
25+
return ValueTask.FromResult(ConversionResult.Failed(ex));
26+
}
27+
}
28+
}

src/Http.Authentication.JwtBearer/FunctionContextExtensions.cs

Lines changed: 0 additions & 21 deletions
This file was deleted.

src/Http.Authentication.JwtBearer/Http.Authentication.JwtBearer.csproj

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -9,9 +9,9 @@
99
</PropertyGroup>
1010

1111
<ItemGroup>
12-
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.1" />
12+
<PackageReference Include="Microsoft.AspNetCore.Authentication.JwtBearer" Version="6.0.9" />
1313
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
14-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.4.0" />
14+
<PackageReference Include="Microsoft.Azure.Functions.Worker" Version="1.8.0" />
1515
</ItemGroup>
1616

1717
</Project>

src/Http.Authentication.JwtBearer/JwtBearerAuthenticationMiddleware.cs

Lines changed: 70 additions & 67 deletions
Original file line numberDiff line numberDiff line change
@@ -22,26 +22,26 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next
2222

2323
if (authentication is not null)
2424
{
25-
var request = context.GetHttpRequestData();
25+
var request = await context.GetHttpRequestDataAsync().ConfigureAwait(false);
2626

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

3131
if (principal is null)
3232
{
33-
context.SetHttpResponseData(request.CreateResponse(HttpStatusCode.Unauthorized));
33+
var response = request.CreateResponse(HttpStatusCode.Unauthorized);
34+
context.GetInvocationResult().Value = response;
3435
return;
3536
}
36-
else
37-
{
38-
context.Items[nameof(ClaimsPrincipal)] = principal;
3937

40-
if (!authentication.IsAuthorized(principal))
41-
{
42-
context.SetHttpResponseData(request.CreateResponse(HttpStatusCode.Forbidden));
43-
return;
44-
}
38+
context.Items[nameof(ClaimsPrincipal)] = principal;
39+
40+
if (!authentication.IsAuthorized(principal))
41+
{
42+
var response = request.CreateResponse(HttpStatusCode.Forbidden);
43+
context.GetInvocationResult().Value = response;
44+
return;
4545
}
4646
}
4747
}
@@ -58,84 +58,87 @@ public async Task Invoke(FunctionContext context, FunctionExecutionDelegate next
5858

5959
try
6060
{
61+
if (!request.Headers.TryGetValues("Authorization", out var values) || !values.Any())
62+
{
63+
logger.LogTrace("No Authorization header present on the request.");
64+
return null;
65+
}
66+
67+
var authorization = values.Single();
68+
var value = AuthenticationHeaderValue.Parse(authorization);
69+
70+
if (!string.Equals(JwtBearerDefaults.AuthenticationScheme, value.Scheme, StringComparison.InvariantCultureIgnoreCase))
71+
{
72+
logger.LogTrace($"Authorizatrion header scheme {JwtBearerDefaults.AuthenticationScheme} is not supported.");
73+
return null;
74+
}
75+
6176
var options = context.InstanceServices.GetRequiredService<IOptionsMonitor<JwtBearerOptions>>().CurrentValue;
6277

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

68-
if (request.Headers.TryGetValues("Authorization", out var values))
69-
{
70-
var authorization = values.SingleOrDefault();
7183

72-
if (authorization is not null)
73-
{
74-
var value = AuthenticationHeaderValue.Parse(authorization);
84+
var token = value.Parameter;
85+
var validationParameters = options.TokenValidationParameters.Clone();
7586

76-
if (string.Equals(JwtBearerDefaults.AuthenticationScheme, value.Scheme, StringComparison.InvariantCultureIgnoreCase))
77-
{
78-
var token = value.Parameter;
79-
var validationParameters = options.TokenValidationParameters.Clone();
87+
if (_configuration is not null)
88+
{
89+
var issuers = new[] { _configuration.Issuer };
90+
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
8091

81-
if (_configuration is not null)
82-
{
83-
var issuers = new[] { _configuration.Issuer };
84-
validationParameters.ValidIssuers = validationParameters.ValidIssuers?.Concat(issuers) ?? issuers;
92+
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
93+
?? _configuration.SigningKeys;
94+
}
8595

86-
validationParameters.IssuerSigningKeys = validationParameters.IssuerSigningKeys?.Concat(_configuration.SigningKeys)
87-
?? _configuration.SigningKeys;
88-
}
96+
List<Exception>? validationFailures = null;
97+
SecurityToken? validatedToken = null;
98+
foreach (var validator in options.SecurityTokenValidators)
99+
{
100+
if (validator.CanReadToken(token))
101+
{
102+
ClaimsPrincipal principal;
103+
try
104+
{
105+
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
106+
}
107+
catch (Exception ex)
108+
{
109+
logger.LogTrace(ex, "Token validation failed: {Message}", ex.Message);
89110

90-
List<Exception>? validationFailures = null;
91-
SecurityToken? validatedToken = null;
92-
foreach (var validator in options.SecurityTokenValidators)
111+
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
112+
if (options.RefreshOnIssuerKeyNotFound && options.ConfigurationManager != null
113+
&& ex is SecurityTokenSignatureKeyNotFoundException)
93114
{
94-
if (validator.CanReadToken(token))
95-
{
96-
ClaimsPrincipal principal;
97-
try
98-
{
99-
principal = validator.ValidateToken(token, validationParameters, out validatedToken);
100-
}
101-
catch (Exception ex)
102-
{
103-
logger.LogInformation(ex, "Token validation failed: {Message}", ex.Message);
104-
105-
// Refresh the configuration for exceptions that may be caused by key rollovers. The user can also request a refresh in the event.
106-
if (options.RefreshOnIssuerKeyNotFound && options.ConfigurationManager != null
107-
&& ex is SecurityTokenSignatureKeyNotFoundException)
108-
{
109-
options.ConfigurationManager.RequestRefresh();
110-
}
111-
112-
if (validationFailures == null)
113-
{
114-
validationFailures = new List<Exception>(1);
115-
}
116-
validationFailures.Add(ex);
117-
continue;
118-
}
119-
120-
logger.LogTrace("Token validation succeeded for principal: {Identity}", principal.Identity?.Name ?? "[null]");
121-
122-
return principal;
123-
}
115+
options.ConfigurationManager.RequestRefresh();
124116
}
125117

126-
if (validationFailures is not null)
118+
if (validationFailures == null)
127119
{
128-
var exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures);
129-
logger.LogTrace(exception, "Token validation failed: {Message}", exception.Message);
130-
}
131-
else
132-
{
133-
logger.LogTrace("No SecurityTokenValidator available for token: {Token}", token ?? "[null]");
120+
validationFailures = new List<Exception>(1);
134121
}
122+
validationFailures.Add(ex);
123+
continue;
135124
}
125+
126+
logger.LogTrace("Token validation succeeded for principal: {Identity}", principal.Identity?.Name ?? "[null]");
127+
128+
return principal;
136129
}
137130
}
138131

132+
if (validationFailures is not null)
133+
{
134+
var exception = (validationFailures.Count == 1) ? validationFailures[0] : new AggregateException(validationFailures);
135+
logger.LogTrace(exception, "Token validation failed: {Message}", exception.Message);
136+
}
137+
else
138+
{
139+
logger.LogTrace("No SecurityTokenValidator available for token: {Token}", token ?? "[null]");
140+
}
141+
139142
return null;
140143
}
141144
catch (Exception ex)

src/Http.Authentication.JwtBearer/JwtExtensions.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -25,6 +25,8 @@ public static IFunctionsWorkerApplicationBuilder AddJwtBearerAuthentication(this
2525

2626
builder.UseMiddleware<JwtBearerAuthenticationMiddleware>();
2727

28+
builder.Services.Configure<WorkerOptions>(options => options.InputConverters.Register<ClaimsPrincipalConverter>());
29+
2830
return builder;
2931
}
3032

src/Http/Http.csproj

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,7 @@
1010

1111
<ItemGroup>
1212
<PackageReference Include="Microsoft.Azure.Functions.Worker.Extensions.Http" Version="3.0.13" />
13-
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.4.0" />
13+
<PackageReference Include="Microsoft.Azure.Functions.Worker.Core" Version="1.6.0" />
1414
</ItemGroup>
1515

1616
</Project>

0 commit comments

Comments
 (0)