Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions docs/auth/start.md
Original file line number Diff line number Diff line change
Expand Up @@ -223,6 +223,35 @@ final auth = FirebaseAuth.instanceFor(app: Firebase.app(), persistence: Persiste
await auth.setPersistence(Persistence.LOCAL);
```

## Share authentication state between Apple apps

On Apple platforms, you can share authentication state between apps in the same
developer account by storing it in a shared Keychain access group. First, enable
the Keychain Sharing capability for each app and add the same access group.
Then configure Firebase Auth with the fully qualified access group:

```dart
await FirebaseAuth.instance.setSettings(
userAccessGroup: 'TEAMID.com.example.group1',
);
```

Changing from the default Keychain to a shared access group signs out an
existing user unless the user is migrated. To preserve the current session,
including an anonymous session, opt into migration when setting the access
group:

```dart
await FirebaseAuth.instance.setSettings(
userAccessGroup: 'TEAMID.com.example.group1',
migrateCurrentUser: true,
);
```

Migration can replace a user already stored in the destination access group.
For more information about configuring access groups, see
[Enabling cross-app authentication with shared Apple Keychain](https://firebase.google.com/docs/auth/ios/single-sign-on).

## Next Steps

Explore the guides on signing in and signing up users with the supported
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -2128,6 +2128,19 @@ public void setUserAccessGroup(@Nullable String setterArg) {
this.userAccessGroup = setterArg;
}

private @NonNull Boolean migrateCurrentUser;

public @NonNull Boolean getMigrateCurrentUser() {
return migrateCurrentUser;
}

public void setMigrateCurrentUser(@NonNull Boolean setterArg) {
if (setterArg == null) {
throw new IllegalStateException("Nonnull field \"migrateCurrentUser\" is null.");
}
this.migrateCurrentUser = setterArg;
}

private @Nullable String phoneNumber;

public @Nullable String getPhoneNumber() {
Expand Down Expand Up @@ -2173,6 +2186,7 @@ public boolean equals(Object o) {
return pigeonDeepEquals(
appVerificationDisabledForTesting, that.appVerificationDisabledForTesting)
&& pigeonDeepEquals(userAccessGroup, that.userAccessGroup)
&& pigeonDeepEquals(migrateCurrentUser, that.migrateCurrentUser)
&& pigeonDeepEquals(phoneNumber, that.phoneNumber)
&& pigeonDeepEquals(smsCode, that.smsCode)
&& pigeonDeepEquals(forceRecaptchaFlow, that.forceRecaptchaFlow);
Expand All @@ -2185,6 +2199,7 @@ public int hashCode() {
getClass(),
appVerificationDisabledForTesting,
userAccessGroup,
migrateCurrentUser,
phoneNumber,
smsCode,
forceRecaptchaFlow
Expand All @@ -2210,6 +2225,14 @@ public static final class Builder {
return this;
}

private @Nullable Boolean migrateCurrentUser;

@CanIgnoreReturnValue
public @NonNull Builder setMigrateCurrentUser(@NonNull Boolean setterArg) {
this.migrateCurrentUser = setterArg;
return this;
}

private @Nullable String phoneNumber;

@CanIgnoreReturnValue
Expand Down Expand Up @@ -2238,6 +2261,7 @@ public static final class Builder {
InternalFirebaseAuthSettings pigeonReturn = new InternalFirebaseAuthSettings();
pigeonReturn.setAppVerificationDisabledForTesting(appVerificationDisabledForTesting);
pigeonReturn.setUserAccessGroup(userAccessGroup);
pigeonReturn.setMigrateCurrentUser(migrateCurrentUser);
pigeonReturn.setPhoneNumber(phoneNumber);
pigeonReturn.setSmsCode(smsCode);
pigeonReturn.setForceRecaptchaFlow(forceRecaptchaFlow);
Expand All @@ -2247,9 +2271,10 @@ public static final class Builder {

@NonNull
public ArrayList<Object> toList() {
ArrayList<Object> toListResult = new ArrayList<>(5);
ArrayList<Object> toListResult = new ArrayList<>(6);
toListResult.add(appVerificationDisabledForTesting);
toListResult.add(userAccessGroup);
toListResult.add(migrateCurrentUser);
toListResult.add(phoneNumber);
toListResult.add(smsCode);
toListResult.add(forceRecaptchaFlow);
Expand All @@ -2264,11 +2289,13 @@ public ArrayList<Object> toList() {
(Boolean) appVerificationDisabledForTesting);
Object userAccessGroup = pigeonVar_list.get(1);
pigeonResult.setUserAccessGroup((String) userAccessGroup);
Object phoneNumber = pigeonVar_list.get(2);
Object migrateCurrentUser = pigeonVar_list.get(2);
pigeonResult.setMigrateCurrentUser((Boolean) migrateCurrentUser);
Object phoneNumber = pigeonVar_list.get(3);
pigeonResult.setPhoneNumber((String) phoneNumber);
Object smsCode = pigeonVar_list.get(3);
Object smsCode = pigeonVar_list.get(4);
pigeonResult.setSmsCode((String) smsCode);
Object forceRecaptchaFlow = pigeonVar_list.get(4);
Object forceRecaptchaFlow = pigeonVar_list.get(5);
pigeonResult.setForceRecaptchaFlow((Boolean) forceRecaptchaFlow);
return pigeonResult;
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1533,6 +1533,7 @@ - (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app
settings:(nonnull InternalFirebaseAuthSettings *)settings
completion:(nonnull void (^)(FlutterError *_Nullable))completion {
FIRAuth *auth = [self getFIRAuthFromAppNameFromPigeon:app];
FIRUser *userToMigrate = settings.migrateCurrentUser ? auth.currentUser : nil;

if (settings.userAccessGroup != nil) {
BOOL useUserAccessGroupSuccessful;
Expand All @@ -1554,6 +1555,15 @@ - (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app
@"on MacOS.");
#endif

if (userToMigrate != nil) {
[auth updateCurrentUser:userToMigrate
completion:^(NSError *_Nullable error) {
completion(error == nil ? nil
: [FLTFirebaseAuthPlugin convertToFlutterError:error]);
}];
return;
}

completion(nil);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -969,12 +969,14 @@ - (NSUInteger)hash {
@implementation InternalFirebaseAuthSettings
+ (instancetype)makeWithAppVerificationDisabledForTesting:(BOOL)appVerificationDisabledForTesting
userAccessGroup:(nullable NSString *)userAccessGroup
migrateCurrentUser:(BOOL)migrateCurrentUser
phoneNumber:(nullable NSString *)phoneNumber
smsCode:(nullable NSString *)smsCode
forceRecaptchaFlow:(nullable NSNumber *)forceRecaptchaFlow {
InternalFirebaseAuthSettings *pigeonResult = [[InternalFirebaseAuthSettings alloc] init];
pigeonResult.appVerificationDisabledForTesting = appVerificationDisabledForTesting;
pigeonResult.userAccessGroup = userAccessGroup;
pigeonResult.migrateCurrentUser = migrateCurrentUser;
pigeonResult.phoneNumber = phoneNumber;
pigeonResult.smsCode = smsCode;
pigeonResult.forceRecaptchaFlow = forceRecaptchaFlow;
Expand All @@ -984,9 +986,10 @@ + (InternalFirebaseAuthSettings *)fromList:(NSArray<id> *)list {
InternalFirebaseAuthSettings *pigeonResult = [[InternalFirebaseAuthSettings alloc] init];
pigeonResult.appVerificationDisabledForTesting = [GetNullableObjectAtIndex(list, 0) boolValue];
pigeonResult.userAccessGroup = GetNullableObjectAtIndex(list, 1);
pigeonResult.phoneNumber = GetNullableObjectAtIndex(list, 2);
pigeonResult.smsCode = GetNullableObjectAtIndex(list, 3);
pigeonResult.forceRecaptchaFlow = GetNullableObjectAtIndex(list, 4);
pigeonResult.migrateCurrentUser = [GetNullableObjectAtIndex(list, 2) boolValue];
pigeonResult.phoneNumber = GetNullableObjectAtIndex(list, 3);
pigeonResult.smsCode = GetNullableObjectAtIndex(list, 4);
pigeonResult.forceRecaptchaFlow = GetNullableObjectAtIndex(list, 5);
return pigeonResult;
}
+ (nullable InternalFirebaseAuthSettings *)nullableFromList:(NSArray<id> *)list {
Expand All @@ -996,6 +999,7 @@ + (nullable InternalFirebaseAuthSettings *)nullableFromList:(NSArray<id> *)list
return @[
@(self.appVerificationDisabledForTesting),
self.userAccessGroup ?: [NSNull null],
@(self.migrateCurrentUser),
self.phoneNumber ?: [NSNull null],
self.smsCode ?: [NSNull null],
self.forceRecaptchaFlow ?: [NSNull null],
Expand All @@ -1011,6 +1015,7 @@ - (BOOL)isEqual:(id)object {
InternalFirebaseAuthSettings *other = (InternalFirebaseAuthSettings *)object;
return self.appVerificationDisabledForTesting == other.appVerificationDisabledForTesting &&
FLTPigeonDeepEquals(self.userAccessGroup, other.userAccessGroup) &&
self.migrateCurrentUser == other.migrateCurrentUser &&
FLTPigeonDeepEquals(self.phoneNumber, other.phoneNumber) &&
FLTPigeonDeepEquals(self.smsCode, other.smsCode) &&
FLTPigeonDeepEquals(self.forceRecaptchaFlow, other.forceRecaptchaFlow);
Expand All @@ -1020,6 +1025,7 @@ - (NSUInteger)hash {
NSUInteger result = [self class].hash;
result = result * 31 + @(self.appVerificationDisabledForTesting).hash;
result = result * 31 + FLTPigeonDeepHash(self.userAccessGroup);
result = result * 31 + @(self.migrateCurrentUser).hash;
result = result * 31 + FLTPigeonDeepHash(self.phoneNumber);
result = result * 31 + FLTPigeonDeepHash(self.smsCode);
result = result * 31 + FLTPigeonDeepHash(self.forceRecaptchaFlow);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -230,11 +230,13 @@ typedef NS_ENUM(NSUInteger, ActionCodeInfoOperation) {
- (instancetype)init NS_UNAVAILABLE;
+ (instancetype)makeWithAppVerificationDisabledForTesting:(BOOL)appVerificationDisabledForTesting
userAccessGroup:(nullable NSString *)userAccessGroup
migrateCurrentUser:(BOOL)migrateCurrentUser
phoneNumber:(nullable NSString *)phoneNumber
smsCode:(nullable NSString *)smsCode
forceRecaptchaFlow:(nullable NSNumber *)forceRecaptchaFlow;
@property(nonatomic, assign) BOOL appVerificationDisabledForTesting;
@property(nonatomic, copy, nullable) NSString *userAccessGroup;
@property(nonatomic, assign) BOOL migrateCurrentUser;
@property(nonatomic, copy, nullable) NSString *phoneNumber;
@property(nonatomic, copy, nullable) NSString *smsCode;
@property(nonatomic, strong, nullable) NSNumber *forceRecaptchaFlow;
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -397,16 +397,23 @@ class FirebaseAuth extends FirebasePlugin implements FirebaseService {
/// Key Sharing capabilities must be enabled for your app via XCode (Project
/// settings > Capabilities). To learn more, visit the
/// [Apple documentation](https://developer.apple.com/documentation/security/keychain_services/keychain_items/sharing_access_to_keychain_items_among_a_collection_of_apps).
///
/// [migrateCurrentUser] This setting only applies to iOS and macOS platforms.
/// When `true`, the currently signed-in user is migrated to
/// [userAccessGroup]. This may replace a user already stored in that access
/// group.
Future<void> setSettings({
bool appVerificationDisabledForTesting = false,
String? userAccessGroup,
bool migrateCurrentUser = false,
String? phoneNumber,
String? smsCode,
bool? forceRecaptchaFlow,
}) {
return _delegate.setSettings(
appVerificationDisabledForTesting: appVerificationDisabledForTesting,
userAccessGroup: userAccessGroup,
migrateCurrentUser: migrateCurrentUser,
phoneNumber: phoneNumber,
smsCode: smsCode,
forceRecaptchaFlow: forceRecaptchaFlow,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -506,20 +506,23 @@ void main() {
smsCode: any,
forceRecaptchaFlow: any,
userAccessGroup: any,
migrateCurrentUser: true,
)).thenAnswer((i) async {});

String phoneNumber = '123456';
String smsCode = '1234';
bool forceRecaptchaFlow = true;
bool appVerificationDisabledForTesting = true;
String userAccessGroup = 'group-id';
bool migrateCurrentUser = true;

await auth.setSettings(
appVerificationDisabledForTesting: appVerificationDisabledForTesting,
phoneNumber: phoneNumber,
smsCode: smsCode,
forceRecaptchaFlow: forceRecaptchaFlow,
userAccessGroup: userAccessGroup,
migrateCurrentUser: migrateCurrentUser,
);

verify(
Expand All @@ -530,6 +533,7 @@ void main() {
smsCode: smsCode,
forceRecaptchaFlow: forceRecaptchaFlow,
userAccessGroup: userAccessGroup,
migrateCurrentUser: migrateCurrentUser,
),
);
});
Expand Down Expand Up @@ -1122,6 +1126,7 @@ class MockFirebaseAuth extends Mock
Future<void> setSettings({
bool? appVerificationDisabledForTesting,
String? userAccessGroup,
bool migrateCurrentUser = false,
String? phoneNumber,
String? smsCode,
bool? forceRecaptchaFlow,
Expand All @@ -1130,6 +1135,7 @@ class MockFirebaseAuth extends Mock
Invocation.method(#setSettings, [
appVerificationDisabledForTesting,
userAccessGroup,
migrateCurrentUser,
phoneNumber,
smsCode,
forceRecaptchaFlow,
Expand Down
34 changes: 25 additions & 9 deletions packages/firebase_auth/firebase_auth/windows/messages.g.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1821,19 +1821,22 @@ size_t PigeonInternalDeepHash(const InternalActionCodeSettings& v) {
// InternalFirebaseAuthSettings

InternalFirebaseAuthSettings::InternalFirebaseAuthSettings(
bool app_verification_disabled_for_testing)
bool app_verification_disabled_for_testing, bool migrate_current_user)
: app_verification_disabled_for_testing_(
app_verification_disabled_for_testing) {}
app_verification_disabled_for_testing),
migrate_current_user_(migrate_current_user) {}

InternalFirebaseAuthSettings::InternalFirebaseAuthSettings(
bool app_verification_disabled_for_testing,
const std::string* user_access_group, const std::string* phone_number,
const std::string* sms_code, const bool* force_recaptcha_flow)
const std::string* user_access_group, bool migrate_current_user,
const std::string* phone_number, const std::string* sms_code,
const bool* force_recaptcha_flow)
: app_verification_disabled_for_testing_(
app_verification_disabled_for_testing),
user_access_group_(user_access_group
? std::optional<std::string>(*user_access_group)
: std::nullopt),
migrate_current_user_(migrate_current_user),
phone_number_(phone_number ? std::optional<std::string>(*phone_number)
: std::nullopt),
sms_code_(sms_code ? std::optional<std::string>(*sms_code)
Expand Down Expand Up @@ -1867,6 +1870,14 @@ void InternalFirebaseAuthSettings::set_user_access_group(
user_access_group_ = value_arg;
}

bool InternalFirebaseAuthSettings::migrate_current_user() const {
return migrate_current_user_;
}

void InternalFirebaseAuthSettings::set_migrate_current_user(bool value_arg) {
migrate_current_user_ = value_arg;
}

const std::string* InternalFirebaseAuthSettings::phone_number() const {
return phone_number_ ? &(*phone_number_) : nullptr;
}
Expand Down Expand Up @@ -1911,10 +1922,11 @@ void InternalFirebaseAuthSettings::set_force_recaptcha_flow(bool value_arg) {

EncodableList InternalFirebaseAuthSettings::ToEncodableList() const {
EncodableList list;
list.reserve(5);
list.reserve(6);
list.push_back(EncodableValue(app_verification_disabled_for_testing_));
list.push_back(user_access_group_ ? EncodableValue(*user_access_group_)
: EncodableValue());
list.push_back(EncodableValue(migrate_current_user_));
list.push_back(phone_number_ ? EncodableValue(*phone_number_)
: EncodableValue());
list.push_back(sms_code_ ? EncodableValue(*sms_code_) : EncodableValue());
Expand All @@ -1925,21 +1937,22 @@ EncodableList InternalFirebaseAuthSettings::ToEncodableList() const {

InternalFirebaseAuthSettings InternalFirebaseAuthSettings::FromEncodableList(
const EncodableList& list) {
InternalFirebaseAuthSettings decoded(std::get<bool>(list[0]));
InternalFirebaseAuthSettings decoded(std::get<bool>(list[0]),
std::get<bool>(list[2]));
auto& encodable_user_access_group = list[1];
if (!encodable_user_access_group.IsNull()) {
decoded.set_user_access_group(
std::get<std::string>(encodable_user_access_group));
}
auto& encodable_phone_number = list[2];
auto& encodable_phone_number = list[3];
if (!encodable_phone_number.IsNull()) {
decoded.set_phone_number(std::get<std::string>(encodable_phone_number));
}
auto& encodable_sms_code = list[3];
auto& encodable_sms_code = list[4];
if (!encodable_sms_code.IsNull()) {
decoded.set_sms_code(std::get<std::string>(encodable_sms_code));
}
auto& encodable_force_recaptcha_flow = list[4];
auto& encodable_force_recaptcha_flow = list[5];
if (!encodable_force_recaptcha_flow.IsNull()) {
decoded.set_force_recaptcha_flow(
std::get<bool>(encodable_force_recaptcha_flow));
Expand All @@ -1954,6 +1967,8 @@ bool InternalFirebaseAuthSettings::operator==(
other.app_verification_disabled_for_testing_) &&
PigeonInternalDeepEquals(user_access_group_,
other.user_access_group_) &&
PigeonInternalDeepEquals(migrate_current_user_,
other.migrate_current_user_) &&
PigeonInternalDeepEquals(phone_number_, other.phone_number_) &&
PigeonInternalDeepEquals(sms_code_, other.sms_code_) &&
PigeonInternalDeepEquals(force_recaptcha_flow_,
Expand All @@ -1970,6 +1985,7 @@ size_t InternalFirebaseAuthSettings::Hash() const {
result = result * 31 +
PigeonInternalDeepHash(app_verification_disabled_for_testing_);
result = result * 31 + PigeonInternalDeepHash(user_access_group_);
result = result * 31 + PigeonInternalDeepHash(migrate_current_user_);
result = result * 31 + PigeonInternalDeepHash(phone_number_);
result = result * 31 + PigeonInternalDeepHash(sms_code_);
result = result * 31 + PigeonInternalDeepHash(force_recaptcha_flow_);
Expand Down
Loading
Loading