From 6e2a2ddcccb8bebb9648e58d645dafe81c38e0b7 Mon Sep 17 00:00:00 2001 From: Bilal Aksoy Date: Mon, 2 Nov 2026 22:27:16 +0300 Subject: [PATCH 1/2] fix(auth,web): return signInSecondFactor from getIdTokenResult The JS SDK's IdTokenResult has signInSecondFactor, but the interop type did not declare it, so the web implementation always returned null. --- .../lib/src/interop/auth.dart | 4 ++ .../lib/src/interop/auth_interop.dart | 1 + .../lib/src/utils/web_utils.dart | 1 + .../convert_web_id_token_result_test.dart | 65 +++++++++++++++++++ 4 files changed, 71 insertions(+) create mode 100644 packages/firebase_auth/firebase_auth_web/test/convert_web_id_token_result_test.dart diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart index 1f04843b89a1..a6b25d189440 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth.dart @@ -325,6 +325,10 @@ class IdTokenResult extends JsObjectWrapper { /// Note, this does not map to provider IDs. String? get signInProvider => jsObject.signInProvider?.toDart; + /// The type of second factor associated with this session, provided the + /// user was multi-factor authenticated (for example, phone or totp). + String? get signInSecondFactor => jsObject.signInSecondFactor?.toDart; + /// The Firebase Auth ID token JWT string. String get token => jsObject.token.toDart; } diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart index 53fa6d346f2f..a7a945ea9f45 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/interop/auth_interop.dart @@ -298,6 +298,7 @@ extension type IdTokenResultImpl._(JSObject _) implements JSObject { external JSString get expirationTime; external JSString get issuedAtTime; external JSString? get signInProvider; + external JSString? get signInSecondFactor; external JSString get token; } diff --git a/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart b/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart index 24db8f844fbd..2f5f9ecf4811 100644 --- a/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart +++ b/packages/firebase_auth/firebase_auth_web/lib/src/utils/web_utils.dart @@ -231,6 +231,7 @@ IdTokenResult convertWebIdTokenResult( expirationTimestamp: webIdTokenResult.expirationTime.millisecondsSinceEpoch, signInProvider: webIdTokenResult.signInProvider, + signInSecondFactor: webIdTokenResult.signInSecondFactor, ), ); } diff --git a/packages/firebase_auth/firebase_auth_web/test/convert_web_id_token_result_test.dart b/packages/firebase_auth/firebase_auth_web/test/convert_web_id_token_result_test.dart new file mode 100644 index 000000000000..4b5698564e70 --- /dev/null +++ b/packages/firebase_auth/firebase_auth_web/test/convert_web_id_token_result_test.dart @@ -0,0 +1,65 @@ +// Copyright 2026 The Chromium Authors. All rights reserved. +// Use of this source code is governed by a BSD-style license that can be +// found in the LICENSE file. + +@TestOn('browser') +library; + +import 'dart:js_interop'; +import 'dart:js_interop_unsafe'; + +import 'package:firebase_auth_web/src/interop/auth.dart' as auth_interop; +import 'package:firebase_auth_web/src/interop/auth_interop.dart' + as auth_js_interop; +import 'package:firebase_auth_web/src/utils/web_utils.dart'; +import 'package:flutter_test/flutter_test.dart'; + +void main() { + // Shaped like the object the JS SDK's getIdTokenResult() resolves to. + JSObject buildJsIdTokenResult({String? signInSecondFactor}) { + final firebaseClaim = JSObject()..['sign_in_provider'] = 'password'.toJS; + if (signInSecondFactor != null) { + firebaseClaim['sign_in_second_factor'] = signInSecondFactor.toJS; + } + return JSObject() + ..['claims'] = (JSObject()..['firebase'] = firebaseClaim) + ..['token'] = 'test-token'.toJS + ..['authTime'] = 'Fri, 22 Sep 2017 01:49:58 GMT'.toJS + ..['issuedAtTime'] = 'Fri, 22 Sep 2017 01:49:58 GMT'.toJS + ..['expirationTime'] = 'Fri, 22 Sep 2017 02:49:58 GMT'.toJS + ..['signInProvider'] = 'password'.toJS + ..['signInSecondFactor'] = signInSecondFactor?.toJS; + } + + Future getIdTokenResult(JSObject jsResult) { + JSPromise fakeGetIdTokenResult(JSBoolean? forceRefresh) => + Future.value(jsResult).toJS; + final jsUser = JSObject()..['getIdTokenResult'] = fakeGetIdTokenResult.toJS; + final user = auth_interop.User.getInstance( + jsUser as auth_js_interop.UserJsImpl, + )!; + return user.getIdTokenResult(false); + } + + group('convertWebIdTokenResult', () { + test('passes signInSecondFactor through', () async { + final result = convertWebIdTokenResult( + await getIdTokenResult( + buildJsIdTokenResult(signInSecondFactor: 'totp'), + ), + ); + + expect(result.signInProvider, 'password'); + expect(result.signInSecondFactor, 'totp'); + }); + + test('returns null signInSecondFactor without a second factor', () async { + final result = convertWebIdTokenResult( + await getIdTokenResult(buildJsIdTokenResult()), + ); + + expect(result.signInProvider, 'password'); + expect(result.signInSecondFactor, isNull); + }); + }); +} From 230c1cae5771d7d422ad1dab5424cd96138435bb Mon Sep 17 00:00:00 2001 From: Bilal Aksoy Date: Mon, 2 Nov 2026 22:27:16 +0300 Subject: [PATCH 2/2] chore: add Bilal Aksoy to AUTHORS --- AUTHORS | 1 + 1 file changed, 1 insertion(+) diff --git a/AUTHORS b/AUTHORS index 0c0e6b091146..f986575bb935 100644 --- a/AUTHORS +++ b/AUTHORS @@ -71,3 +71,4 @@ kfurue Max Wojczuk Julian Pscheid Artem Shevchenko +Bilal Aksoy