From 0474fd75d21879446f2db0347c94a8f3b01baeae Mon Sep 17 00:00:00 2001 From: Swapnamol Abraham Date: Thu, 13 Mar 2025 14:26:09 +0000 Subject: [PATCH] TD-3743: Concurrent Sessions Allowed --- .../Helpers/InMemoryTicketStore.cs | 104 ++++++++++++++++++ .../ServiceCollectionExtension.cs | 10 +- 2 files changed, 113 insertions(+), 1 deletion(-) create mode 100644 Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs diff --git a/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs b/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs new file mode 100644 index 0000000..1bfa669 --- /dev/null +++ b/Auth/LearningHub.Nhs.Auth/Helpers/InMemoryTicketStore.cs @@ -0,0 +1,104 @@ +namespace LearningHub.Nhs.Auth.Helpers +{ + using System; + using System.Collections.Concurrent; + using System.Linq; + using System.Threading.Tasks; + using Microsoft.AspNetCore.Authentication; + using Microsoft.AspNetCore.Authentication.Cookies; + + /// + /// Defines the . + /// + public class InMemoryTicketStore : ITicketStore + { + private readonly ConcurrentDictionary cache; + + /// + /// Initializes a new instance of the class. + /// The InMemoryTicketStore. + /// + /// the cache. + public InMemoryTicketStore(ConcurrentDictionary cache) + { + this.cache = cache; + } + + /// + /// The StoreAsync. + /// + /// The ticket. + /// The key. + public async Task StoreAsync(AuthenticationTicket ticket) + { + var ticketUserId = ticket.Principal.Claims.Where(c => c.Type == "sub") + .FirstOrDefault() + .Value; + var matchingAuthTicket = this.cache.Values.FirstOrDefault( + t => t.Principal.Claims.FirstOrDefault( + c => c.Type == "sub" + && c.Value == ticketUserId) != null); + if (matchingAuthTicket != null) + { + var cacheKey = this.cache.Where( + entry => entry.Value == matchingAuthTicket) + .Select(entry => entry.Key) + .FirstOrDefault(); + this.cache.TryRemove( + cacheKey, + out _); + } + + var key = Guid + .NewGuid() + .ToString(); + await this.RenewAsync( + key, + ticket); + return key; + } + + /// + /// The RenewAsync. + /// + /// The key. + /// The ticket. + /// The Task. + public Task RenewAsync( + string key, + AuthenticationTicket ticket) + { + this.cache.AddOrUpdate( + key, + ticket, + (_, _) => ticket); + return Task.CompletedTask; + } + + /// + /// The RetrieveAsync. + /// + /// The Key. + /// The Task. + public Task RetrieveAsync(string key) + { + this.cache.TryGetValue( + key, + out var ticket); + return Task.FromResult(ticket); + } + + /// + /// The RemoveAsync. + /// + /// The key. + /// The Task. + public Task RemoveAsync(string key) + { + this.cache.TryRemove( + key, + out _); + return Task.CompletedTask; + } + } + } diff --git a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs index 6aaf2f5..0a08288 100644 --- a/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs +++ b/Auth/LearningHub.Nhs.Auth/ServiceCollectionExtension.cs @@ -1,10 +1,12 @@ namespace LearningHub.Nhs.Auth { using System; + using System.Collections.Concurrent; using System.Security.Cryptography.X509Certificates; using Azure.Identity; using IdentityServer4; using LearningHub.Nhs.Auth.Configuration; + using LearningHub.Nhs.Auth.Helpers; using LearningHub.Nhs.Auth.Middleware; using LearningHub.Nhs.Caching; using LearningHub.Nhs.Models.Enums; @@ -70,7 +72,13 @@ public static void ConfigureServices(this IServiceCollection services, IConfigur { options.DefaultAuthenticateScheme = CookieAuthenticationDefaults.AuthenticationScheme; options.DefaultSignInScheme = CookieAuthenticationDefaults.AuthenticationScheme; - }).AddCookie().AddOpenIdConnect( + }) + .AddCookie(CookieAuthenticationDefaults.AuthenticationScheme, options => + { + options.AccessDeniedPath = "/Home/AccessDenied"; + options.SessionStore = new InMemoryTicketStore(new ConcurrentDictionary()); + }) + .AddOpenIdConnect( "oidc_oa", options => {