@@ -24,6 +24,13 @@ import 'counter.dart';
24
24
///
25
25
/// [realtimeClientOptions] specifies different options you can pass to `RealtimeClient` .
26
26
///
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
+ ///
27
34
/// Pass an instance of `YAJsonIsolate` to [isolate] to use your own persisted
28
35
/// isolate instance. A new instance will be created if [isolate] is omitted.
29
36
///
@@ -43,7 +50,7 @@ class SupabaseClient {
43
50
final Client ? _httpClient;
44
51
late final Client _authHttpClient;
45
52
46
- late final GoTrueClient auth ;
53
+ late final GoTrueClient _authInstance ;
47
54
48
55
/// Supabase Functions allows you to deploy and invoke edge functions.
49
56
late final FunctionsClient functions;
@@ -52,8 +59,9 @@ class SupabaseClient {
52
59
late final SupabaseStorageClient storage;
53
60
late final RealtimeClient realtime;
54
61
late final PostgrestClient rest;
55
- late StreamSubscription <AuthState > _authStateSubscription;
62
+ StreamSubscription <AuthState >? _authStateSubscription;
56
63
late final YAJsonIsolate _isolate;
64
+ final Future <String > Function ()? accessToken;
57
65
58
66
/// Increment ID of the stream to create different realtime topic for each stream
59
67
final _incrementId = Counter ();
@@ -83,13 +91,15 @@ class SupabaseClient {
83
91
..clear ()
84
92
..addAll (_headers);
85
93
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
+ }
93
103
94
104
// To apply the new headers in the realtime client,
95
105
// manually unsubscribe and resubscribe to all channels.
@@ -106,6 +116,7 @@ class SupabaseClient {
106
116
AuthClientOptions authOptions = const AuthClientOptions (),
107
117
StorageClientOptions storageOptions = const StorageClientOptions (),
108
118
RealtimeClientOptions realtimeClientOptions = const RealtimeClientOptions (),
119
+ this .accessToken,
109
120
Map <String , String >? headers,
110
121
Client ? httpClient,
111
122
YAJsonIsolate ? isolate,
@@ -122,18 +133,30 @@ class SupabaseClient {
122
133
},
123
134
_httpClient = httpClient,
124
135
_isolate = isolate ?? (YAJsonIsolate ()..initialize ()) {
125
- auth = _initSupabaseAuthClient (
136
+ _authInstance = _initSupabaseAuthClient (
126
137
autoRefreshToken: authOptions.autoRefreshToken,
127
138
gotrueAsyncStorage: authOptions.pkceAsyncStorage,
128
139
authFlowType: authOptions.authFlowType,
129
140
);
130
141
_authHttpClient =
131
- AuthHttpClient (_supabaseKey, httpClient ?? Client (), auth );
142
+ AuthHttpClient (_supabaseKey, httpClient ?? Client (), _getAccessToken );
132
143
rest = _initRestClient ();
133
144
functions = _initFunctionsClient ();
134
145
storage = _initStorageClient (storageOptions.retryAttempts);
135
146
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
+ }
137
160
}
138
161
139
162
/// Perform a table operation.
@@ -200,8 +223,32 @@ class SupabaseClient {
200
223
return realtime.removeAllChannels ();
201
224
}
202
225
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
+
203
250
Future <void > dispose () async {
204
- await _authStateSubscription.cancel ();
251
+ await _authStateSubscription? .cancel ();
205
252
await _isolate.dispose ();
206
253
}
207
254
0 commit comments