Skip to content

Commit 49e53f2

Browse files
committed
feat: jwt-token allowed audiences guard
1 parent c516847 commit 49e53f2

11 files changed

Lines changed: 62 additions & 18 deletions

File tree

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

Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -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+
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+
void 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: 29 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,17 @@ 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 void ValidateSecurityTokenAudience(string audience)
102+
{
103+
if (!_options.AudienceGuardEnabled) return;
104+
if (!_options.AllowedAudiences.Contains(audience)) throw new NotAuthorizedException("The given token audience is not allowed");
105+
}
106+
87107
/// <summary>
88108
/// Configures the OpenID Connect authentication
89109
/// </summary>
@@ -118,10 +138,10 @@ protected virtual async void LoadOpenIdConfigUrl()
118138
if (httpResponse.IsSuccessStatusCode)
119139
{
120140
var jsonData = JsonNode.Parse(await httpResponse.Content.ReadAsStringAsync());
121-
Issuer = jsonData?["issuer"]?.ToString();
141+
_issuer = jsonData?["issuer"]?.ToString();
122142
_jwksOptionsUrl = jsonData?["jwks_uri"]?.ToString();
123143

124-
if (!string.IsNullOrEmpty(Issuer) && !string.IsNullOrEmpty(_jwksOptionsUrl))
144+
if (!string.IsNullOrEmpty(_issuer) && !string.IsNullOrEmpty(_jwksOptionsUrl))
125145
{
126146
break;
127147
}
@@ -130,7 +150,7 @@ protected virtual async void LoadOpenIdConfigUrl()
130150
}
131151

132152
// 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))
153+
if (string.IsNullOrEmpty(_issuer) || string.IsNullOrEmpty(_jwksOptionsUrl))
134154
{
135155
throw new ArgumentNullException("Failed to retrieve OpenID configurations");
136156
}

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/Models/JwtTokenResult.cs

Lines changed: 3 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ namespace VirtualFinland.UserAPI.Security.Models;
22

33
public class JwtTokenResult
44
{
5-
public string? UserId { get; set; }
6-
public string? Issuer { get; set; }
5+
public string UserId { get; set; } = default!;
6+
public string Issuer { get; set; } = default!;
7+
public string Audience { get; set; } = default!;
78
}
Lines changed: 5 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,7 +1,11 @@
1+
namespace VirtualFinland.UserAPI.Security.Models;
2+
13
public class SecurityFeatureOptions
24
{
35
public string? OpenIdConfigurationUrl { get; set; }
46
public string? AuthorizationJwksJsonUrl { get; set; }
5-
public string? Issuer { get; set; }
7+
public string? Issuer { get; set; } = default!;
68
public bool IsEnabled { get; set; }
9+
public List<string> AllowedAudiences { get; set; } = new List<string>();
10+
public bool AudienceGuardEnabled { get; set; } = false;
711
}

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/appsettings.json

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -33,16 +33,22 @@
3333
"ConsentJwksJsonUrl": "https://consent.testbed.fi/.well-known/jwks.json",
3434
"ConsentIssuer": "https://consent.testbed.fi",
3535
"ConsentVerifyUrl": "https://consent.testbed.fi/Consent/Verify",
36-
"IsEnabled": false
36+
"IsEnabled": false,
37+
"AllowedAudiences": [],
38+
"AudienceGuardEnabled": false
3739
},
3840
"Sinuna": {
3941
"OpenIDConfigurationURL": "https://login.iam.qa.sinuna.fi/oxauth/.well-known/openid-configuration",
40-
"IsEnabled": false
42+
"IsEnabled": false,
43+
"AllowedAudiences": [],
44+
"AudienceGuardEnabled": false
4145
},
4246
"SuomiFi": {
4347
"AuthorizationJwksJsonUrl": "http://localhost:4078/auth/saml2/suomifi/.well-known/jwks.json",
4448
"Issuer": "virtual-finland/authentication-gw/suomifi",
45-
"IsEnabled": false
49+
"IsEnabled": false,
50+
"AllowedAudiences": [],
51+
"AudienceGuardEnabled": false
4652
}
4753
}
4854
},

VirtualFinland.UserAPI/src/VirtualFinland.UsersAPI/appsettings.local.json

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -28,7 +28,9 @@
2828
"IsEnabled": true
2929
},
3030
"Sinuna": {
31-
"IsEnabled": true
31+
"IsEnabled": true,
32+
"AllowedAudiences": [],
33+
"AudienceGuardEnabled": false
3234
},
3335
"SuomiFi": {
3436
"IsEnabled": true

0 commit comments

Comments
 (0)