@@ -24,6 +24,13 @@ import 'counter.dart';
2424///
2525/// [realtimeClientOptions] specifies different options you can pass to `RealtimeClient` .
2626///
27+ /// [accessToken] Optional function for using a third-party authentication system with Supabase.
28+ /// The function should return an access token or ID token (JWT) by obtaining
29+ /// it from the third-party auth client library. Note that this function may be
30+ /// called concurrently and many times. Use memoization and locking techniques
31+ /// if this is not supported by the client libraries. When set, the `auth`
32+ /// namespace of the Supabase client cannot be used.
33+ ///
2734/// Pass an instance of `YAJsonIsolate` to [isolate] to use your own persisted
2835/// isolate instance. A new instance will be created if [isolate] is omitted.
2936///
@@ -43,7 +50,7 @@ class SupabaseClient {
4350 final Client ? _httpClient;
4451 late final Client _authHttpClient;
4552
46- late final GoTrueClient auth ;
53+ late final GoTrueClient _authInstance ;
4754
4855 /// Supabase Functions allows you to deploy and invoke edge functions.
4956 late final FunctionsClient functions;
@@ -52,8 +59,9 @@ class SupabaseClient {
5259 late final SupabaseStorageClient storage;
5360 late final RealtimeClient realtime;
5461 late final PostgrestClient rest;
55- late StreamSubscription <AuthState > _authStateSubscription;
62+ StreamSubscription <AuthState >? _authStateSubscription;
5663 late final YAJsonIsolate _isolate;
64+ final Future <String > Function ()? accessToken;
5765
5866 /// Increment ID of the stream to create different realtime topic for each stream
5967 final _incrementId = Counter ();
@@ -83,13 +91,15 @@ class SupabaseClient {
8391 ..clear ()
8492 ..addAll (_headers);
8593
86- auth.headers
87- ..clear ()
88- ..addAll ({
89- ...Constants .defaultHeaders,
90- ..._getAuthHeaders (),
91- ...headers,
92- });
94+ if (accessToken == null ) {
95+ auth.headers
96+ ..clear ()
97+ ..addAll ({
98+ ...Constants .defaultHeaders,
99+ ..._getAuthHeaders (),
100+ ...headers,
101+ });
102+ }
93103
94104 // To apply the new headers in the realtime client,
95105 // manually unsubscribe and resubscribe to all channels.
@@ -106,6 +116,7 @@ class SupabaseClient {
106116 AuthClientOptions authOptions = const AuthClientOptions (),
107117 StorageClientOptions storageOptions = const StorageClientOptions (),
108118 RealtimeClientOptions realtimeClientOptions = const RealtimeClientOptions (),
119+ this .accessToken,
109120 Map <String , String >? headers,
110121 Client ? httpClient,
111122 YAJsonIsolate ? isolate,
@@ -122,18 +133,30 @@ class SupabaseClient {
122133 },
123134 _httpClient = httpClient,
124135 _isolate = isolate ?? (YAJsonIsolate ()..initialize ()) {
125- auth = _initSupabaseAuthClient (
136+ _authInstance = _initSupabaseAuthClient (
126137 autoRefreshToken: authOptions.autoRefreshToken,
127138 gotrueAsyncStorage: authOptions.pkceAsyncStorage,
128139 authFlowType: authOptions.authFlowType,
129140 );
130141 _authHttpClient =
131- AuthHttpClient (_supabaseKey, httpClient ?? Client (), auth );
142+ AuthHttpClient (_supabaseKey, httpClient ?? Client (), _getAccessToken );
132143 rest = _initRestClient ();
133144 functions = _initFunctionsClient ();
134145 storage = _initStorageClient (storageOptions.retryAttempts);
135146 realtime = _initRealtimeClient (options: realtimeClientOptions);
136- _listenForAuthEvents ();
147+ if (accessToken == null ) {
148+ _listenForAuthEvents ();
149+ }
150+ }
151+
152+ GoTrueClient get auth {
153+ if (accessToken == null ) {
154+ return _authInstance;
155+ } else {
156+ throw AuthException (
157+ 'Supabase Client is configured with the accessToken option, accessing supabase.auth is not possible.' ,
158+ );
159+ }
137160 }
138161
139162 /// Perform a table operation.
@@ -200,8 +223,32 @@ class SupabaseClient {
200223 return realtime.removeAllChannels ();
201224 }
202225
226+ Future <String ?> _getAccessToken () async {
227+ if (accessToken != null ) {
228+ return await accessToken !();
229+ }
230+
231+ if (_authInstance.currentSession? .isExpired ?? false ) {
232+ try {
233+ await _authInstance.refreshSession ();
234+ } catch (error) {
235+ final expiresAt = _authInstance.currentSession? .expiresAt;
236+ if (expiresAt != null ) {
237+ // Failed to refresh the token.
238+ final isExpiredWithoutMargin = DateTime .now ()
239+ .isAfter (DateTime .fromMillisecondsSinceEpoch (expiresAt * 1000 ));
240+ if (isExpiredWithoutMargin) {
241+ // Throw the error instead of making an API request with an expired token.
242+ rethrow ;
243+ }
244+ }
245+ }
246+ }
247+ return _authInstance.currentSession? .accessToken;
248+ }
249+
203250 Future <void > dispose () async {
204- await _authStateSubscription.cancel ();
251+ await _authStateSubscription? .cancel ();
205252 await _isolate.dispose ();
206253 }
207254
0 commit comments