diff --git a/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs
new file mode 100644
index 00000000..2f5d2051
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Authentication/SignOutLocalSessionTests.cs
@@ -0,0 +1,70 @@
+#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);
+ using (new AssertionScope())
+ {
+ this.client.CurrentSession.Should().NotBeNull("the others scope signs out every session except this one");
+ this.persistence.LoadSession().Should().NotBeNull();
+ }
+ }
+}
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);
}
///