using System.Linq; using System.Net; using System.Net.Http; using System.Net.Http.Json; using System.Threading.Tasks; using FluentAssertions; using SequenceAuth.Lib; using Xunit; namespace SequenceAuth.Example.Tests; public class SequenceAuthE2ETests : IClassFixture { private readonly HttpClient _client; public SequenceAuthE2ETests(CustomWebApplicationFactory factory) { _client = factory.CreateClient(); } private async Task InitSessionAsync() { var loginResponse = await _client.PostAsJsonAsync("/auth/login", new { username = "testuser" }); loginResponse.EnsureSuccessStatusCode(); var token = loginResponse.Headers.GetValues(new SequenceAuthOptions().NextHeaderName).FirstOrDefault(); token.Should().NotBeNullOrWhiteSpace(); return token!; } [Fact] public async Task RequestWithoutHeader_ReturnsUnauthorized() { var response = await _client.GetAsync("/secure/get-data"); response.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Fact] public async Task ValidChain_FollowsSequenceAndSucceeds() { // 1. Init Session var token1 = await InitSessionAsync(); // 2. First Request var request1 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); request1.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token1); var response1 = await _client.SendAsync(request1); response1.EnsureSuccessStatusCode(); var token2 = response1.Headers.GetValues(new SequenceAuthOptions().NextHeaderName).First(); token2.Should().NotBeNullOrWhiteSpace(); // 3. Second Request with New Token var request2 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); request2.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token2); var response2 = await _client.SendAsync(request2); response2.EnsureSuccessStatusCode(); var token3 = response2.Headers.GetValues(new SequenceAuthOptions().NextHeaderName).First(); token3.Should().NotBeNullOrWhiteSpace(); } [Fact] public async Task AttackerReplaysToken_CompromisesSequence() { var token1 = await InitSessionAsync(); System.Console.WriteLine($"Token 1: {token1}"); var userRequest1 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); userRequest1.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token1); var userResponse1 = await _client.SendAsync(userRequest1); userResponse1.EnsureSuccessStatusCode(); var token2 = userResponse1.Headers.GetValues(new SequenceAuthOptions().NextHeaderName).First(); System.Console.WriteLine($"Token 2: {token2}"); var attackerRequest = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); attackerRequest.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token1); var attackerResponse = await _client.SendAsync(attackerRequest); System.Console.WriteLine($"Attacker Response: {attackerResponse.StatusCode}"); var attackerBody = await attackerResponse.Content.ReadAsStringAsync(); System.Console.WriteLine($"Attacker Body: {attackerBody}"); attackerResponse.StatusCode.Should().Be(HttpStatusCode.Unauthorized); var userRequest2 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); userRequest2.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token2); var userResponse2 = await _client.SendAsync(userRequest2); userResponse2.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } [Fact] public async Task AttackerStrikesFirst_CompromisesSequence() { var token1 = await InitSessionAsync(); var attackerRequest1 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); attackerRequest1.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token1); var attackerResponse1 = await _client.SendAsync(attackerRequest1); attackerResponse1.EnsureSuccessStatusCode(); var token2 = attackerResponse1.Headers.GetValues(new SequenceAuthOptions().NextHeaderName).First(); var userRequest1 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); userRequest1.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token1); var userResponse1 = await _client.SendAsync(userRequest1); System.Console.WriteLine($"User Response 1: {userResponse1.StatusCode}"); userResponse1.StatusCode.Should().Be(HttpStatusCode.Unauthorized); var attackerRequest2 = new HttpRequestMessage(HttpMethod.Get, "/secure/get-data"); attackerRequest2.Headers.Add(new SequenceAuthOptions().AuthHeaderName, token2); var attackerResponse2 = await _client.SendAsync(attackerRequest2); attackerResponse2.StatusCode.Should().Be(HttpStatusCode.Unauthorized); } }