diff --git a/packages/Gotrue/Gotrue.Tests/Gotrue.Tests.csproj b/packages/Gotrue/Gotrue.Tests/Gotrue.Tests.csproj
index e02659b5..0af7db47 100644
--- a/packages/Gotrue/Gotrue.Tests/Gotrue.Tests.csproj
+++ b/packages/Gotrue/Gotrue.Tests/Gotrue.Tests.csproj
@@ -33,6 +33,7 @@
+
diff --git a/packages/Gotrue/Gotrue.Tests/Identities/Data/expired_jwt_error.json b/packages/Gotrue/Gotrue.Tests/Identities/Data/expired_jwt_error.json
new file mode 100644
index 00000000..f3d911f8
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Identities/Data/expired_jwt_error.json
@@ -0,0 +1,5 @@
+{
+ "code": 403,
+ "error_code": "bad_jwt",
+ "msg": "invalid JWT: unable to parse or verify signature, token has invalid claims: token is expired"
+}
diff --git a/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_identities.json b/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_identities.json
new file mode 100644
index 00000000..6c5df362
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_identities.json
@@ -0,0 +1,27 @@
+{
+ "id": "user-id-123",
+ "aud": "authenticated",
+ "email": "user@example.com",
+ "identities": [
+ {
+ "identity_id": "identity-email-1",
+ "id": "user-id-123",
+ "user_id": "user-id-123",
+ "identity_data": { "email": "user@example.com", "sub": "user-id-123" },
+ "provider": "email",
+ "created_at": "2026-10-01T10:00:00Z",
+ "last_sign_in_at": "2026-10-01T10:00:00Z",
+ "updated_at": "2026-10-01T10:00:00Z"
+ },
+ {
+ "identity_id": "identity-github-1",
+ "id": "42",
+ "user_id": "user-id-123",
+ "identity_data": { "user_name": "octocat", "sub": "42" },
+ "provider": "github",
+ "created_at": "2026-10-02T10:00:00Z",
+ "last_sign_in_at": "2026-10-02T10:00:00Z",
+ "updated_at": "2026-10-02T10:00:00Z"
+ }
+ ]
+}
diff --git a/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_null_identities.json b/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_null_identities.json
new file mode 100644
index 00000000..81e6ee19
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Identities/Data/user_with_null_identities.json
@@ -0,0 +1,5 @@
+{
+ "id": "user-id-123",
+ "aud": "authenticated",
+ "identities": null
+}
diff --git a/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesContractTests.cs b/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesContractTests.cs
new file mode 100644
index 00000000..d1502137
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesContractTests.cs
@@ -0,0 +1,172 @@
+#region
+
+using System;
+using System.IO;
+using System.Linq;
+using System.Net.Http;
+using System.Threading;
+using System.Threading.Tasks;
+using FluentAssertions;
+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.Exceptions.FailureHint.Reason;
+
+#endregion
+
+namespace Gotrue.Tests.Identities;
+
+///
+/// Pins how GetUserIdentitiesAsync reads the signed-in user's linked identities: the GET /user request
+/// it puts on the wire, how the user's identities array maps to the result, and the failures raised before
+/// and after the request — all against a stubbed server.
+///
+[TestClass]
+[TestCategory("Contract")]
+public class UserIdentitiesContractTests
+{
+ private const string AccessToken = "an-access-token";
+ private IGotrueClient client = null!;
+ private MockGotrueServer server = null!;
+
+ [TestInitialize]
+ public void TestInitializer()
+ {
+ this.server = new MockGotrueServer();
+ this.client = TestClients.Against(this.server);
+ }
+
+ [TestCleanup]
+ public void TestCleanup() => this.server.Dispose();
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldGetTheUserWithTheSessionBearer()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, Fixture("user_with_identities.json"));
+ await this.client.GetUserIdentitiesAsync();
+ this.server.VerifySingleReceivedRequest()
+ .WithMethod(HttpMethod.Get)
+ .WithPath("/user")
+ .WithHeader("Authorization", $"Bearer {AccessToken}")
+ .WithHeader("apikey", MockGotrueServer.ApiKey);
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldReturnEveryLinkedIdentity_GivenUserWithIdentities()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, Fixture("user_with_identities.json"));
+ var identities = await this.client.GetUserIdentitiesAsync();
+ identities.Select(identity => identity.IdentityId).Should().Equal(new[] { "identity-email-1", "identity-github-1" },
+ "the result is the identities array of the user GoTrue returns from GET /user, in order, not the cached session user who has none");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldReturnEmpty_GivenUserWithNullIdentities()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, Fixture("user_with_null_identities.json"));
+ var identities = await this.client.GetUserIdentitiesAsync();
+ identities.Should().BeEmpty("collections are never null: an absent identities array means no linked identity");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldReturnEmpty_GivenNullUserBody()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, "null");
+ var identities = await this.client.GetUserIdentitiesAsync();
+ identities.Should().BeEmpty("collections are never null: a response without a user means no linked identity");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldThrowServerRejection_GivenExpiredJwt()
+ {
+ this.SignIn();
+ this.MockUserResponse(403, Fixture("expired_jwt_error.json"));
+ var listing = () => this.client.GetUserIdentitiesAsync();
+ (await listing.Should().ThrowAsync()).Which.ErrorCode.Should().Be("bad_jwt",
+ "GoTrue rejects an expired access token on GET /user with error_code bad_jwt");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldThrowNoSessionFound_GivenNoSession()
+ {
+ var listing = () => this.client.GetUserIdentitiesAsync();
+ (await listing.Should().ThrowAsync().WithMessage("A valid session is required.")).Which.Reason.Should().Be(NoSessionFound,
+ "GET /user requires the signed-in user's access token");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldThrowNoSessionFound_GivenSessionWithoutAccessToken()
+ {
+ this.SignIn(new Session { RefreshToken = "a-refresh-token", ExpiresIn = 3600 });
+ var listing = () => this.client.GetUserIdentitiesAsync();
+ (await listing.Should().ThrowAsync()).Which.Reason.Should().Be(NoSessionFound,
+ "GET /user requires the signed-in user's access token");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldNotSendARequest_GivenNoSession()
+ {
+ var listing = () => this.client.GetUserIdentitiesAsync();
+ await listing.Should().ThrowAsync();
+ this.server.CountReceivedRequests().Should().Be(0, "the missing session is detected before the wire");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldThrowOffline_GivenClientIsOffline()
+ {
+ this.SignIn();
+ this.client.Online = false;
+ var listing = () => this.client.GetUserIdentitiesAsync();
+ (await listing.Should().ThrowAsync().WithMessage("Only supported when online")).Which.Reason.Should().Be(Offline,
+ "reading the identities needs the server, like the other identity-linking operations");
+ }
+
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldCancelTheRequest_GivenCancelledToken()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, Fixture("user_with_identities.json"));
+ using var cancellation = new CancellationTokenSource();
+ cancellation.Cancel();
+ var listing = () => this.client.GetUserIdentitiesAsync(cancellation.Token);
+ await listing.Should().ThrowAsync("the cancellation token reaches the HTTP call");
+ }
+
+ [TestMethod]
+ public void GetUserIdentitiesAsync_ShouldComplete_GivenCallerBlockingOnContextThatNeverRunsContinuations()
+ {
+ this.SignIn();
+ this.MockUserResponse(200, Fixture("user_with_identities.json"));
+ BlockingCaller.Completes(() => this.client.GetUserIdentitiesAsync()).Should().BeTrue(
+ "a library must not resume on the caller's context: a UI thread blocking on the task would deadlock");
+ }
+
+ private void SignIn() => this.SignIn(new Session { AccessToken = AccessToken, RefreshToken = "a-refresh-token", ExpiresIn = 3600, User = new User { Id = "user-id-123" } });
+
+ private void SignIn(Session session)
+ {
+ var persistence = SessionPersistenceSubstitute.Tracking();
+ persistence.SaveSession(session);
+ this.client.SetPersistence(persistence);
+ this.client.LoadSession();
+ }
+
+ private void MockUserResponse(int statusCode, string body) =>
+ this.server
+ .Given(Request.Create().WithPath("/user").UsingGet())
+ .RespondWith(Response.Create()
+ .WithStatusCode(statusCode)
+ .WithHeader("Content-Type", "application/json")
+ .WithBody(body));
+
+ private static string Fixture(string name) =>
+ File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Identities", "Data", name));
+}
diff --git a/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesTests.cs b/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesTests.cs
new file mode 100644
index 00000000..93aaa236
--- /dev/null
+++ b/packages/Gotrue/Gotrue.Tests/Identities/UserIdentitiesTests.cs
@@ -0,0 +1,27 @@
+#region
+
+using System.Threading.Tasks;
+using FluentAssertions;
+using Gotrue.Tests.Support;
+using Microsoft.VisualStudio.TestTools.UnitTesting;
+
+#endregion
+
+namespace Gotrue.Tests.Identities;
+
+///
+/// End-to-end listing of the identities linked to the signed-in user against the live stack.
+///
+[TestClass]
+[TestCategory("E2E")]
+public class UserIdentitiesTests : AuthClientFixture
+{
+ [TestMethod]
+ public async Task GetUserIdentitiesAsync_ShouldReturnTheEmailIdentity_GivenUserSignedUpWithEmail()
+ {
+ var session = await this.SignUpNewUser();
+ var identities = await this.Client.GetUserIdentitiesAsync();
+ identities.Should().ContainSingle(identity => identity.Provider == "email" && identity.UserId == session.User!.Id,
+ "GoTrue creates an email identity for an email/password sign-up (GET /user identities)");
+ }
+}
diff --git a/packages/Gotrue/Gotrue.Tests/Observability/ObservabilityContractTests.cs b/packages/Gotrue/Gotrue.Tests/Observability/ObservabilityContractTests.cs
index e1d6c7c0..36008076 100644
--- a/packages/Gotrue/Gotrue.Tests/Observability/ObservabilityContractTests.cs
+++ b/packages/Gotrue/Gotrue.Tests/Observability/ObservabilityContractTests.cs
@@ -119,6 +119,17 @@ public async Task DomainSpan_ShouldParentTheHttpSpan()
this.HttpTokenSpan().ParentSpanId.Should().Be(domainSpan.SpanId);
}
+ [TestMethod]
+ public async Task DomainSpan_ShouldParentTheHttpSpan_GivenUserIdentitiesListing()
+ {
+ this.MockTokenSuccess();
+ await this.Refresh();
+ this.MockUserSuccess();
+ await this.client.GetUserIdentitiesAsync();
+ var domainSpan = this.activities.Should().ContainSingle(a => a.OperationName == "gotrue.get_user_identities").Which;
+ this.activities.Should().ContainSingle(a => a.OperationName == "GET /user").Which.ParentSpanId.Should().Be(domainSpan.SpanId);
+ }
+
[TestMethod]
public async Task HttpSpan_ShouldBeMarkedError_GivenFailedRequest()
{
@@ -217,6 +228,14 @@ private void MockTokenSuccess() =>
.WithHeader("Content-Type", "application/json")
.WithBody(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "TokenRefresh", "Fixtures", "token_success.json"))));
+ private void MockUserSuccess() =>
+ this.server
+ .Given(Request.Create().WithPath("/user").UsingGet())
+ .RespondWith(Response.Create()
+ .WithStatusCode(200)
+ .WithHeader("Content-Type", "application/json")
+ .WithBody(File.ReadAllText(Path.Combine(AppDomain.CurrentDomain.BaseDirectory, "Identities", "Data", "user_with_identities.json"))));
+
private void MockTokenFailure() =>
this.server
.Given(Request.Create().WithPath("/token").UsingPost())
diff --git a/packages/Gotrue/Gotrue/Client.cs b/packages/Gotrue/Gotrue/Client.cs
index a5d84fd2..c5e15d7e 100644
--- a/packages/Gotrue/Gotrue/Client.cs
+++ b/packages/Gotrue/Gotrue/Client.cs
@@ -537,6 +537,20 @@ public Task UnlinkIdentity(UserIdentity userIdentity)
return this.api.UnlinkIdentity(this.CurrentSession.AccessToken!, userIdentity);
}
+ ///
+ [ImplementedCapability("auth.identities.list_identities")]
+ public async Task> GetUserIdentitiesAsync(CancellationToken cancellationToken = default)
+ {
+ using var activity = GotrueInstrumentation.Source.StartActivity(GotrueInstrumentation.Spans.GetUserIdentities);
+ if (!this.Online)
+ {
+ throw new GotrueException("Only supported when online", Offline);
+ }
+ var accessToken = this.CurrentSession?.AccessToken ?? throw new GotrueException("A valid session is required.", NoSessionFound);
+ var user = await this.api.GetUserAsync(accessToken, cancellationToken).ConfigureAwait(false);
+ return user?.Identities ?? new List();
+ }
+
///
[ImplementedCapability("auth.session.sign_out")]
public async Task SignOut(SignOutScope scope = SignOutScope.Global)
diff --git a/packages/Gotrue/Gotrue/GotrueInstrumentation.cs b/packages/Gotrue/Gotrue/GotrueInstrumentation.cs
index 802cc38f..1090f20f 100644
--- a/packages/Gotrue/Gotrue/GotrueInstrumentation.cs
+++ b/packages/Gotrue/Gotrue/GotrueInstrumentation.cs
@@ -8,97 +8,97 @@
#endregion
-namespace Supabase.Gotrue
+namespace Supabase.Gotrue;
+
+///
+/// Diagnostics for the GoTrue client, exposed through so
+/// consumers can subscribe with the OpenTelemetry SDK using
+/// (AddSource(...) / AddMeter(...)). Emission is zero-cost when nothing is listening.
+/// Telemetry must never carry secrets: URLs are recorded without their query string (GoTrue
+/// puts grant types, tokens and API keys there) and no tag may contain a JWT, refresh token,
+/// or other credential or PII.
+///
+internal static class GotrueInstrumentation
{
- ///
- /// Diagnostics for the GoTrue client, exposed through so
- /// consumers can subscribe with the OpenTelemetry SDK using
- /// (AddSource(...) / AddMeter(...)). Emission is zero-cost when nothing is listening.
- /// Telemetry must never carry secrets: URLs are recorded without their query string (GoTrue
- /// puts grant types, tokens and API keys there) and no tag may contain a JWT, refresh token,
- /// or other credential or PII.
- ///
- internal static class GotrueInstrumentation
- {
-
- internal static readonly ActivitySource Source =
- Instrumentation.CreateActivitySource(typeof(GotrueInstrumentation).Assembly, GotrueDiagnostics.SourceName);
-
- private static readonly Meter Meter =
- Instrumentation.CreateMeter(typeof(GotrueInstrumentation).Assembly, GotrueDiagnostics.SourceName);
-
- private static readonly Histogram RequestDuration = Meter.CreateHistogram(
- "supabase.gotrue.http.request.duration", "s", "Duration of HTTP requests sent by the GoTrue client.");
-
- ///
- /// Starts the client span for an outgoing HTTP request, tagged per OpenTelemetry HTTP
- /// conventions with the sanitized (query-less) URL. Returns null when nothing is listening.
- ///
- internal static Activity? StartHttpActivity(HttpMethod method, Uri uri) =>
- Source.StartActivity($"{method.Method} {uri.AbsolutePath}", ActivityKind.Client)
- .SetHttpRequestTags(method.Method, uri);
-
- ///
- /// Records the request duration histogram for an HTTP request outcome.
- ///
- internal static void RecordRequest(HttpMethod method, Uri uri, int? statusCode, string? errorType, long startTimestamp)
- {
- if (!RequestDuration.Enabled)
- return;
-
- var tags = new TagList
- {
- { "http.request.method", method.Method },
- { "server.address", uri.Host },
- { "url.path", uri.AbsolutePath }
- };
-
- if (statusCode.HasValue)
- tags.Add("http.response.status_code", statusCode.Value);
-
- if (errorType != null)
- tags.Add("error.type", errorType);
-
- RequestDuration.Record(GetElapsedSeconds(startTimestamp), tags);
- }
- private static double GetElapsedSeconds(long startTimestamp) => (Stopwatch.GetTimestamp() - startTimestamp) / (double)Stopwatch.Frequency;
-
- /// Names of the domain spans emitted for the public GoTrue operations.
- internal static class Spans
- {
- internal const string SignUp = "gotrue.sign_up";
- internal const string SendMagicLink = "gotrue.send_magic_link";
- internal const string SignInWithIdToken = "gotrue.sign_in.id_token";
- internal const string LinkIdentityWithIdToken = "gotrue.link_identity.id_token";
- internal const string SignInWithOtp = "gotrue.sign_in.otp";
- internal const string SignIn = "gotrue.sign_in";
- internal const string SignInAnonymously = "gotrue.sign_in.anonymous";
- internal const string VerifyOtp = "gotrue.verify_otp";
- internal const string VerifyTokenHash = "gotrue.verify_token_hash";
- internal const string SignOut = "gotrue.sign_out";
- internal const string UpdateUser = "gotrue.update_user";
- internal const string RefreshSession = "gotrue.refresh_session";
- internal const string SetSession = "gotrue.set_session";
- internal const string RetrieveSession = "gotrue.retrieve_session";
- internal const string ExchangeCode = "gotrue.exchange_code";
- internal const string RefreshToken = "gotrue.refresh_token";
- }
-
- /// Keys of the tags set on the domain spans.
- internal static class Tags
- {
- internal const string SignUpType = "gotrue.sign_up.type";
- internal const string Provider = "gotrue.provider";
- internal const string OtpChannel = "gotrue.otp.channel";
- internal const string SignInType = "gotrue.sign_in.type";
- internal const string SignOutScope = "gotrue.sign_out.scope";
- }
-
- /// Values for the tag.
- internal static class Channels
- {
- internal const string Email = "email";
- internal const string Phone = "phone";
- }
- }
+
+ internal static readonly ActivitySource Source =
+ Instrumentation.CreateActivitySource(typeof(GotrueInstrumentation).Assembly, GotrueDiagnostics.SourceName);
+
+ private static readonly Meter Meter =
+ Instrumentation.CreateMeter(typeof(GotrueInstrumentation).Assembly, GotrueDiagnostics.SourceName);
+
+ private static readonly Histogram RequestDuration = Meter.CreateHistogram(
+ "supabase.gotrue.http.request.duration", "s", "Duration of HTTP requests sent by the GoTrue client.");
+
+ ///
+ /// Starts the client span for an outgoing HTTP request, tagged per OpenTelemetry HTTP
+ /// conventions with the sanitized (query-less) URL. Returns null when nothing is listening.
+ ///
+ internal static Activity? StartHttpActivity(HttpMethod method, Uri uri) =>
+ Source.StartActivity($"{method.Method} {uri.AbsolutePath}", ActivityKind.Client)
+ .SetHttpRequestTags(method.Method, uri);
+
+ ///
+ /// Records the request duration histogram for an HTTP request outcome.
+ ///
+ internal static void RecordRequest(HttpMethod method, Uri uri, int? statusCode, string? errorType, long startTimestamp)
+ {
+ if (!RequestDuration.Enabled)
+ return;
+
+ var tags = new TagList
+ {
+ { "http.request.method", method.Method },
+ { "server.address", uri.Host },
+ { "url.path", uri.AbsolutePath }
+ };
+
+ if (statusCode.HasValue)
+ tags.Add("http.response.status_code", statusCode.Value);
+
+ if (errorType != null)
+ tags.Add("error.type", errorType);
+
+ RequestDuration.Record(GetElapsedSeconds(startTimestamp), tags);
+ }
+ private static double GetElapsedSeconds(long startTimestamp) => (Stopwatch.GetTimestamp() - startTimestamp) / (double) Stopwatch.Frequency;
+
+ /// Names of the domain spans emitted for the public GoTrue operations.
+ internal static class Spans
+ {
+ internal const string SignUp = "gotrue.sign_up";
+ internal const string SendMagicLink = "gotrue.send_magic_link";
+ internal const string SignInWithIdToken = "gotrue.sign_in.id_token";
+ internal const string LinkIdentityWithIdToken = "gotrue.link_identity.id_token";
+ internal const string GetUserIdentities = "gotrue.get_user_identities";
+ internal const string SignInWithOtp = "gotrue.sign_in.otp";
+ internal const string SignIn = "gotrue.sign_in";
+ internal const string SignInAnonymously = "gotrue.sign_in.anonymous";
+ internal const string VerifyOtp = "gotrue.verify_otp";
+ internal const string VerifyTokenHash = "gotrue.verify_token_hash";
+ internal const string SignOut = "gotrue.sign_out";
+ internal const string UpdateUser = "gotrue.update_user";
+ internal const string RefreshSession = "gotrue.refresh_session";
+ internal const string SetSession = "gotrue.set_session";
+ internal const string RetrieveSession = "gotrue.retrieve_session";
+ internal const string ExchangeCode = "gotrue.exchange_code";
+ internal const string RefreshToken = "gotrue.refresh_token";
+ }
+
+ /// Keys of the tags set on the domain spans.
+ internal static class Tags
+ {
+ internal const string SignUpType = "gotrue.sign_up.type";
+ internal const string Provider = "gotrue.provider";
+ internal const string OtpChannel = "gotrue.otp.channel";
+ internal const string SignInType = "gotrue.sign_in.type";
+ internal const string SignOutScope = "gotrue.sign_out.scope";
+ }
+
+ /// Values for the tag.
+ internal static class Channels
+ {
+ internal const string Email = "email";
+ internal const string Phone = "phone";
+ }
}
\ No newline at end of file
diff --git a/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs b/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs
index 932bddeb..18e06978 100644
--- a/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs
+++ b/packages/Gotrue/Gotrue/Interfaces/IGotrueClient.cs
@@ -1,6 +1,7 @@
#region
using System;
+using System.Collections.Generic;
using System.Threading;
using System.Threading.Tasks;
using Supabase.Core.Interfaces;
@@ -470,6 +471,29 @@ public interface IGotrueClient : IGettableHeaders
///
Task UnlinkIdentity(UserIdentity userIdentity);
+ ///
+ /// Lists the external identities linked to the signed-in user, read from the server so identities linked
+ /// or unlinked since sign-in are included. Returns an empty list when the user has none.
+ ///
+ ///
+ /// The identities linked to the signed-in user.
+ ///
+ /// when the client is offline,
+ /// when no user is signed in,
+ /// or the server's rejection of the request.
+ ///
+ ///
+ ///
+ /// var identities = await client.GetUserIdentitiesAsync();
+ /// var github = identities.FirstOrDefault(identity => identity.Provider == "github");
+ /// if (github != null)
+ /// {
+ /// await client.UnlinkIdentity(github);
+ /// }
+ ///
+ ///
+ Task> GetUserIdentitiesAsync(CancellationToken cancellationToken = default);
+
///
/// Add a listener to get errors that occur outside of a typical Exception flow.
/// In particular, this is used to get errors and messages from the background thread
diff --git a/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt b/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt
index 6b8b7a19..c6c65b66 100644
--- a/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt
+++ b/packages/Gotrue/Gotrue/PublicAPI.Unshipped.txt
@@ -34,6 +34,7 @@ Supabase.Gotrue.Api.CreateCustomProviderAsync(string! jwt, Supabase.Gotrue.Creat
Supabase.Gotrue.Api.GetCustomProviderAsync(string! jwt, string! identifier, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Supabase.Gotrue.Api.ListCustomProvidersAsync(string! jwt, Supabase.Gotrue.Common.CustomProviderType? type = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
Supabase.Gotrue.Api.UpdateCustomProviderAsync(string! jwt, string! identifier, Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges! changes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+Supabase.Gotrue.Client.GetUserIdentitiesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
Supabase.Gotrue.Common.CustomProvider
Supabase.Gotrue.Common.CustomProvider.$() -> Supabase.Gotrue.Common.CustomProvider!
Supabase.Gotrue.Common.CustomProvider.AcceptableClientIds.get -> System.Collections.Generic.IReadOnlyList!
@@ -159,6 +160,7 @@ Supabase.Gotrue.Interfaces.IGotrueApi.GetCustomProviderAsync(st
Supabase.Gotrue.Interfaces.IGotrueApi.ListCustomProvidersAsync(string! jwt, Supabase.Gotrue.Common.CustomProviderType? type = null, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
Supabase.Gotrue.Interfaces.IGotrueApi.SignOutAsync(string! jwt, Supabase.Gotrue.Constants.SignOutScope scope = Supabase.Gotrue.Constants.SignOutScope.Global, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
Supabase.Gotrue.Interfaces.IGotrueApi.UpdateCustomProviderAsync(string! jwt, string! identifier, Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges! changes, System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!
+Supabase.Gotrue.Interfaces.IGotrueClient.GetUserIdentitiesAsync(System.Threading.CancellationToken cancellationToken = default(System.Threading.CancellationToken)) -> System.Threading.Tasks.Task!>!
Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges
Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges.$() -> Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges!
Supabase.Gotrue.UpdateCustomProvider.CustomProviderChanges.AuthorizationUrl.get -> System.Uri?
diff --git a/packages/Gotrue/Gotrue/User.cs b/packages/Gotrue/Gotrue/User.cs
index 78643164..7349173e 100644
--- a/packages/Gotrue/Gotrue/User.cs
+++ b/packages/Gotrue/Gotrue/User.cs
@@ -5,7 +5,6 @@
using System;
using System.Collections.Generic;
using System.Text.Json.Serialization;
-using Supabase.Core.Attributes;
using Supabase.Gotrue.Mfa;
#endregion
@@ -45,7 +44,6 @@ public class User
public string? Id { get; set; }
[JsonPropertyName("identities")]
- [PartialCapability("auth.identities.list_identities", "No dedicated getUserIdentities method; linked identities are reachable only via the User.Identities property.")]
public List Identities { get; set; } = new List();
[JsonPropertyName("invited_at")]