From 26b2224f9772299d7e66e9e10fc7910f809fdba9 Mon Sep 17 00:00:00 2001 From: Bruno Gale Date: Sun, 27 Sep 2026 00:21:13 +0200 Subject: [PATCH 1/2] fix(gotrue): sign out locally even if the server call fails --- .../SignOutLocalSessionTests.cs | 66 +++++++++++++++++++ packages/Gotrue/Gotrue/Client.cs | 15 ++++- 2 files changed, 78 insertions(+), 3 deletions(-) create mode 100644 packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs diff --git a/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs new file mode 100644 index 00000000..1e4d9cf7 --- /dev/null +++ b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs @@ -0,0 +1,66 @@ +#region + +using System.Threading.Tasks; +using FluentAssertions; +using FluentAssertions.Execution; +using Gotrue.Tests.Support; +using Microsoft.VisualStudio.TestTools.UnitTesting; +using Supabase.Gotrue; +using Supabase.Gotrue.Exceptions; +using Supabase.Gotrue.Interfaces; +using WireMock.RequestBuilders; +using WireMock.ResponseBuilders; +using static Supabase.Gotrue.Constants; + +#endregion + +namespace Gotrue.Tests.Authentication; + +/// +/// Sign-out clears the local session even if the server call fails, and keeps it for the others scope. +/// +[TestClass] +[TestCategory("Contract")] +public class SignOutLocalSessionTests +{ + private IGotrueClient client = null!; + private IGotrueSessionPersistence persistence = null!; + private MockGotrueServer server = null!; + + [TestInitialize] + public void TestInitialize() + { + this.server = new MockGotrueServer(); + this.client = TestClients.Against(this.server); + this.persistence = SessionPersistenceSubstitute.Tracking(); + this.persistence.SaveSession(new Session { AccessToken = "an-access-token", RefreshToken = "a-refresh-token", ExpiresIn = 3600 }); + this.client.SetPersistence(this.persistence); + this.client.LoadSession(); + } + + [TestCleanup] + public void TestCleanup() => this.server.Dispose(); + + [TestMethod] + public async Task SignOut_ShouldClearTheLocalSession_GivenTheServerFails() + { + this.server.Given(Request.Create().WithPath("/logout").UsingPost()) + .RespondWith(Response.Create().WithStatusCode(500)); + var signOut = () => this.client.SignOut(); + await signOut.Should().ThrowAsync(); + using (new AssertionScope()) + { + this.client.CurrentSession.Should().BeNull("a failed sign-out must not leave the user signed in"); + this.persistence.LoadSession().Should().BeNull(); + } + } + + [TestMethod] + public async Task SignOut_ShouldKeepTheLocalSession_GivenOthersScope() + { + this.server.Given(Request.Create().WithPath("/logout").UsingPost()) + .RespondWith(Response.Create().WithStatusCode(204)); + await this.client.SignOut(SignOutScope.Others); + this.client.CurrentSession.Should().NotBeNull("the others scope signs out every session except this one"); + } +} diff --git a/packages/Gotrue/Gotrue/Client.cs b/packages/Gotrue/Gotrue/Client.cs index c92969af..45a0d861 100644 --- a/packages/Gotrue/Gotrue/Client.cs +++ b/packages/Gotrue/Gotrue/Client.cs @@ -507,11 +507,20 @@ public async Task SignOut(SignOutScope scope = SignOutScope.Global) { using var activity = GotrueInstrumentation.Source.StartActivity(GotrueInstrumentation.Spans.SignOut); activity?.SetTag(GotrueInstrumentation.Tags.SignOutScope, scope.ToString()); - if (this.CurrentSession?.AccessToken != null) + try + { + if (this.CurrentSession?.AccessToken != null) + { + await this.api.SignOut(this.CurrentSession.AccessToken, scope).ConfigureAwait(false); + } + } + finally { - await this.api.SignOut(this.CurrentSession.AccessToken, scope); + if (scope != SignOutScope.Others) + { + await this.UpdateSessionAsync(null).ConfigureAwait(false); + } } - await this.UpdateSessionAsync(null).ConfigureAwait(false); } /// From a3d3e8cf252b4e78b0547f792b6a21d60b5896df Mon Sep 17 00:00:00 2001 From: Bruno Gale Date: Mon, 28 Sep 2026 16:30:05 +0200 Subject: [PATCH 2/2] chore(gotrue): check the persisted session in the others scope test --- .../Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs index 1e4d9cf7..2f5d2051 100644 --- a/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs +++ b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs @@ -61,6 +61,10 @@ public async Task SignOut_ShouldKeepTheLocalSession_GivenOthersScope() this.server.Given(Request.Create().WithPath("/logout").UsingPost()) .RespondWith(Response.Create().WithStatusCode(204)); await this.client.SignOut(SignOutScope.Others); - this.client.CurrentSession.Should().NotBeNull("the others scope signs out every session except this one"); + using (new AssertionScope()) + { + this.client.CurrentSession.Should().NotBeNull("the others scope signs out every session except this one"); + this.persistence.LoadSession().Should().NotBeNull(); + } } }