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. `migrateCurrentUser: true` requires a non-null `userAccessGroup`:

```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 @@ -570,7 +570,10 @@ public void setLanguageCode(
public void setSettings(
@NonNull GeneratedAndroidFirebaseAuth.AuthPigeonFirebaseApp app,
@NonNull GeneratedAndroidFirebaseAuth.InternalFirebaseAuthSettings settings,
@NonNull GeneratedAndroidFirebaseAuth.VoidResult result) {
@NonNull
GeneratedAndroidFirebaseAuth.NullableResult<
GeneratedAndroidFirebaseAuth.InternalUserDetails>
result) {
try {
FirebaseAuth firebaseAuth = getAuthFromPigeon(app);

Expand All @@ -591,7 +594,8 @@ public void setSettings(
settings.getPhoneNumber(), settings.getSmsCode());
}

result.success();
// Apple-only migration; Android has nothing to return here.
result.success(null);
} catch (Exception e) {
result.error(e);
}
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 Expand Up @@ -3345,7 +3372,7 @@ void setLanguageCode(
void setSettings(
@NonNull AuthPigeonFirebaseApp app,
@NonNull InternalFirebaseAuthSettings settings,
@NonNull VoidResult result);
@NonNull NullableResult<InternalUserDetails> result);

void verifyPasswordResetCode(
@NonNull AuthPigeonFirebaseApp app, @NonNull String code, @NonNull Result<String> result);
Expand Down Expand Up @@ -3999,10 +4026,10 @@ public void error(Throwable error) {
AuthPigeonFirebaseApp appArg = (AuthPigeonFirebaseApp) args.get(0);
InternalFirebaseAuthSettings settingsArg =
(InternalFirebaseAuthSettings) args.get(1);
VoidResult resultCallback =
new VoidResult() {
public void success() {
wrapped.add(0, null);
NullableResult<InternalUserDetails> resultCallback =
new NullableResult<InternalUserDetails>() {
public void success(InternalUserDetails result) {
wrapped.add(0, result);
reply.reply(wrapped);
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -1531,16 +1531,18 @@ - (void)setLanguageCodeApp:(nonnull AuthPigeonFirebaseApp *)app

- (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app
settings:(nonnull InternalFirebaseAuthSettings *)settings
completion:(nonnull void (^)(FlutterError *_Nullable))completion {
completion:(nonnull void (^)(InternalUserDetails *_Nullable,
FlutterError *_Nullable))completion {
FIRAuth *auth = [self getFIRAuthFromAppNameFromPigeon:app];
FIRUser *userToMigrate = settings.migrateCurrentUser ? auth.currentUser : nil;

if (settings.userAccessGroup != nil) {
BOOL useUserAccessGroupSuccessful;
NSError *useUserAccessGroupErrorPtr;
useUserAccessGroupSuccessful = [auth useUserAccessGroup:settings.userAccessGroup
error:&useUserAccessGroupErrorPtr];
if (!useUserAccessGroupSuccessful) {
completion([FLTFirebaseAuthPlugin convertToFlutterError:useUserAccessGroupErrorPtr]);
completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:useUserAccessGroupErrorPtr]);
return;
}
}
Expand All @@ -1554,7 +1556,22 @@ - (void)setSettingsApp:(nonnull AuthPigeonFirebaseApp *)app
@"on MacOS.");
#endif

completion(nil);
if (userToMigrate != nil) {
[auth
updateCurrentUser:userToMigrate
completion:^(NSError *_Nullable error) {
if (error != nil) {
completion(nil, [FLTFirebaseAuthPlugin convertToFlutterError:error]);
return;
}
FIRUser *migratedUser = auth.currentUser;
completion(
migratedUser != nil ? [PigeonParser getPigeonDetails:migratedUser] : nil, nil);
}];
return;
}

completion(nil, nil);
}

- (void)signInAnonymouslyApp:(nonnull AuthPigeonFirebaseApp *)app
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 Expand Up @@ -2036,11 +2042,12 @@ void SetUpFirebaseAuthHostApiWithSuffix(id<FlutterBinaryMessenger> binaryMesseng
NSArray<id> *args = message;
AuthPigeonFirebaseApp *arg_app = GetNullableObjectAtIndex(args, 0);
InternalFirebaseAuthSettings *arg_settings = GetNullableObjectAtIndex(args, 1);
[api setSettingsApp:arg_app
settings:arg_settings
completion:^(FlutterError *_Nullable error) {
callback(wrapResult(nil, error));
}];
[api
setSettingsApp:arg_app
settings:arg_settings
completion:^(InternalUserDetails *_Nullable output, FlutterError *_Nullable error) {
callback(wrapResult(output, error));
}];
}];
} else {
[channel setMessageHandler:nil];
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 Expand Up @@ -388,7 +390,8 @@ NSObject<FlutterMessageCodec> *nullGetFirebaseAuthMessagesCodec(void);
completion:(void (^)(NSString *_Nullable, FlutterError *_Nullable))completion;
- (void)setSettingsApp:(AuthPigeonFirebaseApp *)app
settings:(InternalFirebaseAuthSettings *)settings
completion:(void (^)(FlutterError *_Nullable))completion;
completion:
(void (^)(InternalUserDetails *_Nullable, FlutterError *_Nullable))completion;
- (void)verifyPasswordResetCodeApp:(AuthPigeonFirebaseApp *)app
code:(NSString *)code
completion:
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]. [userAccessGroup] must be non-null when this is
/// `true`. 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
Original file line number Diff line number Diff line change
Expand Up @@ -921,7 +921,8 @@ void FirebaseAuthPlugin::SetLanguageCode(
void FirebaseAuthPlugin::SetSettings(
const AuthPigeonFirebaseApp& app,
const InternalFirebaseAuthSettings& settings,
std::function<void(std::optional<FlutterError> reply)> result) {
std::function<void(ErrorOr<std::optional<InternalUserDetails>> reply)>
result) {
result(FlutterError("unimplemented",
"SetSettings is not available on this platform yet.",
nullptr));
Expand Down
Loading
Loading