Skip to content

Commit fb53f4c

Browse files
authored
Merge pull request #28 from jokk-itu/feature/parallelize-backchannel-logout
2 parents 69134df + 5c45e2d commit fb53f4c

File tree

22 files changed

+171
-118
lines changed

22 files changed

+171
-118
lines changed

src/AuthServer.TestClient/Pages/Index.cshtml.cs

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,3 @@
1-
using Microsoft.AspNetCore.Mvc;
21
using Microsoft.AspNetCore.Mvc.RazorPages;
32

43
namespace AuthServer.TestClient.Pages;

src/AuthServer/Authentication/Abstractions/IClientLogoutService.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -3,12 +3,12 @@
33
internal interface IClientLogoutService
44
{
55
/// <summary>
6-
/// Requests the client's backchannel logout endpoint.
6+
/// Requests logout for all provided clients at the backchannel logout endpoint.
77
/// </summary>
8-
/// <param name="clientId"></param>
8+
/// <param name="clientIds"></param>
99
/// <param name="sessionId"></param>
1010
/// <param name="subjectIdentifier"></param>
1111
/// <param name="cancellationToken"></param>
1212
/// <returns></returns>
13-
Task Logout(string clientId, string? sessionId, string? subjectIdentifier, CancellationToken cancellationToken);
13+
Task Logout(IReadOnlyCollection<string> clientIds, string? sessionId, string? subjectIdentifier, CancellationToken cancellationToken);
1414
}

src/AuthServer/Authentication/ClientJwkService.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -84,8 +84,6 @@ public async Task<IEnumerable<JsonWebKey>> GetKeys(string clientId, string use,
8484

8585
private async Task<string> RefreshJwks(string? clientId, string jwksUri, CancellationToken cancellationToken)
8686
{
87-
// TODO implement a Timeout to reduce Denial-Of-Service attacks
88-
// TODO implement retry delegate handler (5XX and 429)
8987
try
9088
{
9189
using var httpClient = _httpClientFactory.CreateClient(HttpClientNameConstants.Client);
Lines changed: 47 additions & 33 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
using AuthServer.Authentication.Abstractions;
2-
using AuthServer.Cache.Abstractions;
32
using AuthServer.Core;
3+
using AuthServer.Entities;
44
using AuthServer.TokenBuilders;
55
using AuthServer.TokenBuilders.Abstractions;
66
using Microsoft.Extensions.Logging;
@@ -10,54 +10,68 @@ internal class ClientLogoutService : IClientLogoutService
1010
{
1111
private readonly IHttpClientFactory _httpClientFactory;
1212
private readonly ITokenBuilder<LogoutTokenArguments> _tokenBuilder;
13-
private readonly ICachedClientStore _cachedClientStore;
1413
private readonly ILogger<ClientLogoutService> _logger;
14+
private readonly AuthorizationDbContext _authorizationDbContext;
1515

1616
public ClientLogoutService(
1717
IHttpClientFactory httpClientFactory,
1818
ITokenBuilder<LogoutTokenArguments> tokenBuilder,
19-
ICachedClientStore cachedClientStore,
20-
ILogger<ClientLogoutService> logger)
19+
ILogger<ClientLogoutService> logger,
20+
AuthorizationDbContext authorizationDbContext)
2121
{
2222
_httpClientFactory = httpClientFactory;
2323
_tokenBuilder = tokenBuilder;
24-
_cachedClientStore = cachedClientStore;
2524
_logger = logger;
25+
_authorizationDbContext = authorizationDbContext;
2626
}
2727

28-
public async Task Logout(string clientId, string? sessionId, string? subjectIdentifier, CancellationToken cancellationToken)
28+
public async Task Logout(IReadOnlyCollection<string> clientIds, string? sessionId, string? subjectIdentifier, CancellationToken cancellationToken)
2929
{
30-
var client = await _cachedClientStore.Get(clientId, cancellationToken);
31-
32-
var httpClient = _httpClientFactory.CreateClient(HttpClientNameConstants.Client);
33-
var logoutToken = await _tokenBuilder.BuildToken(new LogoutTokenArguments
30+
var logoutRequests = new List<LogoutRequest>();
31+
foreach (var clientId in clientIds)
3432
{
35-
ClientId = clientId,
36-
SessionId = sessionId,
37-
SubjectIdentifier = subjectIdentifier
38-
}, cancellationToken);
33+
var logoutToken = await _tokenBuilder.BuildToken(
34+
new LogoutTokenArguments
35+
{
36+
ClientId = clientId,
37+
SessionId = sessionId,
38+
SubjectIdentifier = subjectIdentifier
39+
},
40+
cancellationToken);
3941

40-
var body = new Dictionary<string, string>
41-
{
42-
{ Parameter.LogoutToken, logoutToken }
43-
};
42+
var client = (await _authorizationDbContext.FindAsync<Client>([clientId], cancellationToken))!;
4443

45-
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, client.BackchannelLogoutUri)
46-
{
47-
Content = new FormUrlEncodedContent(body)
48-
};
44+
logoutRequests.Add(new LogoutRequest(clientId, client.BackchannelLogoutUri!, logoutToken));
45+
}
4946

50-
// TODO Implement retry for 5XX and 429
51-
// TODO Implement Timeout to remove denial-of-service attacks
47+
await Parallel.ForEachAsync(
48+
logoutRequests,
49+
cancellationToken,
50+
async (logoutRequest, innerToken) =>
51+
{
52+
var httpClient = _httpClientFactory.CreateClient(HttpClientNameConstants.Client);
5253

53-
try
54-
{
55-
var response = await httpClient.SendAsync(httpRequestMessage, cancellationToken);
56-
response.EnsureSuccessStatusCode();
57-
}
58-
catch (HttpRequestException e)
59-
{
60-
_logger.LogWarning(e, "Error occurred requesting logout for client {ClientId}", clientId);
61-
}
54+
var body = new Dictionary<string, string>
55+
{
56+
{ Parameter.LogoutToken, logoutRequest.LogoutToken }
57+
};
58+
59+
var httpRequestMessage = new HttpRequestMessage(HttpMethod.Post, logoutRequest.LogoutUri)
60+
{
61+
Content = new FormUrlEncodedContent(body)
62+
};
63+
64+
try
65+
{
66+
var response = await httpClient.SendAsync(httpRequestMessage, innerToken);
67+
response.EnsureSuccessStatusCode();
68+
}
69+
catch (HttpRequestException e)
70+
{
71+
_logger.LogWarning(e, "Error occurred requesting logout for client {ClientId}", logoutRequest.ClientId);
72+
}
73+
});
6274
}
75+
76+
private sealed record LogoutRequest(string ClientId, string LogoutUri, string LogoutToken);
6377
}

