using System; using System.Security.Claims; using System.Security.Cryptography; using System.Threading.Tasks; using Microsoft.AspNetCore.Authentication; using Microsoft.AspNetCore.Authentication.Cookies; using Microsoft.Extensions.Caching.Distributed; namespace API.Services.Store; /// /// The is used as for the OIDC implementation /// The full AuthenticationTicket cannot be included in the Cookie as popular reverse proxies (like nginx) will deny the request /// due the large header size. Instead, the key is used. /// /// public class CustomTicketStore(IDistributedCache cache, TicketSerializer ticketSerializer): ITicketStore { public async Task StoreAsync(AuthenticationTicket ticket) { // Note: It might not be needed to make this cryptographic random, but better safe than sorry var bytes = new byte[32]; RandomNumberGenerator.Fill(bytes); var key = Convert.ToBase64String(bytes); await RenewAsync(key, ticket); return key; } public Task RenewAsync(string key, AuthenticationTicket ticket) { var options = new DistributedCacheEntryOptions(); var expiresUtc = ticket.Properties.ExpiresUtc; if (expiresUtc.HasValue) { options.AbsoluteExpiration = expiresUtc.Value; } else { options.SlidingExpiration = TimeSpan.FromDays(7); } return cache.SetAsync(key, ticketSerializer.Serialize(ticket), options); } public async Task RetrieveAsync(string key) { var bytes = await cache.GetAsync(key); if (bytes == null) return CreateFailureTicket(); return ticketSerializer.Deserialize(bytes); } public Task RemoveAsync(string key) { return cache.RemoveAsync(key); } private static AuthenticationTicket CreateFailureTicket() { var identity = new ClaimsIdentity(); var principal = new ClaimsPrincipal(identity); var properties = new AuthenticationProperties { ExpiresUtc = DateTimeOffset.UtcNow.AddMinutes(-1), // Already expired }; return new AuthenticationTicket(principal, properties, "Cookies"); } }