From 2c0819c2709e1cf37643109f3d691252b03e55ba Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 15 Sep 2025 10:48:04 -0300 Subject: [PATCH 1/2] feat(gotrue): implement linkIdentityWithIdToken for OIDC support - Add linkIdentityWithIdToken method to link identities using ID tokens - Support OAuth providers with ID token verification - Include nonce and access token validation for enhanced security - Emit userUpdated event when identity is successfully linked Closes CLIBS-282 --- packages/gotrue/lib/src/gotrue_client.dart | 69 +++++++++++++++++++--- 1 file changed, 62 insertions(+), 7 deletions(-) diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index b229dcfb9..056854cc7 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -390,18 +390,20 @@ class GoTrueClient { String? nonce, String? captchaToken, }) async { + final body = { + 'provider': provider.snakeCase, + 'id_token': idToken, + 'nonce': nonce, + 'gotrue_meta_security': {'captcha_token': captchaToken}, + 'access_token': accessToken, + }; + final response = await _fetch.request( '$_url/token', RequestMethodType.post, options: GotrueRequestOptions( headers: _headers, - body: { - 'provider': provider.snakeCase, - 'id_token': idToken, - 'nonce': nonce, - 'gotrue_meta_security': {'captcha_token': captchaToken}, - 'access_token': accessToken, - }, + body: body, query: {'grant_type': 'id_token'}, ), ); @@ -902,6 +904,59 @@ class GoTrueClient { return res.user?.identities ?? []; } + /// Link an identity to the current user using an ID token. + /// + /// [provider] is the OAuth provider + /// + /// [idToken] is the ID token from the OAuth provider + /// + /// [accessToken] is the access token from the OAuth provider + /// + /// [nonce] is the nonce used for the OAuth flow + /// + /// [captchaToken] is the verification token received when the user + /// completes the captcha on the app. + Future linkIdentityWithIdToken({ + required OAuthProvider provider, + required String idToken, + String? accessToken, + String? nonce, + String? captchaToken, + }) async { + final body = { + 'provider': provider.snakeCase, + 'id_token': idToken, + 'nonce': nonce, + 'gotrue_meta_security': {'captcha_token': captchaToken}, + 'access_token': accessToken, + 'link_identity': true, + }; + + final response = await _fetch.request( + '$_url/token', + RequestMethodType.post, + options: GotrueRequestOptions( + headers: _headers, + jwt: _currentSession?.accessToken, + body: body, + query: {'grant_type': 'id_token'}, + ), + ); + + final authResponse = AuthResponse.fromJson(response); + + if (authResponse.session == null) { + throw AuthException( + 'An error occurred on token verification.', + ); + } + + _saveSession(authResponse.session!); + notifyAllSubscribers(AuthChangeEvent.userUpdated); + + return authResponse; + } + /// Returns the URL to link the user's identity with an OAuth provider. Future getLinkIdentityUrl( OAuthProvider provider, { From 394bc2999874b6bb0b4ccc09ea35a7e4bb5ebdee Mon Sep 17 00:00:00 2001 From: Guilherme Souza Date: Mon, 15 Sep 2025 10:56:50 -0300 Subject: [PATCH 2/2] pass body directly --- packages/gotrue/lib/src/gotrue_client.dart | 34 ++++++++++------------ 1 file changed, 15 insertions(+), 19 deletions(-) diff --git a/packages/gotrue/lib/src/gotrue_client.dart b/packages/gotrue/lib/src/gotrue_client.dart index 056854cc7..5c69bc135 100644 --- a/packages/gotrue/lib/src/gotrue_client.dart +++ b/packages/gotrue/lib/src/gotrue_client.dart @@ -390,20 +390,18 @@ class GoTrueClient { String? nonce, String? captchaToken, }) async { - final body = { - 'provider': provider.snakeCase, - 'id_token': idToken, - 'nonce': nonce, - 'gotrue_meta_security': {'captcha_token': captchaToken}, - 'access_token': accessToken, - }; - final response = await _fetch.request( '$_url/token', RequestMethodType.post, options: GotrueRequestOptions( headers: _headers, - body: body, + body: { + 'provider': provider.snakeCase, + 'id_token': idToken, + 'nonce': nonce, + 'gotrue_meta_security': {'captcha_token': captchaToken}, + 'access_token': accessToken, + }, query: {'grant_type': 'id_token'}, ), ); @@ -923,22 +921,20 @@ class GoTrueClient { String? nonce, String? captchaToken, }) async { - final body = { - 'provider': provider.snakeCase, - 'id_token': idToken, - 'nonce': nonce, - 'gotrue_meta_security': {'captcha_token': captchaToken}, - 'access_token': accessToken, - 'link_identity': true, - }; - final response = await _fetch.request( '$_url/token', RequestMethodType.post, options: GotrueRequestOptions( headers: _headers, jwt: _currentSession?.accessToken, - body: body, + body: { + 'provider': provider.snakeCase, + 'id_token': idToken, + 'nonce': nonce, + 'gotrue_meta_security': {'captcha_token': captchaToken}, + 'access_token': accessToken, + 'link_identity': true, + }, query: {'grant_type': 'id_token'}, ), );