From 950b143bfbcf78e894e3c1e6887f9da01459bdbd Mon Sep 17 00:00:00 2001 From: mazensakr05 Date: Wed, 7 Jan 2026 00:06:41 +0200 Subject: [PATCH 1/2] Add state parameter support for server-side OAuth CSRF protection - Add optional State property to SignInOptions for custom state values - Add State property to ProviderAuthState to return state to developer - Auto-generate state using GenerateNonce() when not provided by developer - Add state parameter to OAuth authorization URL query string - Implements CSRF protection per OAuth2 RFC 6749 Section 10.12 This allows developers to: 1. Provide custom state for server-side CSRF validation 2. Use auto-generated state for convenience 3. Store and validate state in OAuth callbacks Tested with ASP.NET Core application - state parameter now appears in OAuth URLs and is accessible via ProviderAuthState.State property. Fixes supabase-community/supabase-csharp#222 --- Gotrue/Helpers.cs | 18 +++++++++++++++++- Gotrue/ProviderAuthState.cs | 6 ++++++ Gotrue/SignInOptions.cs | 7 +++++++ 3 files changed, 30 insertions(+), 1 deletion(-) diff --git a/Gotrue/Helpers.cs b/Gotrue/Helpers.cs index bcee01b7..f08f7a20 100644 --- a/Gotrue/Helpers.cs +++ b/Gotrue/Helpers.cs @@ -105,7 +105,23 @@ internal static ProviderAuthState GetUrlForProvider(string url, Constants.Provid result.PKCEVerifier = codeVerifier; } - if (attr == null) + // Handle state parameter for CSRF protection + string stateParameter; + if (!string.IsNullOrEmpty(options.State)) + { + // Developer provided their own state - use it + stateParameter = options.State; + } + else + { + // Auto-generate state for convenience and security + stateParameter = Helpers.GenerateNonce(); + } + + query.Add("state", stateParameter); + result.State = stateParameter; + + if (attr == null) throw new Exception("Unknown provider"); query.Add("provider", attr.Mapping); diff --git a/Gotrue/ProviderAuthState.cs b/Gotrue/ProviderAuthState.cs index 28a11692..fe148db3 100644 --- a/Gotrue/ProviderAuthState.cs +++ b/Gotrue/ProviderAuthState.cs @@ -26,5 +26,11 @@ public ProviderAuthState(Uri uri) { Uri = uri; } + + /// + /// The state parameter for CSRF protection. + /// This should be stored by the developer and validated when the OAuth callback is received. + /// + public string? State { get; set; } } } diff --git a/Gotrue/SignInOptions.cs b/Gotrue/SignInOptions.cs index 4223345d..87560d95 100644 --- a/Gotrue/SignInOptions.cs +++ b/Gotrue/SignInOptions.cs @@ -29,5 +29,12 @@ public class SignInOptions /// PKCE is recommended for mobile and server-side applications. /// public OAuthFlowType FlowType { get; set; } = OAuthFlowType.Implicit; + + + /// + /// The state parameter for CSRF protection. + /// This should be stored by the developer and validated when the OAuth callback is received. + /// + public string? State { get; set; } } } From 2e6eae08ba5a63dc56e163984ec9b46da2dc6f7b Mon Sep 17 00:00:00 2001 From: mazensakr05 Date: Wed, 7 Jan 2026 20:54:09 +0200 Subject: [PATCH 2/2] Add CaptchaToken support to SignUp and related tests Updated the `SignUpWithEmail` method to support a new `CaptchaToken` property in `SignUpOptions`, enabling captcha verification during sign-up. Removed the `redirect_to` query parameter logic for cleaner implementation. Added the `CaptchaToken` property to the `SignUpOptions` class. Introduced a new test method, `SignUpUserWithCaptchaToken`, to validate the functionality of signing up with a captcha token. --- Gotrue/Api.cs | 25 +++++++++++++------------ Gotrue/SignUpOptions.cs | 6 ++++++ GotrueTests/AnonKeyClientTests.cs | 19 ++++++++++++++++++- 3 files changed, 37 insertions(+), 13 deletions(-) diff --git a/Gotrue/Api.cs b/Gotrue/Api.cs index 76e14ce0..d81ddb27 100644 --- a/Gotrue/Api.cs +++ b/Gotrue/Api.cs @@ -68,20 +68,21 @@ public Api(string url, Dictionary? headers = null) var body = new Dictionary { { "email", email }, { "password", password } }; var endpoint = $"{Url}/signup"; - if (options != null) - { - if (!string.IsNullOrEmpty(options.RedirectTo)) - { - endpoint = Helpers.AddQueryParams(endpoint, new Dictionary { { "redirect_to", options.RedirectTo! } }).ToString(); - } - if (options.Data != null) - { - body.Add("data", options.Data); - } - } + if (options != null) + { + if (options.Data != null) + { + body.Add("data", options.Data); + } + if (!string.IsNullOrEmpty(options.CaptchaToken)) + { + body.Add("captcha_token", options.CaptchaToken); + } + } + - var response = await Helpers.MakeRequest(HttpMethod.Post, endpoint, body, Headers); + var response = await Helpers.MakeRequest(HttpMethod.Post, endpoint, body, Headers); if (!string.IsNullOrEmpty(response.Content)) { diff --git a/Gotrue/SignUpOptions.cs b/Gotrue/SignUpOptions.cs index 75175224..2914d0f8 100644 --- a/Gotrue/SignUpOptions.cs +++ b/Gotrue/SignUpOptions.cs @@ -10,5 +10,11 @@ public class SignUpOptions : SignInOptions /// Optional user metadata. /// public Dictionary? Data { get; set; } + + + /// + /// Captcha token for verification when captcha is enabled + /// + public string? CaptchaToken { get; set; } } } diff --git a/GotrueTests/AnonKeyClientTests.cs b/GotrueTests/AnonKeyClientTests.cs index 02b35239..74b77cc2 100644 --- a/GotrueTests/AnonKeyClientTests.cs +++ b/GotrueTests/AnonKeyClientTests.cs @@ -123,7 +123,24 @@ public async Task SignUpUserPhone() AreEqual("Testing", session.User.UserMetadata["firstName"]); } - [TestMethod("Client: Triggers Token Refreshed Event")] + [TestMethod("Client: Sign Up with Captcha Token")] + public async Task SignUpUserWithCaptchaToken() + { + IsTrue(AuthStateIsEmpty()); + + var email = $"{RandomString(12)}@supabase.io"; + var options = new SignUpOptions + { + CaptchaToken = "test-captcha-token-12345" + }; + + var session = await _client.SignUp(email, PASSWORD, options); + + VerifyGoodSession(session); + } + + + [TestMethod("Client: Triggers Token Refreshed Event")] public async Task ClientTriggersTokenRefreshedEvent() { var tsc = new TaskCompletionSource();