src/AuthServer/Authorization/SecureRequestService.cs

Lines changed: 0 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -126,8 +126,6 @@ public AuthorizeRequestDto GetCachedRequest()
126126
public async Task<AuthorizeRequestDto?> GetRequestByReference(Uri requestUri, string clientId,
127127
ClientTokenAudience audience, CancellationToken cancellationToken)
128128
{
129-
// TODO implement a Timeout to reduce Denial-Of-Service attacks, where a RequestUri recursively calls Authorize
130-
// TODO implement retry delegate handler (5XX and 429)
131129
var httpClient = _httpClientFactory.CreateClient(HttpClientNameConstants.Client);
132130
var request = new HttpRequestMessage(HttpMethod.Get, requestUri);
133131
request.Headers.Accept.Add(new MediaTypeWithQualityHeaderValue(MimeTypeConstants.OAuthRequestJwt));

src/AuthServer/Authorize/AuthorizeEndpointModule.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using AuthServer.Core;
22
using AuthServer.Core.Abstractions;
3+
using AuthServer.Endpoints;
34
using AuthServer.Endpoints.Filters;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.Http;
@@ -15,7 +16,7 @@ public void RegisterEndpoint(IEndpointRouteBuilder endpointRouteBuilder)
1516
var routeBuilder = endpointRouteBuilder.MapMethods(
1617
"connect/authorize",
1718
["GET", "POST"],
18-
(HttpContext httpContext, [FromKeyedServices("Authorize")] IEndpointHandler endpointHandler,
19+
(HttpContext httpContext, [FromKeyedServices(EndpointNameConstants.Authorize)] IEndpointHandler endpointHandler,
1920
CancellationToken cancellationToken) => endpointHandler.Handle(httpContext, cancellationToken));
2021

2122
routeBuilder

src/AuthServer/EndSession/EndSessionEndpointModule.cs

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,6 @@
11
using AuthServer.Core;
22
using AuthServer.Core.Abstractions;
3+
using AuthServer.Endpoints;
34
using AuthServer.Endpoints.Filters;
45
using Microsoft.AspNetCore.Builder;
56
using Microsoft.AspNetCore.Http;
@@ -15,7 +16,7 @@ public void RegisterEndpoint(IEndpointRouteBuilder endpointRouteBuilder)
1516
var routeBuilder = endpointRouteBuilder.MapMethods(
1617
"connect/end-session",
1718
["GET", "POST"],
18-
(HttpContext httpContext, [FromKeyedServices("EndSession")] IEndpointHandler endpointHandler,
19+
(HttpContext httpContext, [FromKeyedServices(EndpointNameConstants.EndSession)] IEndpointHandler endpointHandler,
1920
CancellationToken cancellationToken) => endpointHandler.Handle(httpContext, cancellationToken));
2021

2122
routeBuilder

src/AuthServer/EndSession/EndSessionRequestProcessor.cs

Lines changed: 6 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -98,10 +98,12 @@ public async Task<Unit> Process(EndSessionValidatedRequest request, Cancellation
9898
private async Task BackchannelLogout(IEnumerable<Client> clients, EndSessionValidatedRequest request,
9999
CancellationToken cancellationToken)
100100
{
101-
foreach (var client in clients.Where(x => x.BackchannelLogoutUri is not null))
102-
{
103-
await _clientLogoutService.Logout(client.Id, request.SessionId, request.SubjectIdentifier, cancellationToken);
104-
}
101+
var clientIds = clients
102+
.Where(x => x.BackchannelLogoutUri is not null)
103+
.Select(x => x.Id)
104+
.ToList();
105+
106+
await _clientLogoutService.Logout(clientIds, request.SessionId, request.SubjectIdentifier, cancellationToken);
105107
}
106108

107109
private sealed record SessionQuery(IEnumerable<Client> Clients);
Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
namespace AuthServer.Endpoints;
2+
3+
internal static class EndpointNameConstants
4+
{
5+
public const string PushedAuthorization = "PushedAuthorization";
6+
public const string Register = "Register";
7+
public const string EndSession = "EndSession";
8+
public const string Authorize = "Authorize";
9+
public const string Userinfo = "Userinfo";
10+
public const string Introspection = "Introspection";
11+
public const string Revocation = "Revocation";
12+
public const string Token = "Token";
13+
public const string GrantManagementQuery = "GrantManagementQuery";
14+
public const string GrantManagementRevoke = "GrantManagementRevoke";
15+
}

0 commit comments

Comments
 (0)