Skip to content

Commit e45d31f

Browse files
authored
Merge pull request #1 from fmichellonet/features/services-configuration
services configuration & authorize attribute
2 parents 5aae5a4 + 4daa79a commit e45d31f

15 files changed

+309
-197
lines changed
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,2 @@
1+
<wpf:ResourceDictionary xml:space="preserve" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:s="clr-namespace:System;assembly=mscorlib" xmlns:ss="urn:shemas-jetbrains-com:settings-storage-xaml" xmlns:wpf="http://schemas.microsoft.com/winfx/2006/xaml/presentation">
2+
<s:String x:Key="/Default/CodeStyle/Naming/CSharpNaming/Abbreviations/=ID/@EntryIndexedValue">ID</s:String></wpf:ResourceDictionary>

src/AzureFunctions.Extensions.OpenIDConnect/ApiAuthenticationService.cs renamed to src/AzureFunctions.Extensions.OpenIDConnect/AuthenticationService.cs

+17-45
Original file line numberDiff line numberDiff line change
@@ -5,44 +5,30 @@
55
using System.Security.Claims;
66
using System.Threading.Tasks;
77
using Microsoft.AspNetCore.Http;
8-
using Microsoft.Extensions.Options;
98
using Microsoft.IdentityModel.Tokens;
109

