Streaming is available in most browsers,
and in the Developer app.
-
Get the most out of Sign in with Apple
Sign in with Apple makes it easy for people to sign in to your apps and websites with the Apple ID they already have. Fully integrate Sign in with Apple into your app using secure requests, and by handling state changes and server notifications. We'll also introduce new APIs that allow you to let existing users switch to Sign in with Apple quickly and easily.
Resources
- Human Interface Guidelines: Sign in with Apple
- Implementing User Authentication with Sign in with Apple
- Sign In with Apple
Related Videos
WWDC22
WWDC21
WWDC20
WWDC19
-
Download
Hello and welcome to WWDC.
Hi. Welcome to "Get the Most Out of Sign in with Apple." My name is Alfonso, and together with my colleague, Jonathan, we're going to talk about best practices and some of the new features we have worked this year for Sign in with Apple. As you know, Sign in with Apple makes the experience of signing into your application fast and easy. Users are really loving how easy it is to use, as well as the fact that there are no more passwords for them to remember. Its privacy features have been embraced by our users, with over half of them opting to use private e-mail relay. Because of Sign in with Apple, users are now able to create an account with your application with a simple tap. If you haven't already, be sure to watch last year's WWDC session "Introducing Sign in with Apple," where you will be able to see a live demo on how you can add this feature to your application. Today, we will go over several topics. We will start by digging deeper into the authorization request and how to make it more secure. After that, we will talk about best practices when handling credentialState changes for your applications. We will also introduce new server-to-server developer notifications, as well as some new additions and changes to the Sign in with Apple Button. And finally, we will talk about upgrading to Sign in with Apple. As you know, security is very important for our users, and it was one of the things we focused on when creating Sign in with Apple. When using Sign in with Apple, you already get great security features for your accounts, like automatic two-factor authentication, but there are also a few things you can do to make your requests even more secure. Today, we will review some of the best practices for creating your authorization requests, as well as how to get the most out of the security features integrated into them.
This is an example on how to create an authorization request using Swift.
If you have seen our previous session or implemented Sign in with Apple before, this will look very familiar.
This is the method where we will handle the SignInWithAppleButtonPress and the right place to create your authorization request and controller.
As you know, you can modify the properties of the request to add things like requestedScopes, in case your application requires user information like name or e-mail.
But as we mentioned before, the request also has some properties that can help make the authorization process more secure. These are nonce and state.
These properties will allow you to verify that the authorization and credential you get after executing a request are the ones you were expecting. Let's start by talking about what these properties are. The nonce is an opaque blob of data sent as a string in the request. It is important to generate one unique nonce every time you create a new request, as later on, you will be able to verify this value.
In order to do that, the nonce value will be returned to you embedded in the identityToken property of the authorization credential.
This will allow you to verify this value in your server, helping prevent replay attacks.
Like the nonce, the state value is also an opaque blob of data sent with the request.
One key difference it has with the nonce value is that the state will be returned in the credential, allowing you to locally match a credential to a request and verify this was generated from your application. Now that we have added these properties, we're ready to tell the controller to perform and present our request to the user.
At this moment, the user will see the authorization request, which will look like this. If you requested the user's e-mail address, the user will be presented with the choice to share or hide their e-mail address. If the user decides to hide their e-mail address, what you will receive will be a private e-mail relay. You might have several questions about this, like, "What is a private e-mail relay? How can I verify this e-mail address? What if I need a reply from a user?" So let's try to answer some of these questions.
E-mail relays are just like a real e-mail address and are team-scoped. This means that they will be unique for you but will always be the same e-mail address for a user across all of your applications.
E-mail relays route e-mails to an e-mail address verified by Apple to be real and owned by the user, so you don't have to worry about verifying the e-mail address.
You can also be sure that users will have the ability to reply to your e-mails if a reply is needed. There are no downtimes for this service, so users are able to receive and reply to your e-mails anytime. As you can see, this makes the e-mail relay a great choice for your users while remaining a convenient communication method between you and them. If a user has chosen to use a private e-mail relay, be sure to respect their choice. Once the user makes a selection, the authorization will proceed. When an authorization is successful, you will receive a credential in the delegate methods for the authorization controller. This is an example on how to get a credential from an authorization.
Inside of this credential, you will find properties containing the user information that you requested, like name and e-mail.
Along with this, you will also find important properties that will allow you to securely verify the request and create a session with your servers. These are the state, identityToken and authorizationCode.
As you know, some of these parameters, like name and e-mail, will only be included on the credential the first time a user authorizes your application.
Because of this, it is important to cache the objects you need in case of a failure communicating with your server due to poor connectivity. That way, the important information you need will not be lost.
Make sure you verify the state value of the credential to be the same state value you previously generated.
As I mentioned before, the response contains an authorizationCode and an identityToken. Send these values to your server, where they can be decoded.
Once decoded, verify the received information, as well as the session with Apple servers. Let's see how the identityToken will look when decoded by your server. As you can see, some of the values you can get from it are: the subject, which is the userIdentifier that was returned to you on the authorization and will allow you to create a session in your servers.
The nonce, as we mentioned before, it's a very important value. Verify this to be the same nonce you generated previously in the request. This will allow you to verify the authenticity of the authorization and help mitigate replay attacks.
If you requested the user's e-mail address, it will also be included in the identityToken.
And finally, the real_user_status will be returned the first time that a user authorizes your application. Includes one of three states: 0 for unsupported, 1 for unknown, and 2 for likely real.
As we just saw, the identityToken is a JSON Web Token that contains several pieces of information related to the authorization.
Once you have this information, it's time to verify the token by using the public key you can obtain from Apple, and make sure the token has not expired.
You can exchange the authorizationCode with the Apple ID servers, and when this is successful, you will receive a refresh token and an access token for future calls, as well as a new identityToken that should be identical to the one you already have.
You may verify a refresh token once a day to confirm that the user's Apple ID on that device is still in good standing. By following these best practices, you will not only make sure your users have a great and smooth sign-in experience, but you can also be confident that the authorization, along with the information contained in it, is valid and secure.
Now that we have covered the authorization process, let's cover some of the best practices to have when checking for the credentialState of your application. For this, we use the getCredentialState API. It allows you to verify the current state of a credential by sending the currently stored userIdentifier.
This API will help you verify that the user remains signed into this device and to your application, helping you make sure everything is okay for you to continue with your session.
It is important that this method is called every time your application is launched or changes to the foreground. That way, you will be able to react on time to any changes in status and present the appropriate screen in your application to your user.
These are the already known states you can receive when calling this API. Let's quickly review those again and what to do when encountering each one.
Authorized means you can fast-track the user directly to your application and skip the log-in UI.
Revoked means that the user decided to sign out of your application. In this scenario, you should sign the user out of your application, close your session, and present the log-in screen.
NotFound will be returned when there was no credential matching the userIdentifier. Present the log-in screen so that the user can authenticate to your application. And finally, you will notice there is a new credentialState...
Transferred. Let's talk about this credentialState and how we can handle it.
The Transferred credentialState will only be received by applications that recently were transferred from one development team to another. For example, after a company is acquired.
As we have mentioned before, userIdentifiers are unique to a team, so when transferring ownership of an application, the existing users will need to be migrated to a new userIdentifier to match the new team.
This migration is handled silently, that is, without any user interaction, and can be triggered by calling the same API used to create a new account or log in a user. As you can see, this is the same code for creating a new account or signing into an existing account.
By adding the currently stored userIdentifier to the request, we can validate the transferred state of the user and generate the new userIdentifier that will match the new team.
The response will be returned in the AuthorizationController delegate methods, and the application can continue to be used without the user noticing a change. Now, let's talk about a new feature we're introducing this year for Sign in with Apple: server-to-server developer notifications.
Listening to these notifications will allow you to monitor things like credentialState changes right from your server as well as receive other types of events. The process to start receiving these notifications is simple.
First, you have to register your server end point on the Apple Developer's website.
Once you have completed this registration, you are ready to start receiving events.
Events will be sent as JSON Web Tokens signed by Apple. Let's take a look at how these tokens look like. As you can see, the JSON Web Token will contain important information, like the issuer and the bundle identifier of the application. This is an example of the e-mail-disabled event, which you can get if a user has decided to stop receiving e-mails on their e-mail relay.
The e-mail-enabled event means that the user opted back in to receiving e-mail messages. It is important to note that this event, as well as e-mail-disabled, will only be sent when the user previously decided to use a private e-mail relay for their account. The consent-revoked event will be sent to you when a user decided to stop using their Apple ID with your application, and should be treated as a sign out by the user.
One example on when you might receive this notification is when a user decides to disconnect your application from Settings.
And finally, account-delete. This event will be sent when a user has asked Apple to delete their Apple ID. When receiving this notification, the userIdentifier associated with the user will no longer be valid. As you can see, by listening to these notifications, you will be able to react to these four different scenarios in a better way and directly from your servers.
Now, let's talk about another new feature we're introducing this year: a Sign in with Apple Button for SwiftUI. With SwiftUI, it is really simple to present the Sign in with Apple Button in your application, but not only that, the SwiftUI API will also help you create the authorization request as well as handle the response within the same block of code.
As you can see, the button is very simple to create. On your view, you can just add a SignInWithAppleButton object and specify a label for it. In this case, we have chosen "SignIn," but we can also choose "Continue" or "SignUp." After that, we have an onRequest closure where we're able to create our request. Just like we saw before in Swift, this is the right place to add the requested scopes like name and e-mail, and the nonce and state values we talked about before.
With the button, we also have an onCompletion closure where we will get the result of the request and handle the success or failure cases just as you would on the delegate methods for the authorization controller. And finally, we can also select the button style.
The available styles for the Sign in with Apple Button are black, white, and white outline. And since we're talking about styles for the button, if you need to personalize the Sign in with Apple Button more to match the specific design of your website or application, we're also happy to introduce a new online portal where you will be able to do just that. You can access this website at appleid.apple.com/signinwithapple/button.
Here, you will be able to customize many features of the button like the label, the size, and the position of the elements. You will also be able to download and get the button assets so that you can import them to your project or website. And now, I would like to invite Jonathan to talk about upgrading to Sign in with Apple. Thanks, Alfonso. Last year, we launched Sign in with Apple, and customer feedback has been phenomenal. Users love being able to sign in to your apps with just one tap. But what about users that are already using a traditional username- and password-based account and don't want to fork their current account? This year, we're introducing a new API to help your users upgrade to using Sign in with Apple. So why would you want your users to upgrade their account to Sign in with Apple? First, it is secure for both you and your users. Upgrading to Sign in with Apple is the easiest way to convert your accounts to two-factor authentication. Sign in with Apple requires two-factor and utilizes device trust for local credentialState.
Account recovery is also a lot less complicated with Sign in with Apple. Users forget their passwords, and they also need to be verified. You don't need to manage your users' secrets when you use Sign in with Apple. That's all done for you. Utilizing this API, you will prevent the duplication of accounts. Users love the convenience and security of Sign in with Apple. Those users that already have a traditional username- and password-based account won't abandon their current account to use Sign in with Apple because now they can just upgrade. So what is Upgrade to Sign in with Apple? Upgrade to Sign in with Apple is a new API designed to be a fast, easy security upgrade for your users, and it is available in multiple places throughout iOS and iPadOS, including your app. To help you upgrade your users to Sign in with Apple, Authentication Services is utilizing an extension-based API. We call this extension the Account Authentication Modification Extension.
Using it, you can provide a seamless experience for your users to upgrade the way they sign in to Sign in with Apple.
When a user upgrades to Sign in with Apple from a weak credential, that weak credential gets removed from the system once the flow is completed successfully.
We've also included support for adding your own UI in cases in which security verification should be completed prior to asking for a Sign in with Apple credential.
This same extension supports both upgrades to Sign in with Apple and strong password upgrades. For our session, I'll be providing an overview of the new Upgrade to Sign in with Apple flow. For all the details about implementing this new API, including strong password upgrades, I invite you to check out the WWDC 2020 session, "One-Tap Account Security Upgrades." Once implemented, there are three ways your Account Modification Extension can be invoked by users.
The first is when security recommendations identifies a weak credential in the new password manager in Settings.
The second is when a user is interacting with your app, utilizes password autofill, and the selected credential is a weak credential.
The last is when you invoke the new Authentication Services API via a user interaction that you specify within your app. Now that we know where the extension is invoked, let's take a look at how it works. There are a few concepts to the Account Modification Extension.
If you couldn't tell by its name, let me clarify that the ASAccountAuthentication ModificationViewController is a view controller. Your subclass of it will be the NSExtensionPrincipalClass.
The first call to your extension will invoke a non-user interactive function that serves as the first attempt to upgrade the user to Sign in with Apple with the existing credential. We hope to see most upgrades occur here, as it would require the least work for our users.
Understanding there are cases that will require additional UI to verify a user, a step-up security view can be displayed. We'll then pass the same credential to the extension via a separately defined function.
The last element is the extension context property. The extension context is used for communication between your extension and the extension hosting process while within the user interactive and step-up security functions. Using the extension context, you'll be able to tell the extension hosting process what happened and what needs to happen next.
Here, you can see a stubbed-out version of the ExtensionViewController subclass named AccountModificationViewController. As you can see, it is overriding three functions.
The first, convertAccountToSignIn WithAppleWithoutUserInteraction, is called initially by the extension hosting process in an attempt to upgrade the credential without user interaction.
The second is viewDidLoad, which can be used to set up an intermediary user interface for the security step-up flow if necessary. Providing intermediary UI to indicate progress might help provide the best user experience if you expect there to be network calls or delays. The last, prepareInterface ToConvertAccountToSignInWithApple, is called just prior to your view appearing. It gets past the same ASPasswordCredential provided in the non-user interactive flow. This credential is to be used for actions such as prefilling a username in a form or kicking off a two-factor authentication flow. Tasks that will take a while should be done asynchronously, utilizing intermediary UI to indicate progress. Your step-up view is not shown until this call finishes.
Now that we've completed an overview of the AccountAuthentication ModificationViewController, let's take a closer look at how it all works together by looking at the ASAccountAuthentication ModificationExtensionContext. The extension context is the integral property of the account modification view controller. As mentioned earlier, the extension context is used to control the flow of the upgrade. So, how does it control the flow? First, it has a function to request Sign in with Apple authorization. This is similar to calling perform requests with an Apple ID credential, as mentioned earlier by Alfonso. This will display Sign in with Apple UI specific to your app.
Then if you're evaluating the credential or currently signed-in account and determine that additional UI is needed to verify the account, the extension context is used to request that the Step-Up Security UI is displayed.
The extension context is also used to tell the hosting process to complete the flow. This removes the existing credential.
Last, the extension context is also used in cases in which you need to cancel the flow entirely.
Let's take a closer look at the extension context property in more detail.
GetSignInWithAppleAuthorizationWithState displays the Sign in with Apple UI with your app's information, asking the user to authorize the upgrade.
Earlier, Alfonso mentioned setting a state and nonce on the ASAuthorizationAppleID request. In upgrade flows, nonce and state is passed as a parameter to this call. Once the user authorizes the upgrade, the completion handler will be called with the Apple ID credential after the flow completes.
Next, we have completeUpgrade ToSignInWithApple. This lets the extension hosting process know that the authorization completed successfully, resulting in the removal of the password credential.
Last, we have cancelRequest.
The API expects this to be called with an ASExtensionError. If your extension determines that additional UI needs to be shown to the user, it is critical to call this with the userInteractionRequired error. If the user cancels while within your UI flow, utilize the userCanceled error.
All other failures can use the failed error.
In our previous slides, we did a brief overview of the account modification view controller. Then we reviewed the extension context property and how it controls the flow. Now, let's dive deeper into implementation details around converting the account without UI. We begin the flow in the OS when the user interacts with a weak credential and wants to upgrade that credential to Sign in with Apple.
Your Account Modification Extension is initialized by the password managing process, and the Convert without UI function is called, providing the password credential intended to be upgraded to Sign in with Apple. The extension's job is to evaluate the passed credential to make sure it is valid prior to upgrading to Sign in with Apple.
At this point, checking to see if the username matches the currently signed-in user is a good first course of action.
Let's say the extension determined that the state of the signed-in user and the passed credential requires a call to the server to verify the account.
The server verifies the credential, and the authentication is a success.
The server then replies back to the extension confirming that the account is ready to be upgraded to Sign in with Apple.
Now that the account is confirmed as authenticated and eligible for upgrade, the extension context would be used to request the Apple ID credential. But before we jump into that part of the flow, let's take a look at what would happen if there is an issue with the account preventing an immediate upgrade.
As seen in our previous example, the flow begins when the user interacts with a weak credential and wants to upgrade that credential to Sign in with Apple. Your Account Modification Extension is then initialized by the password managing process. Then the Prepare to Convert Without UI function is called with the password credential.
The credentials are then verified by the extension. Let's take a moment to look at some sample code for verifying the credentials. As you can see, the sample code defines an enum representing a verification result.
There are three values that represent the verification result: success, failure, and twoFactorAuthRequired.
When the existing credential is verified, one of these three values would get returned.
A failure result is for cases such as when the server attempts to validate the credential, but the account or server state is not appropriate for converting the account at that time. For this case, the extensionContext would be used to call cancelRequest with the failed error. This would end the flow.
Success indicates that the account is ready to be converted right away, as shown in our previous sequence diagram. The extensionContext is then used to get the Apple ID credential.
TwoFactorAuthRequired means that we need to show some UI to the user. The credential alone is not enough to upgrade to Sign in with Apple. This case is handled by using the extensionContext to call cancelWithError with the userInteractionRequired error.
Back to our diagram. The extension calls the verifyCredential function, which results in a call to the extension's back end to validate the credential.
The server will attempt to verify the account with the request.
The server determines that this account needs additional verification.
That reply comes back to the extension, and the verifyCredential function evaluates this as a twoFactorAuthRequired VerificationResult.
As shown in the sample code for the verifyCredentials call, this case is handled by using the extension context to call cancelWithError with the userInteractionRequired error.
We have a recommendation of requesting UI only if needed to avoid users having to do extra work. In most cases where a user is already authenticated in your app, we'd expect that a non-user interactive flow will be enough to complete the conversion. However, as seen in this example, there could be circumstances where UI flows might be warranted. Let's take a look at how this would work. After the extension context canceled with the userInteractionRequired error, the password managing process initializes a new instance of the account modification view controller...
and calls the Prepare to Convert with UI function, passing the ASPasswordCredential.
At this point, the extension's view controller is presented providing intermediary UI, such as a spinning gear.
Utilizing the same credentials as earlier with perhaps a slight change of context, a request is made to the back end.
The server attempts to verify the account, requires 2FA, and sends a reply back to the extension.
The extension updates the view, asking the user for two-factor authentication.
The user provides the verification code, which the extension would then verify. If the user could not be verified at this point, the extension context would be used to cancel the flow with the failed error. Let's assume the user provided a valid code and verification is a success. The extension is now ready to request the Apple ID credential. So now, we're back on the happy path with an account that is authenticated and eligible for upgrade. Now that the account is ready, let's request the Apple ID credential and finish the flow.
As mentioned earlier by Alfonso, it is important to secure your request. To do so, your extension would generate a state and nonce and pass them as parameters for the getAppleIDCredential call. This results in Upgrade to Sign in with Apple UI being displayed to the user. The user then authorizes using Face ID.
The Apple ID credential is provided to the extension in the completion block of the getAppleIDCredential call. The extension would then verify the credential's state property as being the same generated earlier.
The extension would then request an upgrade of the account, providing the necessary account information and the Apple ID credential to the server back end so the account can be converted.
The back end would then exchange the authorization code with Apple servers, verifying the value of the nonce in the identityToken, and if successful, perform a conversion of the account.
After converting, it sends a reply back with a success value. It is important to note again that if the server's operation failed, the extension context should be used to call cancelWithError with the failed error.
This time, the server is indicating success, so the extension would perform any required bookkeeping and then call completeUpgradeToSignIn WithApple on the extension context. This results in the password manager removing the existing credential, and with that, the flow is completed. The new Authentication Services Account Modification Extension is built specifically to help improve users' security by offering Sign in with Apple in place of their weak credentials. Because we remove the old credential, users will not be confused when they sign in to your app next. Utilizing this API, you can help your users implement the best security practices for their credentials while avoiding duplicate accounts.
Upgrade to Sign in with Apple offers a convenient API for you to upgrade your users' existing accounts. We're incredibly excited to see how you implement Upgrade to Sign in with Apple and look forward to your feedback. I hope you all enjoyed our talk today. Thank you for joining us and have a great WWDC.
-
-
2:02 - Create an Authorization Request
// Configure request, setup delegates and perform authorization request @objc func handleAuthorizationButtonPress() { let request = ASAuthorizationAppleIDProvider().createRequest() request.requestedScopes = [.fullName, .email] request.nonce = myNonceString() request.state = myStateString() let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self controller.performRequests() }
-
5:37 - Get a credential from an Authorization
// ASAuthorizationControllerDelegate func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) { if let credential = authorization.credential as? ASAuthorizationAppleIDCredential { let userIdentifier = credential.user let fullName = credential.fullName let email = credential.email let realUserStatus = credential.realUserStatus let state = credential.state let identityToken = credential.identityToken let authorizationCode = credential.authorizationCode // Securely store the userIdentifier locally self.saveUserIdentifier(userIdentifier) // Create a session with your server and verify the information self.createSession(identityToken: identityToken, authorizationCode: authorizationCode) } }
-
8:51 - Verify the state of a credential
// Getting a credential state let provider = ASAuthorizationAppleIDProvider() provider.getCredentialState(forUserID: getStoredUserIdentifier()) { (credentialState, error) in switch(credentialState) { case .authorized: // Sign in with Apple credential Valid case .revoked: // Sign in with Apple credential Revoked, Sign out case .notFound: // Credential was not found, fallback to login screen case .transferred: // Application was recently transferred, refresh User Identifier @unknown default: break } }
-
11:00 - Migrate a user identifier
// Migrating a user identifier let request = ASAuthorizationAppleIDProvider().createRequest() request.requestedScopes = [.fullName, .email] request.user = getStoredUserIdentifier() request.nonce = myNonceString() request.state = myStateString() let controller = ASAuthorizationController(authorizationRequests: [request]) controller.delegate = self controller.presentationContextProvider = self controller.performRequests()
-
13:54 - Create a Sign in with Apple button
// SwiftUI example: SignInWithAppleButton(.signIn) { onRequest: { (request) in request.requestedScopes = [.fullName, .email] request.nonce = myNonceString() request.state = myStateString() } onCompletion: { (result) in switch result { case .success(let authorization): // Handle Authorization case .failure(let error) // Handle Failure } } }.signInWithAppleButtonStyle(.black)
-
25:15 - convertAccountToSignInWithAppleWithoutUserInteraction
enum VerificationResult : Int { case success; case failure; case twoFactorAuthRequired; override func convertAccountToSignInWithAppleWithoutUserInteraction( for serviceIdentifier: ASCredentialServiceIdentifier, existingCredential: ASPasswordCredential ) { verifyCredential(existingCredential) { (result: VerificationResult) in switch result { case .failure: self.extensionContext.cancelRequest(withError: ASExtensionError(.failed)) case .success: self.extensionContext.getSignInWithAppleAuthorizationWithState(state: myStateString(), nonce: myNonceString(), {…} case .twoFactorAuthRequired: self.extensionContext.cancelRequest(withError: ASExtensionError(.userInteractionRequired)) } }
-
-
Looking for something specific? Enter a topic above and jump straight to the good stuff.
An error occurred when submitting your query. Please check your Internet connection and try again.