Skip to main content

apple_sign_in

PackageKindProvides the roleNeedsChoose with
smf_apple_sign_ininfrastructurenonea provider of the authentication, such as firebase_auth-m apple_sign_in

apple_sign_in lets the users of the app sign in with their Apple Account on iOS, with Sign in with Apple through the sign_in_with_apple package. It is a way to sign in next to the email address and the password: the module asks Apple for a credential, and the module that provides the authentication signs the user in with it. On Android the app has no Sign in with Apple.

The module has no screen. A screen of sign-in, such as those of sign_in, shows its button, and code of your own can use it too. Sign-in describes the ways to sign in of an app and what appSession does with their credentials.

What it adds to the app​

In the appWhat it is
sign_in_with_apple: ^8.2.0 and crypto: ^3.0.7 in the dependenciesThe package of Sign in with Apple, and the package that hashes the nonce of a request.
lib/core/apple_sign_in/apple_sign_in.dartappleCredential(), which asks Apple for a credential, and AppleSignInIcon, the icon of its button.
com.apple.developer.applesignin in ios/Runner/Runner.entitlementsThe entitlement of the capability Sign in with Apple, with the step by hand app_entry.ios_app_id; see the capability.
The section "Sign in with Apple" of the READMEWhat the capability needs of the team that signs the app, what Apple gives only at the first sign-in, what a deletion asks of Apple and of the backend, and that App Review checks the logo of the button.

The module signs the iOS app with the entitlement through a socket of the app entry, whichever module provides the app entry; see the sockets of the app entry:

ios/Runner/Runner.entitlements
<dict>
<key>com.apple.developer.applesignin</key>
<array>
<string>Default</string>
</array>
</dict>

The module gives the auth role its way to sign in. Whichever module provides the role, the role lists it in appSignInMethods of lib/core/auth/sign_in_methods.dart. In an app with this module alone:

lib/core/auth/sign_in_methods.dart
const List<SignInMethod> appSignInMethods = [
SignInMethod(
label: _appleSignInLabel,
icon: method0.AppleSignInIcon(),
platforms: {TargetPlatform.iOS},
credential: method0.appleCredential,
issuer: 'https://appleid.apple.com',
revokesOnDeletion: true,
),
];
The way to sign in with Apple
LabelContinue with Apple, one of the titles that Apple allows a custom button of Sign in with Apple, and Продовжити з Apple in Ukrainian. In an app without a module that provides the localization, the label is in English.
IconAppleSignInIcon, the Apple logo of the Material icons, Icons.apple, in the color of the label; see the button.
PlatformsiOS. A screen of sign-in shows its button on no other.
Issuerhttps://appleid.apple.com. AccountSession.issuers names Apple by it.
For the App StoreThe equivalent of a login service of a third party; see the App Store.
What it needs of the backendNothing that the module states. Deleting an account needs a key of Apple at the provider of the authentication; see deleting an account.
Deleting an accountAsks Apple again first: revokesOnDeletion is true.

In the guide for coding agents of the app, the module writes the section "Sign in with Apple": which file asks Apple and what it returns, that Apple gives the name only at the first sign-in, which errors become which reason, the entitlement, and what a test sets to sign in with Apple.

Signing in with it​

appleCredential() shows the sheet of Sign in with Apple. It asks for the email address and the full name of the user, with the SHA-256 hash of a new random nonce of generateNonce(), which Apple writes into the ID token. It returns an IdTokenCredential of the issuer of Apple with:

  • the ID token of Apple;
  • the raw nonce itself, against which the provider of the authentication checks the token;
  • the authorization code of the request, with which the provider can revoke the tokens that Apple gave the app;
  • the given name and the family name of the user.

A screen of sign-in calls it as credential() of the way to sign in, from its state, and gives the credential to appSession.signInWith. Here method is the way to sign in from appSignInMethods:

final credential = await method.credential();
if (credential == null) return; // The user closed the sheet of Apple.
await appSession.signInWith(credential);