1110
/// <summary>
1211
/// Encapsulates checks of bearer tokens in HTTP request headers.
1312
/// </summary>
14-
internal class ApiAuthenticationService : IApiAuthentication
13+
internal class AuthenticationService : IAuthenticationService
1514
{
15+
private readonly TokenValidationParameters _tokenValidationParameters;
1616
private readonly IAuthorizationHeaderBearerTokenExtractor _authorizationHeaderBearerTokenExractor;
17-
1817
private readonly IJwtSecurityTokenHandlerWrapper _jwtSecurityTokenHandlerWrapper;
19-
20-
private readonly IOidcConfigurationManager _oidcConfigurationManager;
21-
22-
private readonly string _issuerUrl;
23-
private readonly string _issuer;
24-
private readonly string _audience;
25-
26-
private readonly string _nameClaimType;
27-
private readonly string _roleClaimType;
28-
29-
public ApiAuthenticationService(
30-
IOptions<OidcApiAuthSettings> apiAuthorizationSettingsOptions,
18+
private readonly IOpenIdConnectConfigurationManager _openIdConnectConfigurationManager;
19+
20+
public AuthenticationService(
21+
TokenValidationParameters tokenValidationParameters,
3122
IAuthorizationHeaderBearerTokenExtractor authorizationHeaderBearerTokenExractor,
3223
IJwtSecurityTokenHandlerWrapper jwtSecurityTokenHandlerWrapper,
33-
IOidcConfigurationManager oidcConfigurationManager)
24+
IOpenIdConnectConfigurationManager openIdConnectConfigurationManager)
3425
{
35-
_issuerUrl = apiAuthorizationSettingsOptions?.Value?.IssuerUrl;
36-
_issuer = apiAuthorizationSettingsOptions?.Value?.Issuer ?? _issuerUrl;
37-
_audience = apiAuthorizationSettingsOptions?.Value?.Audience;
38-
_nameClaimType = apiAuthorizationSettingsOptions?.Value?.NameClaimType ?? "http://schemas.xmlsoap.org/ws/2005/05/identity/claims/nameidentifier";
39-
_roleClaimType = apiAuthorizationSettingsOptions?.Value?.RoleClaimType ?? "http://schemas.microsoft.com/ws/2008/06/identity/claims/roleidentifier";
40-
26+
_tokenValidationParameters = tokenValidationParameters;
4127
_authorizationHeaderBearerTokenExractor = authorizationHeaderBearerTokenExractor;
4228

4329
_jwtSecurityTokenHandlerWrapper = jwtSecurityTokenHandlerWrapper;
4430

45-
_oidcConfigurationManager = oidcConfigurationManager;
31+
_openIdConnectConfigurationManager = openIdConnectConfigurationManager;
4632
}
4733

4834
/// <summary>
@@ -57,12 +43,11 @@ public ApiAuthenticationService(
5743
public async Task<ApiAuthenticationResult> AuthenticateAsync(
5844
IHeaderDictionary httpRequestHeaders)
5945
{
60-
bool isTokenValid = false;
46+
var isTokenValid = false;
6147
ClaimsPrincipal principal = new ClaimsPrincipal();
6248

63-
string authorizationBearerToken = _authorizationHeaderBearerTokenExractor.GetToken(
64-
httpRequestHeaders);
65-
if (authorizationBearerToken == null)
49+
var bearerToken = _authorizationHeaderBearerTokenExractor.GetToken(httpRequestHeaders);
50+
if (bearerToken == null)
6651
{
6752
return new ApiAuthenticationResult(principal,
6853
"Authorization header is missing, invalid format, or is not a Bearer token.");
@@ -80,7 +65,7 @@ public async Task<ApiAuthenticationResult> AuthenticateAsync(
8065
// then a fresh set of signing keys are retrieved from the OpenID Connect provider
8166
// (issuer) cached and returned.
8267
// This method will throw if the configuration cannot be retrieved, instead of returning null.
83-
isserSigningKeys = await _oidcConfigurationManager.GetIssuerSigningKeysAsync();
68+
isserSigningKeys = await _openIdConnectConfigurationManager.GetIssuerSigningKeysAsync();
8469
}
8570
catch (Exception ex)
8671
{
@@ -92,25 +77,12 @@ public async Task<ApiAuthenticationResult> AuthenticateAsync(
9277
try
9378
{
9479
// Try to validate the token.
80+
9581

96-
var tokenValidationParameters = new TokenValidationParameters
97-
{
98-
RequireSignedTokens = true,
99-
ValidAudience = _audience,
100-
ValidateAudience = true,
101-
ValidIssuer = _issuer,
102-
ValidateIssuer = true,
103-
ValidateIssuerSigningKey = true,
104-
ValidateLifetime = true,
105-
IssuerSigningKeys = isserSigningKeys,
106-
NameClaimType = _nameClaimType,
107-
RoleClaimType = _roleClaimType
108-
};
82+
_tokenValidationParameters.IssuerSigningKeys = isserSigningKeys;
10983

11084
// Throws if the the token cannot be validated.
111-
principal = _jwtSecurityTokenHandlerWrapper.ValidateToken(
112-
authorizationBearerToken,
113-
tokenValidationParameters);
85+
principal = _jwtSecurityTokenHandlerWrapper.ValidateToken(bearerToken,_tokenValidationParameters);
11486

11587
isTokenValid = true;
11688
}
@@ -124,7 +96,7 @@ public async Task<ApiAuthenticationResult> AuthenticateAsync(
12496
// Then we retry by asking for the signing keys and validating the token again.
12597
// We only retry once.
12698

127-
_oidcConfigurationManager.RequestRefresh();
99+
_openIdConnectConfigurationManager.RequestRefresh();
128100

129101
validationRetryCount++;
130102
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,46 @@
1+
namespace AzureFunctions.Extensions.OpenIDConnect
2+
{
3+
using System.Net;
4+
using System.Threading;
5+
using System.Threading.Tasks;
6+
using Microsoft.AspNetCore.Http;
7+
using Microsoft.Azure.WebJobs.Host;
8+
9+
public class AuthorizeFilter : FunctionInvocationFilterAttribute
10+
{
11+
private readonly IHttpContextAccessor _httpContextAccessor;
12+
private readonly IAuthenticationService _authenticationService;
13+
private readonly IRouteGuardian _routeGuardian;
14+
15+
public AuthorizeFilter(IHttpContextAccessor httpContextAccessor, IAuthenticationService authenticationService, IRouteGuardian routeGuardian)
16+
{
17+
_httpContextAccessor = httpContextAccessor;
18+
_authenticationService = authenticationService;
19+
_routeGuardian = routeGuardian;
20+
}
21+
22+
public override async Task OnExecutingAsync(FunctionExecutingContext executingContext, CancellationToken cancellationToken)
23+
{
24+
if (await _routeGuardian.ShouldAuthorize(executingContext.FunctionName))
25+
{
26+
var httpContext = _httpContextAccessor.HttpContext;
27+
28+
// Authenticate the user
29+
var authResult = await _authenticationService.AuthenticateAsync(httpContext.Request.Headers);
30+
31+
if (authResult.Failed)
32+
{
33+
await Unauthorized(httpContext, cancellationToken);
34+
return;
35+
}
36+
}
37+
await base.OnExecutingAsync(executingContext, cancellationToken);
38+
}
39+
40+
private async Task Unauthorized(HttpContext httpContext, CancellationToken cancellationToken)
41+
{
42+
httpContext.Response.StatusCode = (int)HttpStatusCode.Unauthorized;
43+
await httpContext.Response.WriteAsync(string.Empty, cancellationToken);
44+
}
45+
}
46+
}

src/AzureFunctions.Extensions.OpenIDConnect/AzureFunctions.Extensions.OpenIDConnect.csproj

+5-4
Original file line numberDiff line numberDiff line change
@@ -28,11 +28,12 @@ Works with popular identity providers including Auth0, Azure AD B2C, Azure AD, G
2828
<Message Importance="high" Text="NuspecProperties: $(NuspecProperties)" />
2929
</Target>
3030
<ItemGroup>
31-
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.1.8" />
31+
<PackageReference Include="Microsoft.AspNetCore.Http.Features" Version="3.1.12" />
3232
<PackageReference Include="Microsoft.Azure.Functions.Extensions" Version="1.1.0" />
33+
<PackageReference Include="Microsoft.Azure.WebJobs.Extensions.Http" Version="3.0.12" />
3334
<PackageReference Include="Microsoft.Extensions.Options" Version="3.1.8" />
34-
<PackageReference Include="Microsoft.IdentityModel.Protocols" Version="6.7.1" />
35-
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.7.1" />
36-
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.7.1" />
35+
<PackageReference Include="Microsoft.IdentityModel.Protocols" Version="6.8.0" />
36+
<PackageReference Include="Microsoft.IdentityModel.Protocols.OpenIdConnect" Version="6.8.0" />
37+
<PackageReference Include="System.IdentityModel.Tokens.Jwt" Version="6.8.0" />
3738
</ItemGroup>
3839
</Project>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,47 @@
1+
using Microsoft.Extensions.DependencyInjection;
2+
using Microsoft.IdentityModel.Tokens;
3+
4+
namespace AzureFunctions.Extensions.OpenIDConnect.Configuration
5+
{
6+
7+
public class ConfigurationBuilder
8+
{
9+
private readonly IServiceCollection _services;
10+
private bool _hasConfigurationManagerSettings;
11+
private bool _hasTokenValidationParameters;
12+
13+
internal bool IsValid => _hasTokenValidationParameters && _hasConfigurationManagerSettings;
14+
15+
internal ConfigurationBuilder(IServiceCollection services)
16+
{
17+
_services = services;
18+
}
19+
20+
public void SetTokenValidation(string audience, string issuer)
21+
{
22+
SetTokenValidation(TokenValidationParametersHelpers.Default(audience, issuer));
23+
}
24+
25+
public void SetTokenValidation(TokenValidationParameters settings)
26+
{
27+
_services.AddSingleton(settings);
28+
_hasTokenValidationParameters = true;
29+
}
30+
31+
public void SetIssuerBaseUrlConfiguration(string issuerUrl)
32+
{
33+
SetConfigurationManagerSettings(ConfigurationManagerSettings.FromIssuerBaseAddress(issuerUrl));
34+
}
35+
36+
public void SetIssuerMetadataConfigurationEndpoint(string metadataUrl)
37+
{
38+
SetConfigurationManagerSettings(ConfigurationManagerSettings.WithSpecificConfigurationEndpoint(metadataUrl));
39+
}
40+
41+
private void SetConfigurationManagerSettings(ConfigurationManagerSettings settings)
42+
{
43+
_services.AddSingleton(settings);
44+
_hasConfigurationManagerSettings = true;
45+
}
46+
}
47+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
using System;
2+
3+
namespace AzureFunctions.Extensions.OpenIDConnect.Configuration
4+
{
5+
internal class ConfigurationManagerSettings
6+
{
7+
public static string DefaultOpenidConfigurationEndPoint = ".well-known/openid-configuration";
8+
9+
public Uri MetadataAddress { get; }
10+
11+
private ConfigurationManagerSettings(string metadataAddress)
12+
{
13+
if(string.IsNullOrEmpty(metadataAddress))
14+
{
15+
throw new ArgumentNullException(nameof(metadataAddress));
16+
}
17+
18+
MetadataAddress = new Uri(metadataAddress);
19+
}
20+
21+
public static ConfigurationManagerSettings FromIssuerBaseAddress(string baseUrl)
22+
{
23+
return new ConfigurationManagerSettings($"{baseUrl}{DefaultOpenidConfigurationEndPoint}");
24+
}
25+
26+
public static ConfigurationManagerSettings WithSpecificConfigurationEndpoint(string metadataEndpointUrl)
27+
{
28+
return new ConfigurationManagerSettings(metadataEndpointUrl);
29+
}
30+
}
31+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,51 @@
1+
namespace AzureFunctions.Extensions.OpenIDConnect.Configuration
2+
{
3+
using System;
4+
using Microsoft.Azure.WebJobs.Host;
5+
using Microsoft.Extensions.DependencyInjection;
6+
7+
public static class ServicesConfigurationExtensions
8+
{
9+
public static void AddOpenIDConnect(this IServiceCollection services, string issuer, string audience)
10+
{
11+
Action<ConfigurationBuilder> configurator = builder =>
12+
{
13+
builder.SetTokenValidation(TokenValidationParametersHelpers.Default(audience, issuer));
14+
15+
builder.SetIssuerBaseUrlConfiguration(issuer);
16+
};
17+
18+
AddOpenIDConnect(services, configurator);
19+
}
20+
21+
public static void AddOpenIDConnect(this IServiceCollection services, Action<ConfigurationBuilder> configurator)
22+
{
23+
if (configurator == null)
24+
{
25+
throw new ArgumentNullException(nameof(configurator));
26+
}
27+
28+
var builder = new ConfigurationBuilder(services);
29+
configurator(builder);
30+
31+
if (!builder.IsValid)
32+
{
33+
throw new ArgumentException("Be sure to configure Token Validation and Configuration Manager");
34+
}
35+
36+
// These are created as a singletons, so that only one instance of each
37+
// is created for the lifetime of the hosting Azure Function App.
38+
// That helps reduce the number of calls to the authorization service
39+
// for the signing keys and other stuff that can be used across multiple
40+
// calls to the HTTP triggered Azure Functions.
41+
42+
services.AddHttpContextAccessor();
43+
services.AddSingleton<IAuthorizationHeaderBearerTokenExtractor, AuthorizationHeaderBearerTokenExtractor>();
44+
services.AddSingleton<IJwtSecurityTokenHandlerWrapper, JwtSecurityTokenHandlerWrapper>();
45+
services.AddSingleton<IOpenIdConnectConfigurationManager, OpenIdConnectConfigurationManager>();
46+
services.AddSingleton<IAuthenticationService, AuthenticationService>();
47+
services.AddSingleton<IRouteGuardian, RouteGuardian>();
48+
services.AddSingleton<IFunctionFilter, AuthorizeFilter>();
49+
}
50+
}
51+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
using Microsoft.IdentityModel.Tokens;
2+
3+
namespace AzureFunctions.Extensions.OpenIDConnect.Configuration
4+
{
5+
public static class TokenValidationParametersHelpers
6+
{
7+
public static TokenValidationParameters Default(string audience, string issuer)
8+
{
9+
return new TokenValidationParameters
10+
{
11+
RequireSignedTokens = true,
12+
ValidateIssuerSigningKey = true,
13+
ValidateLifetime = true,
14+
15+
ValidateAudience = true,
16+
ValidAudience = audience,
17+
18+
ValidateIssuer = true,
19+
ValidIssuer = issuer
20+
};
21+
}
22+
}
23+
}

src/AzureFunctions.Extensions.OpenIDConnect/IApiAuthentication.cs renamed to src/AzureFunctions.Extensions.OpenIDConnect/IAuthenticationService.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -3,7 +3,7 @@
33
using System.Threading.Tasks;
44
using Microsoft.AspNetCore.Http;
55

6-
public interface IApiAuthentication
6+
public interface IAuthenticationService
77
{
88
Task<ApiAuthenticationResult> AuthenticateAsync(IHeaderDictionary httpRequestHeaders);
99
}

src/AzureFunctions.Extensions.OpenIDConnect/IOidcConfigurationManager.cs renamed to src/AzureFunctions.Extensions.OpenIDConnect/IOpenIDConnectConfigurationManager.cs

+1-1
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,7 @@
44
using System.Threading.Tasks;
55
using Microsoft.IdentityModel.Tokens;
66

7-
internal interface IOidcConfigurationManager
7+
internal interface IOpenIdConnectConfigurationManager
88
{
99
/// <summary>
1010
/// Returns the cached signing keys if they were retrieved previously.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,9 @@
1+
namespace AzureFunctions.Extensions.OpenIDConnect
2+
{
3+
using System.Threading.Tasks;
4+
5+
public interface IRouteGuardian
6+
{
7+
Task<bool> ShouldAuthorize(string functionName);
8+
}
9+
}

0 commit comments

Comments
 (0)