Skip to content

Commit deb90f6

Browse files
authored
Various minor SSO fixes and readme updates (#95)
* Fix the following: 1. Update readme for instructions on implementing SSO, 2: Fix some missing enum values ported from supabase auth repo, 3: Add some example persistence code * Cleanup ClientPersistence example file
1 parent 020c3b2 commit deb90f6

File tree

7 files changed

+125
-11
lines changed

7 files changed

+125
-11
lines changed

Gotrue/Constants.cs

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ public enum EmailOtpType
6262
/// </summary>
6363
public enum Provider
6464
{
65+
[MapTo("anonymous_users")]
66+
AnonymousUsers,
6567
[MapTo("apple")]
6668
Apple,
6769
[MapTo("azure")]
@@ -149,4 +151,4 @@ public enum SignUpType
149151
Phone
150152
}
151153
}
152-
}
154+
}

Gotrue/Settings.cs

Lines changed: 8 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -21,10 +21,14 @@ public class Settings
2121
[JsonProperty("sms_provider")]
2222
public string? SmsProvider { get; set; }
2323

24-
[JsonProperty("external")]
25-
public Dictionary<string, bool>? Services { get; set; }
24+
[JsonProperty("mfa_enabled")]
25+
public bool? MFAEnabled { get; set; }
26+
27+
// SAML = SSO enabled
28+
[JsonProperty("saml_enabled")]
29+
public bool? SAMLEnabled { get; set; }
2630

27-
[JsonProperty("external_labels")]
28-
public Dictionary<string, string>? Labels { get; set; }
31+
[JsonProperty("external")]
32+
public Dictionary<string, bool>? ExternalProviders { get; set; }
2933
}
3034
}

Gotrue/SignInAnonymouslyOptions.cs

Lines changed: 4 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,9 @@
44

55
namespace Supabase.Gotrue
66
{
7+
/// <summary>
8+
/// Options for handling signing in anonymously
9+
/// </summary>
710
public class SignInAnonymouslyOptions
811
{
912
/// <summary>
@@ -20,4 +23,4 @@ public class SignInAnonymouslyOptions
2023
[JsonProperty("captchaToken")]
2124
public string? CaptchaToken { get; set; }
2225
}
23-
}
26+
}

GotrueExample/ClientPersistence.cs

Lines changed: 52 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,52 @@
1+
#nullable enable
2+
3+
using Microsoft.Extensions.Logging;
4+
using Supabase.Gotrue;
5+
using Supabase.Gotrue.Interfaces;
6+
7+
namespace GotrueExample;
8+
9+
/// <summary>
10+
/// This is an example of how to handle session persistence using the helpers and interfaces provided
11+
///
12+
/// We typically would load the session from storage here if we want to persist things to disk
13+
/// between application restarts. Depending on your use case you will need to implement SaveSession and LoadSession differently
14+
/// to handle loading and saving the session in your applications cache
15+
/// </summary>
16+
/// <example>
17+
/// Usage using dependency injection
18+
/// <code>
19+
/// services.AddSingleton<IGotrueSessionPersistence<Session>, ClientPersistence>();
20+
/// var provider = Host.Run();
21+
/// var client = provider.GetRequiredService<IGotrueClient<User, Session>>();
22+
/// var sessionPersistence = provider.GetRequiredService<IGotrueSessionPersistence<Session>>();
23+
/// client.SetPersistence(sessionPersistence);
24+
/// </code>
25+
/// </example>
26+
public class ClientPersistence : IGotrueSessionPersistence<Session>
27+
{
28+
private Session? _session;
29+
private ILogger<ClientPersistence> Logger { get; }
30+
31+
public ClientPersistence(ILogger<ClientPersistence> logger)
32+
{
33+
Logger = logger;
34+
IGotruePersistenceListener<Session> persistenceListener = new PersistenceListener(this);
35+
persistenceListener.Persistence.LoadSession();
36+
}
37+
38+
public void SaveSession(Session session)
39+
{
40+
_session = session;
41+
}
42+
43+
public void DestroySession()
44+
{
45+
_session = null;
46+
}
47+
48+
public Session? LoadSession()
49+
{
50+
return _session;
51+
}
52+
}

GotrueExample/Program.cs

Lines changed: 10 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -8,13 +8,18 @@
88

