Skip to content

Commit 76bf4d4

Browse files
authored
Merge pull request #79 from Virtual-Finland-Development/feature/security-token-audience-guard
Security token audience guard
2 parents c516847 + 78282de commit 76bf4d4

15 files changed

Lines changed: 71 additions & 24 deletions

File tree

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Activities/Productizer/ProductizerController.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -159,7 +159,7 @@ public async Task<IActionResult> SaveOrUpdatePersonJobApplicantProfile(UpdateJob
159159
e.Message);
160160
try
161161
{
162-
var jwkToken = _authenticationService.ParseAuthenticationHeader(Request);
162+
var jwkToken = await _authenticationService.ParseAuthenticationHeader(Request);
163163
var query = new VerifyIdentityUser.Query(jwkToken.UserId, jwkToken.Issuer);
164164
var createdUser = await _mediator.Send(query);
165165
userId = createdUser.Id;

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Helpers/Services/AuthenticationService.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -18,7 +18,7 @@ public AuthenticationService(UserSecurityService userSecurityService)
1818
return person.Id;
1919
}
2020

21-
public JwtTokenResult ParseAuthenticationHeader(HttpRequest httpRequest)
21+
public Task<JwtTokenResult> ParseAuthenticationHeader(HttpRequest httpRequest)
2222
{
2323
var token = httpRequest.Headers.Authorization.ToString().Replace("Bearer ", string.Empty);
2424
return _userSecurityService.ParseJwtToken(token);

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Helpers/Services/UserSecurityService.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -27,7 +27,7 @@ public UserSecurityService(UsersDbContext usersDbContext, ILogger<UserSecuritySe
2727
/// <exception cref="NotAuthorizedException">If user id and the issuer are not found in the DB for any given user, this is not a valid user within the users database.</exception>
2828
public async Task<Person> VerifyAndGetAuthenticatedUser(string token)
2929
{
30-
var jwtTokenResult = ParseJwtToken(token);
30+
var jwtTokenResult = await ParseJwtToken(token);
3131

3232
try
3333
{
@@ -44,7 +44,7 @@ public async Task<Person> VerifyAndGetAuthenticatedUser(string token)
4444
/// <summary>
4545
/// Parses the JWT token and returns the issuer and the user id
4646
/// </summary>
47-
public JwtTokenResult ParseJwtToken(string token)
47+
public Task<JwtTokenResult> ParseJwtToken(string token)
4848
{
4949
return _applicationSecurity.ParseJwtToken(token);
5050
}

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/ApplicationSecurity.cs

Lines changed: 6 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,7 @@ public ApplicationSecurity(List<ISecurityFeature> features)
1717
/// <summary>
1818
/// Parses the JWT token and returns the issuer and the user id
1919
/// </summary>
20-
public JwtTokenResult ParseJwtToken(string token)
20+
public async Task<JwtTokenResult> ParseJwtToken(string token)
2121
{
2222
if (string.IsNullOrEmpty(token)) throw new NotAuthorizedException("No token provided");
2323

@@ -30,8 +30,12 @@ public JwtTokenResult ParseJwtToken(string token)
3030
var tokenIssuer = parsedToken.Issuer;
3131
var securityFeature = _features.Find(o => o.Issuer == tokenIssuer) ?? throw new NotAuthorizedException("The given token issuer is not valid");
3232

33+
// Resolve and validate the token audience
34+
var tokenAudience = parsedToken.Audiences.FirstOrDefault() ?? throw new NotAuthorizedException("The given token audience is not valid");
35+
await securityFeature.ValidateSecurityTokenAudience(tokenAudience);
36+
3337
// Resolve user id
3438
var userId = securityFeature.ResolveTokenUserId(parsedToken) ?? throw new NotAuthorizedException("The given token claim is not valid");
35-
return new JwtTokenResult { UserId = userId, Issuer = securityFeature.Issuer };
39+
return new JwtTokenResult { UserId = userId, Issuer = securityFeature.Issuer, Audience = tokenAudience };
3640
}
3741
}

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/Features/ISecurityFeature.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ public interface ISecurityFeature
1010
void BuildAuthorization(AuthorizationOptions options);
1111
string GetSecurityPolicySchemeName();
1212
string? ResolveTokenUserId(JwtSecurityToken jwtSecurityToken);
13+
Task ValidateSecurityTokenAudience(string audience);
1314

14-
public string? Issuer { get; }
15+
public string Issuer { get; }
1516
}

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/Features/SecurityFeature.cs

Lines changed: 32 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,8 @@
44
using Microsoft.AspNetCore.Authorization;
55
using Microsoft.IdentityModel.Tokens;
66
using NetDevPack.Security.JwtExtensions;
7+
using VirtualFinland.UserAPI.Exceptions;
8+
using VirtualFinland.UserAPI.Security.Models;
79
using JwksExtension = VirtualFinland.UserAPI.Helpers.Extensions.JwksExtension;
810

911
namespace VirtualFinland.UserAPI.Security.Features;
@@ -13,7 +15,13 @@ public class SecurityFeature : ISecurityFeature
1315
///
1416
/// The issuer of the JWT token
1517
///
16-
public string? Issuer { get; set; }
18+
public string Issuer => _issuer ?? throw new ArgumentNullException("Issuer is not set");
19+
protected string? _issuer;
20+
21+
/// <summary>
22+
/// Security feature options
23+
/// </summary>
24+
protected SecurityFeatureOptions _options { get; set; }
1725

1826
/// <summary>
1927
/// The URL to the OpenID configuration
@@ -35,15 +43,16 @@ public class SecurityFeature : ISecurityFeature
3543
/// </summary>
3644
protected const int _configUrlRetryWaitTime = 3000;
3745

38-
public SecurityFeature(SecurityFeatureOptions configuration)
46+
public SecurityFeature(SecurityFeatureOptions options)
3947
{
40-
Issuer = configuration.Issuer;
41-
_openIDConfigurationURL = configuration.OpenIdConfigurationUrl;
42-
_jwksOptionsUrl = configuration.AuthorizationJwksJsonUrl;
48+
_options = options;
49+
_issuer = options.Issuer;
50+
_openIDConfigurationURL = options.OpenIdConfigurationUrl;
51+
_jwksOptionsUrl = options.AuthorizationJwksJsonUrl;
4352

4453
if (string.IsNullOrEmpty(_openIDConfigurationURL) && string.IsNullOrEmpty(_jwksOptionsUrl))
4554
{
46-
throw new ArgumentNullException("Invalid security feature configuration");
55+
throw new ArgumentException("Invalid security feature configuration");
4756
}
4857
}
4958

@@ -84,6 +93,20 @@ public string GetSecurityPolicySchemeName()
8493
return jwtSecurityToken.Subject; // the "sub" claim
8594
}
8695

96+
/// <summary>
97+
/// Validates the token audience
98+
/// </summary>
99+
/// <param name="audience"></param>
100+
/// <exception cref="NotAuthorizedException"></exception>
101+
public virtual Task ValidateSecurityTokenAudience(string audience)
102+
{
103+
if (_options.AudienceGuardEnabled)
104+
{
105+
if (!_options.AllowedAudiences.Contains(audience)) throw new NotAuthorizedException("The given token audience is not allowed");
106+
}
107+
return Task.CompletedTask;
108+
}
109+
87110
/// <summary>
88111
/// Configures the OpenID Connect authentication
89112
/// </summary>
@@ -118,10 +141,10 @@ protected virtual async void LoadOpenIdConfigUrl()
118141
if (httpResponse.IsSuccessStatusCode)
119142
{
120143
var jsonData = JsonNode.Parse(await httpResponse.Content.ReadAsStringAsync());
121-
Issuer = jsonData?["issuer"]?.ToString();
144+
_issuer = jsonData?["issuer"]?.ToString();
122145
_jwksOptionsUrl = jsonData?["jwks_uri"]?.ToString();
123146

124-
if (!string.IsNullOrEmpty(Issuer) && !string.IsNullOrEmpty(_jwksOptionsUrl))
147+
if (!string.IsNullOrEmpty(_issuer) && !string.IsNullOrEmpty(_jwksOptionsUrl))
125148
{
126149
break;
127150
}
@@ -130,7 +153,7 @@ protected virtual async void LoadOpenIdConfigUrl()
130153
}
131154

132155
// If all retries fail, then send an exception since the security information is critical to the functionality of the backend
133-
if (string.IsNullOrEmpty(Issuer) || string.IsNullOrEmpty(_jwksOptionsUrl))
156+
if (string.IsNullOrEmpty(_issuer) || string.IsNullOrEmpty(_jwksOptionsUrl))
134157
{
135158
throw new ArgumentNullException("Failed to retrieve OpenID configurations");
136159
}

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/Features/SinunaSecurityFeature.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,6 @@
11
using System.IdentityModel.Tokens.Jwt;
2+
using VirtualFinland.UserAPI.Exceptions;
3+
using VirtualFinland.UserAPI.Security.Models;
24

35
namespace VirtualFinland.UserAPI.Security.Features;
46

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/Features/SuomiFiSecurityFeature.cs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
using Microsoft.AspNetCore.Authentication;
22
using Microsoft.IdentityModel.Tokens;
33
using NetDevPack.Security.JwtExtensions;
4+
using VirtualFinland.UserAPI.Security.Models;
45
using JwksExtension = VirtualFinland.UserAPI.Helpers.Extensions.JwksExtension;
56

67
namespace VirtualFinland.UserAPI.Security.Features;

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/Features/TestbedSecurityFeature.cs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,5 @@
1+
using VirtualFinland.UserAPI.Security.Models;
2+
13
namespace VirtualFinland.UserAPI.Security.Features;
24

35
public class TestbedSecurityFeature : SecurityFeature

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/Security/IApplicationSecurity.cs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,5 +2,5 @@ namespace VirtualFinland.UserAPI.Security.Models;
22

33
public interface IApplicationSecurity
44
{
5-
JwtTokenResult ParseJwtToken(string token);
5+
Task<JwtTokenResult> ParseJwtToken(string token);
66
}

0 commit comments

Comments
 (0)