Call it from the state of a screen, never within a call of appSession. An anonymous user who signs in with Apple gets the identity and keeps the id, or signs in to the account that has it already; see ways to sign in.

Apple gives the name of the user only the first time that the user signs in to the app. The credential has it then, and a later one has none, so do not expect it there. The user may also hide the email address. The account then gets an address of the private relay of Apple, which forwards messages to the user.

What happensWhat appleCredential() does
The app runs on a platform other than iOSIt fails with notConfigured without asking Apple. On Android, the hint is Sign in with Apple works on iOS only, and the app runs on android.
The user closed the sheet, canceledIt returns null. That is no failure: the screen goes back to how it was.
Apple answers with an unknown error, unknownIt fails with notConfigured. Apple answers so when the app is not signed with the capability, and may do so on a device without an Apple Account.
Any other error, or a credential without an identity tokenIt fails with unknown, with the error in the developerHint.

The developerHint of the unknown error names the capability and the section of the README:

AuthFailure: notConfigured (Apple answered with an unknown error (<the message of Apple>). It does so when the app is not signed with the capability Sign in with Apple, which needs a team of the paid Apple Developer Program, and may do so on a device without an Apple Account. See the section "Sign in with Apple" of the README of the app.)

The provider of the authentication has to take Apple too. With firebase_auth, Apple is enabled in the Firebase console, under Authentication > Sign-in method, since the Firebase CLI cannot enable it. Until then, signing in with Apple fails with notConfigured, and the hint tells how to enable it.

The capability​

Sign in with Apple is a capability of the app. The key com.apple.developer.applesignin in ios/Runner/Runner.entitlements asks for it, and every build configuration of the Runner target signs with that file. The App ID of the app needs the capability too: when Xcode manages the signing of the app, it adds the capability to the App ID. Signing with the capability needs a team of the paid Apple Developer Program.

So the app lists the step by hand app_entry.ios_app_id, in which you sign the app once in Xcode with your paid team, which registers the App ID with the capability. The README of the app has it under "Finish the setup", and the click-by-click guide shows each screen.

A free personal team cannot sign the app for an iPhone. To run the app there, remove the key from ios/Runner/Runner.entitlements. Signing in with Apple then fails with notConfigured, as in any app without the capability.

Deleting an account​

App Store Review Guideline 5.1.1(v) asks an app with Sign in with Apple to revoke the tokens that Apple gave it when the user deletes the account. So on iOS, for an account that signs in with Apple, appSession.deleteAccount() first calls appleCredential() itself, and the sheet of Apple shows again. The provider of the authentication then signs the user in again with the new credential, revokes the tokens of Apple with its authorization code, and deletes the account. If the user closes the sheet, nothing is deleted, and that is no failure.

The backend needs a key of Apple to revoke the tokens. With firebase_auth, the settings of the Apple provider in the Firebase console take the Services ID and the OAuth code flow configuration: your Apple team ID, and the Key ID and the private key of a key for Sign in with Apple. The private key is a secret: enter it in the console yourself, and never put it into the app or its repository. Until they are set, deleting an account that signs in with Apple fails with notConfigured, and the account stays. See firebase_auth. Adding Apple in the Firebase console and entering the key there are steps by hand of an app with firebase_auth, and the click-by-click guide shows each screen of both.

The screen of the account of sign_in has a text of its own for such a failure. It has one too for a confirmation with another Apple Account, which the provider refuses with invalidCredentials, deleting nothing.

The button​

The sign-in of sign_in in the light theme: the title Sign in with the line Choose how to sign in to your account. below it, the white buttons Continue with Google, with the colored G, and Continue with Apple, with the Apple logo, then or and the form for an email address and a password.The same sign-in in the dark theme: the two buttons stay white, with a black label and icon.

AppleSignInIcon is Icon(Icons.apple), the Apple logo of the Material icons. It takes the color and the size of the IconTheme around it, so it has the color of the label of its button. SMF does not ship the logo artwork of Apple, whose license does not allow it.