99
namespace GotrueExample
1010
{
11-
internal class Program
11+
internal static class Program
1212
{
13-
private static Random random = new Random();
13+
private static Random random = new();
1414
static void Main(string[] args)
1515
{
1616
using IHost host = Host.CreateDefaultBuilder(args)
17-
.ConfigureServices((_, services) => services.AddSingleton<IGotrueClient<User, Session>, Client>())
17+
.ConfigureServices((_, services) =>
18+
{
19+
services.AddSingleton<IGotrueClient<User, Session>, Client>();
20+
services.AddSingleton<IGotrueSessionPersistence<Session>, ClientPersistence>();
21+
services.AddLogging();
22+
})
1823
.Build();
1924

2025
UseClient(host.Services);
@@ -28,6 +33,8 @@ static async void UseClient(IServiceProvider services)
2833
IServiceProvider provider = serviceScope.ServiceProvider;
2934

3035
var client = provider.GetRequiredService<IGotrueClient<User, Session>>();
36+
var sessionPersistence = provider.GetRequiredService<IGotrueSessionPersistence<Session>>();
37+
client.SetPersistence(sessionPersistence);
3138

3239
Session session = null;
3340
var email = $"{RandomString(12)}@supabase.io";

GotrueTests/StatelessClientTests.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -76,8 +76,8 @@ public async Task Settings()
7676
{
7777
var settings = await _client.Settings(Options);
7878
Assert.IsNotNull(settings);
79-
Assert.IsFalse(settings.Services["zoom"]);
80-
Assert.IsTrue(settings.Services["email"]);
79+
Assert.IsFalse(settings.ExternalProviders["zoom"]);
80+
Assert.IsTrue(settings.ExternalProviders["email"]);
8181
Assert.IsFalse(settings.DisableSignup);
8282
Assert.IsTrue(settings.MailerAutoConfirm);
8383
Assert.IsTrue(settings.PhoneAutoConfirm);

README.md

Lines changed: 46 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -227,6 +227,52 @@ var state = await client.SignIn(Constants.Provider.Github, new SignInOptions
227227
var session = await client.ExchangeCodeForSession(state.PKCEVerifier, RETRIEVE_CODE_FROM_GET_PARAMS);
228228
```
229229

230+
## Sign In With Single Sign On (SSO)
231+
Single Sign On (SSO) is an enterprise level authentication protocol that allows a single enterprise account to
232+
access many apps at once. A few examples of supported SSO providers are Okta, Microsoft Entra and Google Workspaces
233+
234+
If not already done so, you must first add an SSO provider to your supabase project via the supabase CLI.
235+
See the following [link](https://supabase.com/docs/guides/auth/enterprise-sso/auth-sso-saml) for more info on how to
236+
configure SSO providers
237+
238+
The flow functions similar to the OAuth flow and supports many of the same parameters. Under the hood
239+
the flow is handled quite differently by the GoTrue server but the client is agnostic to the difference in
240+
implementations and session info is handled in the same way as the OAuth flow
241+
242+
General auth flow is as follows:
243+
244+
1. Request initiated by calling `SignInWithSSO`
245+
1. The `RedirectTo` attribute is recommended for handling the callback and converting to a session
246+
1. `ssoResposne` contains the providers login Uri, navigate to this
247+
1. User logs in with provider (Okta/Auth0, Microsoft Entra, Google Workspaces ect...)
248+
1. Supabase GoTrue server handles SAML exchange for us
249+
1. Supabase GoTrue server generates session info and appends it to the callback (RedirectedTo) url
250+
1. We can then use either `ExchangeCodeForSession(code)` or `GetSessionFromUrl(callbackUri)`
251+
252+
```csharp
253+
using Constants = Supabase.Gotrue.Constants;
254+
255+
var ssoResponse = await client.SignInWithSSO("supabase.io", new SignInWithSSOOptions
256+
{
257+
RedirectTo = "https://localhost:3000/welcome"
258+
});
259+
260+
// Handle login via ssoResponse.Uri
261+
//
262+
// When the user logs in using the Uri from the ssoResponse,
263+
// they will be redirected to the RedirectTo
264+
265+
// In callback received from Supabase returning to RedirectTo (set above)
266+
// Url is set as: http://REDIRECT_TO_URL?access_token=foobar&expires_at=123...
267+
var session = await client.GetSessionFromUrl(url);
268+
```
269+
270+
For handling session persistence its recommended using a session persistence layer, take a look at
271+
the following [example](GotrueExample/SupabaseClientPersistence.cs)
272+
273+
For additional info on how the GoTrue server handles SSO requests see
274+
[here](https://github.com/supabase/auth/blob/55409f797bea55068a3fafdddd6cfdb78feba1b4/internal/api/samlacs.go#L315-L316)
275+
and [here](https://github.com/supabase/auth/blob/55409f797bea55068a3fafdddd6cfdb78feba1b4/internal/api/token.go#L54-L55)
230276
## Troubleshooting
231277

232278
**Q: I've created a User but while attempting to log in it throws an exception:**

0 commit comments

Comments
 (0)