Skip to content

Commit 0cbf697

Browse files
committed
Update transformation
1 parent 54f4b14 commit 0cbf697

6 files changed

+187
-2
lines changed

.gitignore

+2
Original file line numberDiff line numberDiff line change
@@ -397,3 +397,5 @@ FodyWeavers.xsd
397397
# JetBrains Rider
398398
*.sln.iml
399399
/_logs-IdentityProvider.txt
400+
401+
/_logs**
+124
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,124 @@
1+
using IdentityModel.Client;
2+
using Microsoft.Extensions.Caching.Distributed;
3+
using Microsoft.Extensions.Options;
4+
5+
namespace RazorPageOidcClient;
6+
7+
public class ApiTokenCacheClient
8+
{
9+
private readonly ILogger<ApiTokenCacheClient> _logger;
10+
private readonly HttpClient _httpClient;
11+
private readonly IOptions<AuthConfigurations> _authConfigurations;
12+
13+
private static readonly object _lock = new();
14+
private readonly IDistributedCache _cache;
15+
16+
private const int cacheExpirationInDays = 1;
17+
18+
private class AccessTokenItem
19+
{
20+
public string AccessToken { get; set; } = string.Empty;
21+
public DateTime ExpiresIn { get; set; }
22+
}
23+
24+
public ApiTokenCacheClient(
25+
IOptions<AuthConfigurations> authConfigurations,
26+
IHttpClientFactory httpClientFactory,
27+
ILoggerFactory loggerFactory,
28+
IDistributedCache cache)
29+
{
30+
_authConfigurations = authConfigurations;
31+
_httpClient = httpClientFactory.CreateClient();
32+
_logger = loggerFactory.CreateLogger<ApiTokenCacheClient>();
33+
_cache = cache;
34+
}
35+
36+
public async Task<string> GetApiToken(string api_name, string api_scope, string secret)
37+
{
38+
var accessToken = GetFromCache(api_name);
39+
40+
if (accessToken != null)
41+
{
42+
if (accessToken.ExpiresIn > DateTime.UtcNow)
43+
{
44+
return accessToken.AccessToken;
45+
}
46+
else
47+
{
48+
// remove => NOT Needed for this cache type
49+
}
50+
}
51+
52+
_logger.LogDebug("GetApiToken new from STS for {api_name}", api_name);
53+
54+
// add
55+
var newAccessToken = await GetApiTokenInternal(api_name, api_scope, secret);
56+
AddToCache(api_name, newAccessToken);
57+
58+
return newAccessToken.AccessToken;
59+
}
60+
61+
private async Task<AccessTokenItem> GetApiTokenInternal(string api_name, string api_scope, string secret)
62+
{
63+
try
64+
{
65+
var disco = await HttpClientDiscoveryExtensions.GetDiscoveryDocumentAsync(
66+
_httpClient,
67+
_authConfigurations.Value.StsServer);
68+
69+
if (disco.IsError)
70+
{
71+
_logger.LogError("disco error Status code: {discoIsError}, Error: {discoError}", disco.IsError, disco.IsError);
72+
throw new ApplicationException($"Status code: {disco.IsError}, Error: {disco.Error}");
73+
}
74+
75+
var tokenResponse = await HttpClientTokenRequestExtensions.RequestClientCredentialsTokenAsync(_httpClient, new ClientCredentialsTokenRequest
76+
{
77+
Scope = api_scope,
78+
ClientSecret = secret,
79+
Address = disco.TokenEndpoint,
80+
ClientId = api_name
81+
});
82+
83+
if (tokenResponse.IsError || tokenResponse.AccessToken == null)
84+
{
85+
_logger.LogError("tokenResponse.IsError Status code: {tokenResponseIsError}, Error: {tokenResponseError}", tokenResponse.IsError, tokenResponse.Error);
86+
throw new ApplicationException($"Status code: {tokenResponse.IsError}, Error: {tokenResponse.Error}");
87+
}
88+
89+
return new AccessTokenItem
90+
{
91+
ExpiresIn = DateTime.UtcNow.AddSeconds(tokenResponse.ExpiresIn),
92+
AccessToken = tokenResponse.AccessToken
93+
};
94+
95+
}
96+
catch (Exception e)
97+
{
98+
_logger.LogError("Exception {e}", e);
99+
throw new ApplicationException($"Exception {e}");
100+
}
101+
}
102+
103+
private void AddToCache(string key, AccessTokenItem accessTokenItem)
104+
{
105+
var options = new DistributedCacheEntryOptions()
106+
.SetSlidingExpiration(TimeSpan.FromDays(cacheExpirationInDays));
107+
108+
lock (_lock)
109+
{
110+
_cache.SetString(key, System.Text.Json.JsonSerializer.Serialize(accessTokenItem), options);
111+
}
112+
}
113+
114+
private AccessTokenItem? GetFromCache(string key)
115+
{
116+
var item = _cache.GetString(key);
117+
if (item != null)
118+
{
119+
return System.Text.Json.JsonSerializer.Deserialize<AccessTokenItem>(item);
120+
}
121+
122+
return null;
123+
}
124+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
namespace RazorPageOidcClient;
2+
3+
public class AuthConfigurations
4+
{
5+
public string StsServer { get; set; } = string.Empty;
6+
public string ProtectedApiUrl { get; set; } = string.Empty;
7+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,40 @@
1+
using RazorPageOidcClient;
2+
using Yarp.ReverseProxy.Transforms;
3+
using Yarp.ReverseProxy.Transforms.Builder;
4+
5+
namespace BffOpenIddict.Server.ApiClient;
6+
7+
public class JwtTransformProvider : ITransformProvider
8+
{
9+
private readonly ApiService _apiService;
10+
11+
public JwtTransformProvider(ApiService apiService)
12+
{
13+
_apiService = apiService;
14+
}
15+
16+
public void Apply(TransformBuilderContext context)
17+
{
18+
if(context.Route.RouteId == "downstreamapiroute")
19+
{
20+
_apiService.
21+
context.AddRequestTransform(transformContext =>
22+
{
23+
transformContext.ProxyRequest
24+
.Options
25+
.Set(new HttpRequestOptionsKey<string>("CustomMetadata"), value);
26+
27+
return default;
28+
});
29+
}
30+
31+
public void ValidateCluster(TransformClusterValidationContext context)
32+
{
33+
var route = context.Cluster;
34+
}
35+
36+
public void ValidateRoute(TransformRouteValidationContext context)
37+
{
38+
var route = context.Route;
39+
}
40+
}

bff/server/BffOpenIddict.Server.csproj

+2
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,8 @@
1313
<PackageReference Include="NetEscapades.AspNetCore.SecurityHeaders.TagHelpers" Version="0.21.0" />
1414
<PackageReference Include="Yarp.ReverseProxy" Version="2.1.0" />
1515

16+
<PackageReference Include="IdentityModel" Version="6.2.0" />
17+
1618
<PackageReference Include="Serilog" Version="3.1.1" />
1719
<PackageReference Include="Serilog.AspNetCore" Version="8.0.1" />
1820
<PackageReference Include="Serilog.Settings.Configuration" Version="8.0.0" />

bff/server/Program.cs

+12-2
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,18 @@
11
using BffOpenIddict.Server;
2+
using BffOpenIddict.Server.ApiClient;
23
using BffOpenIddict.Server.Services;
34
using Microsoft.AspNetCore.Authentication.Cookies;
45
using Microsoft.AspNetCore.Authentication.OpenIdConnect;
56
using Microsoft.AspNetCore.Mvc;
7+
using Microsoft.Extensions.Configuration;
68
using Microsoft.IdentityModel.JsonWebTokens;
79
using Microsoft.IdentityModel.Logging;
810
using Microsoft.IdentityModel.Protocols.OpenIdConnect;
911
using Microsoft.IdentityModel.Tokens;
12+
using RazorPageOidcClient;
13+
using System.Net.Security;
14+
using System.Runtime.ConstrainedExecution;
15+
using System.Security.Cryptography.X509Certificates;
1016

1117
var builder = WebApplication.CreateBuilder(args);
1218

@@ -65,10 +71,14 @@
6571
// .RequireAuthenticatedUser()
6672
// .Build();
6773
//options.Filters.Add(new AuthorizeFilter(policy));
68-
});
74+
});
75+
76+
builder.Services.AddTransient<ApiService>();
77+
builder.Services.AddSingleton<ApiTokenCacheClient>();
6978

7079
builder.Services.AddReverseProxy()
71-
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"));
80+
.LoadFromConfig(builder.Configuration.GetSection("ReverseProxy"))
81+
.AddTransforms<JwtTransformProvider>();
7282

7383
var app = builder.Build();
7484

0 commit comments

Comments
 (0)