Apple asks a custom button of Sign in with Apple to use the logo artwork of Apple Design Resources, and App Review checks every such button. Before you submit the app, download that logo and show it in AppleSignInIcon of lib/core/apple_sign_in/apple_sign_in.dart. In this sketch the logo is an image of your own, which pubspec.yaml declares among the assets of the app, at the size of the icons of the button:

lib/core/apple_sign_in/apple_sign_in.dart
class AppleSignInIcon extends StatelessWidget {
const AppleSignInIcon({super.key});


Widget build(BuildContext context) {
final size = IconTheme.of(context).size;
// The label of the button names Apple.
return Image.asset(
'assets/apple_logo.png',
width: size,
height: size,
excludeFromSemantics: true,
);
}
}

Keep the constructor const and without arguments: the list of the ways to sign in creates the icon as a constant.

sign_in shows the button above the form of the sign-in and of the sign-up, on iOS only. It draws the button white with a grey hairline, with the label and the icon in black, in the light theme and in the dark one. The icon is 20 logical pixels and keeps that size at a large text size.

The App Store​

App Store Review Guideline 4.8 asks an app on iOS that offers a login service of a third party, such as Google sign-in, to offer an equivalent way to sign in too: one that asks for no more than the name and the email address of the user and lets the user keep the address private. The App Store takes Sign in with Apple as such a way.

An app with google_sign_in and without this module gets the warning auth.no_equivalent_sign_in from smf create, which stops a run with --strict. With this module next to it, the warning goes away:

smf create my_app -m google_sign_in,apple_sign_in

See the App Store of google_sign_in, and Lenient and strict.

In tests​

appleCredential() asks Apple only while debugDefaultTargetPlatformOverride is TargetPlatform.iOS, so a test that calls it, as a tap on the button does, sets that first. The package asks the platform on the method channel com.aboutyou.dart_packages.sign_in_with_apple. A test answers its method performAuthorizationRequest with a map whose type is appleid, with identityToken, authorizationCode, givenName and familyName.

SMF's CI tests the module in every app with it, in each mode of the auth role, on the real sign_in_with_apple package. A stand-in answers its method channel as the plugin does on iOS, for one user of Apple. Each request gets an ID token and a new authorization code, and only the first request of a test file gets the name and the email address, as at the first sign-in in an app. The ID tokens are test tokens: JSON Web Tokens without a signature whose payload has the issuer of Apple, the user, the address of the private relay, and the nonce of the request, which is the SHA-256 hash of the raw nonce, as Apple writes it. The mocks of the provider of the authentication take such tokens.

The test checks the request with a new raw nonce each time, the credential, the name at the first sign-in only, the outcome of each answer of Apple, the failure on every platform but iOS, that the app lists the way to sign in on iOS with revokesOnDeletion, and the color and the size of the icon. The test of the ways to sign in of the auth role then signs in with the credential on firebase_auth, and deletes the account after the session asked for a new credential. Nobody can sign in on a device, so the start check of CI has no probe for the module. See Contributing.

Choosing it​

After the features, smf create asks "Infrastructure: which do you want?" and offers the module there:

apple_sign_in — Sign in with Apple on iOS with sign_in_with_apple

The module requires sign-in, and firebase_auth is the only module that provides it, so it comes without a question, and firebase_core with it:

Adding firebase_auth: the only provider of the authentication role, which apple_sign_in requires.
Adding firebase_core: a dependency of firebase_auth.

The run then asks who may use the app without an account, as for every app with sign-in. To choose the modules without the questions, here with the screens of sign-in, BLoC and tabs at the bottom:

smf create my_app -m home,sign_in,bloc,bottom_tabs,apple_sign_in

After generation, firebase_core asks whether to run flutterfire configure, and firebase_auth then asks whether to run the script that enables the ways to sign in. The script cannot enable Apple, which you enable in the Firebase console with the key of Apple; see deleting an account and Firebase. The run ends with the link to the guide of these steps by hand and of the App ID.