authWithOAuth2 method
Authenticate a single auth collection record with OAuth2 without custom redirects, deeplinks or even page reload.
This method initializes a one-off realtime subscription and will
call urlCallback
with the OAuth2 vendor url to authenticate.
Once the external OAuth2 sign-in/sign-up flow is completed, the popup
window will be automatically closed and the OAuth2 data sent back
to the user through the previously established realtime connection.
On success this method automatically updates the client's AuthStore.
Example:
await pb.collection('users').authWithOAuth2('google', (url) async {
await launchUrl(url);
});
Site-note: when creating the OAuth2 app in the provider dashboard
you have to configure https://yourdomain.com/api/oauth2-redirect
as redirect URL.
Implementation
Future<RecordAuth> authWithOAuth2(
String providerName,
OAuth2UrlCallbackFunc urlCallback, {
List<String> scopes = const [],
Map<String, dynamic> createData = const {},
String? expand,
String? fields,
}) async {
final authMethods = await listAuthMethods();
final AuthMethodProvider provider;
try {
provider =
authMethods.authProviders.firstWhere((p) => p.name == providerName);
} catch (_) {
throw ClientException(
originalError: Exception("missing provider $providerName"),
);
}
final redirectUrl = client.buildUrl("/api/oauth2-redirect");
final completer = Completer<RecordAuth>();
Future<void> Function()? unsubscribeFunc;
try {
unsubscribeFunc = await client.realtime.subscribe("@oauth2", (e) async {
final oldState = client.realtime.clientId;
try {
final eventData = e.jsonData();
final code = eventData["code"] as String? ?? "";
final state = eventData["state"] as String? ?? "";
final error = eventData["error"] as String? ?? "";
if (state.isEmpty || state != oldState) {
throw StateError("State parameters don't match.");
}
if (error.isNotEmpty || code.isEmpty) {
throw StateError("OAuth2 redirect error or missing code.");
}
final auth = await authWithOAuth2Code(
provider.name,
code,
provider.codeVerifier,
redirectUrl.toString(),
createData: createData,
expand: expand,
fields: fields,
);
completer.complete(auth);
if (unsubscribeFunc != null) {
unawaited(unsubscribeFunc());
}
} catch (err) {
if (err is ClientException) {
completer.completeError(err);
} else {
completer.completeError(ClientException(originalError: err));
}
}
});
final authUrl = Uri.parse(provider.authUrl + redirectUrl.toString());
final queryParameters = Map<String, String>.of(authUrl.queryParameters);
queryParameters["state"] = client.realtime.clientId;
// set custom scopes (if any)
if (scopes.isNotEmpty) {
queryParameters["scope"] = scopes.join(" ");
}
urlCallback(authUrl.replace(queryParameters: queryParameters));
} catch (err) {
if (err is ClientException) {
completer.completeError(err);
} else {
completer.completeError(ClientException(originalError: err));
}
}
return completer.future;
}