Skip to content

Commit 14f2f02

Browse files
author
Steve Hobbs
authored
Sign In With Apple iOS Swift quickstart (#8223)
* First draft of SIWA quickstart * Fixed typos and punctuation * Heading language change * Wording tweaks from feedback * Expanded or removed acronyms where appropriate * Reworded description * Moved SIWA tutorial to its own quickstart * Added next steps and a section on session renewal * Added comments for a line in a code snippet * Updated sample repo URL * Amendments based on review feedback * Bumped library version, added keychain save * Changed ID token for Access token * Clarified usage of getCredentialState * Aligned some code snippets with the sample
1 parent 6c4e03b commit 14f2f02

File tree

11 files changed

+442
-4
lines changed

11 files changed

+442
-4
lines changed

articles/quickstart/native/_includes/_ios_dependency_centralized.md

+4-2
Original file line numberDiff line numberDiff line change
@@ -1,11 +1,13 @@
1+
<!-- markdownlint-disable MD002 MD041 -->
2+
13
## Install Dependencies
24

35
### Carthage
46

57
If you are using Carthage, add the following to your `Cartfile`:
68

79
```ruby
8-
github "auth0/Auth0.swift" ~> 1.13
10+
github "auth0/Auth0.swift" ~> 1.18
911
```
1012

1113
Then, run `carthage bootstrap`.
@@ -20,7 +22,7 @@ If you are using [Cocoapods](https://cocoapods.org/), add the following to your
2022

2123
```ruby
2224
use_frameworks!
23-
pod 'Auth0', '~> 1.13'
25+
pod 'Auth0', '~> 1.18'
2426
```
2527

2628
Then, run `pod install`.
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
---
2+
title: Login
3+
description: This tutorial demonstrates how to add user login to a Swift application using Sign In With Apple.
4+
budicon: 448
5+
topics:
6+
- quickstarts
7+
- native
8+
- ios
9+
- swift
10+
- siwa
11+
github:
12+
path: 00-Login
13+
contentType: tutorial
14+
useCase: quickstart
15+
requirements:
16+
- CocoaPods
17+
- Xcode 11 Beta 6
18+
- iOS 13 Beta 8 Device
19+
---
20+
21+
<!-- markdownlint-disable MD002 MD041 -->
22+
23+
## Before you Start
24+
25+
This tutorial describes how to implement the native [Sign In With Apple](https://developer.apple.com/sign-in-with-apple/) introduced in iOS 13. Alternatively check out the [iOS Swift Login tutorial](/quickstart/native/ios-swift) for Web Authentication with Auth0.
26+
27+
Before you start, you'll need:
28+
* An [Apple Developer](https://developer.apple.com/programs/) account, which is a paid account with Apple. There is no free trial available unless you're in the [iOS Developer University Program](https://developer.apple.com/support/compare-memberships/).
29+
* To configure your Auth0 tenant and application client to enable **Sign In With Apple**. See [Add Sign In with Apple to Your Native App](/articles/connections/references/apple-native/guides/add-siwa-to-native-app) for details on how to do this.
30+
31+
<%= include('_includes/_login_centralized') %>
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,313 @@
1+
<!-- markdownlint-disable MD002 MD041 -->
2+
3+
<%= include('../../_includes/_getting_started', { library: 'Swift' }) %>
4+
5+
Add your credentials in the `Auth0.plist` file. If the file does not exist in your project yet, create it:
6+
7+
```xml
8+
<!-- Auth0.plist -->
9+
10+
<?xml version="1.0" encoding="UTF-8"?>
11+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
12+
<plist version="1.0">
13+
<dict>
14+
<key>ClientId</key>
15+
<string>${account.clientId}</string>
16+
<key>Domain</key>
17+
<string>${account.namespace}</string>
18+
</dict>
19+
</plist>
20+
```
21+
22+
<%= include('../../_includes/_ios_dependency_centralized') %>
23+
24+
## Add the "Sign In With Apple" Button
25+
26+
As an example of adding the sign-in button to your view, add a `UIStackView` to your main storyboard and create an outlet in `ViewController.swift`:
27+
28+
```swift
29+
@IBOutlet weak var loginProviderStackView: UIStackView!
30+
```
31+
32+
Next, add the `AuthenticationServices` Framework to your `ViewController.swift`:
33+
34+
```swift
35+
import AuthenticationServices
36+
```
37+
38+
Add a function that attaches an `ASAuthorizationAppleIDButton` button to the stack view:
39+
40+
```swift
41+
func setupProviderLoginView() {
42+
// Create Button
43+
let authorizationButton = ASAuthorizationAppleIDButton()
44+
45+
// Add Callback on Touch
46+
authorizationButton.addTarget(self, action: #selector(handleAuthorizationAppleIDButtonPress), for: .touchUpInside)
47+
48+
//Add button to the UIStackView
49+
self.loginProviderStackView.addArrangedSubview(authorizationButton)
50+
}
51+
```
52+
53+
Call this function in `viewDidLoad`:
54+
55+
```swift
56+
override func viewDidLoad() {
57+
super.viewDidLoad()
58+
59+
setupProviderLoginView()
60+
}
61+
```
62+
63+
## Handle Authorization
64+
65+
The following code shows the implementation of a button handler that initializes an authorization request and launches
66+
the native Sign In With Apple flow:
67+
68+
```swift
69+
@objc
70+
func handleAuthorizationAppleIDButtonPress() {
71+
// Create the authorization request
72+
let request = ASAuthorizationAppleIDProvider().createRequest()
73+
74+
// Set scopes
75+
request.requestedScopes = [.email, .fullName]
76+
77+
// Setup a controller to display the authorization flow
78+
let controller = ASAuthorizationController(authorizationRequests: [request])
79+
80+
// Set delegates to handle the flow response.
81+
controller.delegate = self
82+
controller.presentationContextProvider = self
83+
84+
// Action
85+
controller.performRequests()
86+
}
87+
```
88+
89+
:::note
90+
At this time the only scopes that Apple support are `email` and `fullName`. For more information, see [ASAuthorization.scope](https://developer.apple.com/documentation/authenticationservices/asauthorization/scope).
91+
:::
92+
93+
At this point, you will have a few compile errors to deal with. Before moving forward with all the code, let's remove those errors by providing some basic delegates.
94+
95+
### Add authorization extensions
96+
97+
Add the following to the end of the `ViewController.swift` file. This enables the authorization flow UI to be displayed from the controller:
98+
99+
```swift
100+
extension ViewController: ASAuthorizationControllerPresentationContextProviding {
101+
func presentationAnchor(for controller: ASAuthorizationController) -> ASPresentationAnchor {
102+
return self.view.window!
103+
}
104+
}
105+
```
106+
107+
Next, add a stub delegate for the `ASAuthorizationController` to the end of the `ViewController.swift` file. This code will handle the response sent to the `ASAuthorizationController`. You will come back to this one later.
108+
109+
```swift
110+
extension ViewController: ASAuthorizationControllerDelegate {
111+
}
112+
```
113+
114+
### Add the Sign In With Apple capability
115+
116+
To enable Sign In With Apple in your application, you need to enable this capability to your app, as shown:
117+
118+
![Add capabilities](/media/articles/ios/swift/add-capability.png)
119+
120+
:::panel Checkpoint
121+
Start the application and click the **Sign In With Apple** button. You should see the default sign-in dialog appear:
122+
123+
![Default sign-in dialog](/media/articles/ios/swift/authz-dialog.png)
124+
:::
125+
126+
## Process the Authorization Response
127+
128+
Add the following to the end of your `ViewController.swift` file.
129+
130+
```swift
131+
extension ViewController: ASAuthorizationControllerDelegate {
132+
// Handle authorization success
133+
func authorizationController(controller: ASAuthorizationController, didCompleteWithAuthorization authorization: ASAuthorization) {
134+
135+
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
136+
// Success
137+
}
138+
}
139+
140+
// Handle authorization failure
141+
func authorizationController(controller: ASAuthorizationController, didCompleteWithError error: Error) {
142+
print("Authorization Failed: \(error)")
143+
}
144+
}
145+
```
146+
147+
### Inspect the response
148+
149+
Upon successful authorization, you will be able to access the following information:
150+
151+
- UUID, team-scoped user ID
152+
- Verification Data
153+
- ID Token, Authorization Code
154+
- Account Information
155+
- Name, Verified Email Address (Original or relay address)
156+
- Real user indicator
157+
- High confidence indicator that the user is who they say they are
158+
159+
:::note
160+
The Account Information is ONLY sent on the first successful authorization. This information should be saved by your app if you wish to use it when a user leaves and reloads your application.
161+
:::
162+
163+
## Auth0 Token Exchange
164+
165+
Now that you have a successful authorization response, you can use the `authorizationCode` to perform a token exchange. The token exchange will give you access to Auth0 credentials, such as the ID and Access Tokens. For example:
166+
167+
```swift
168+
if let appleIDCredential = authorization.credential as? ASAuthorizationAppleIDCredential {
169+
// Convert Data -> String
170+
guard let authorizationCode = appleIDCredential.authorizationCode, let authCode = String(data: authorizationCode, encoding: .utf8) else
171+
{
172+
print("Problem with the authorizationCode")
173+
return
174+
}
175+
176+
// Auth0 Token Exchange
177+
Auth0
178+
.authentication()
179+
.tokenExchange(withAppleAuthorizationCode: authCode).start { result in
180+
switch(result) {
181+
case .success(let credentials):
182+
print("Auth0 Success: \(credentials)")
183+
184+
case .failure(let error):
185+
print("Exchange Failed: \(error)")
186+
}
187+
188+
}
189+
}
190+
```
191+
192+
## Renew Authentication
193+
194+
When users return to your app, you can log them in automatically if their login session is still valid. This involves:
195+
196+
* Calling `ASAuthorizationAppleIDProvider.getCredentialState` to make sure the user is still authorized
197+
* Retrieving Auth0 credentials
198+
199+
To do this, you're going to:
200+
201+
* Use `CredentialsManager` to store the Auth0 credentials
202+
* Use `SimpleKeychain` to store the Apple user ID
203+
* Check the credential state using Apple's APIs before retrieving Auth0 credentials
204+
205+
### Store credentials
206+
207+
The credentials manager retrieves stored credentials from the keychain and checks if the Access Token is still valid:
208+
209+
* If the current credentials are still valid, the credentials manager returns them
210+
* If the Access Token has expired, the credentials manager renews them using the Refresh Token and returns them
211+
212+
At the top of `ViewController.swift`, add a `CredentialsManager` and `SimpleKeychain` as follows:
213+
214+
```swift
215+
let credentialsManager = CredentialsManager(authentication: Auth0.authentication())
216+
let keychain = A0SimpleKeychain()
217+
218+
// Add a place to store credentials locally once they're renewed
219+
var credentials: Credentials?
220+
```
221+
222+
Next, modify the token exchange call you added earlier to store credentials when the user logs in:
223+
224+
```swift
225+
Auth0
226+
.authentication()
227+
.tokenExchange(withAppleAuthorizationCode: authCode).start { result in
228+
switch(result) {
229+
case .success(let credentials):
230+
231+
// NEW - store the user ID in the keychain
232+
self.keychain.setString(appleIDCredential.user, forKey: "userId")
233+
234+
// NEW - store the credentials locally
235+
self.credentials = credentials
236+
237+
// NEW - store the credentials in the credentials manager
238+
self.credentialsManager.store(credentials: credentials)
239+
240+
case .failure(let error):
241+
print("Exchange Failed: \(error)")
242+
}
243+
}
244+
```
245+
246+
### Renew authentication state
247+
248+
Add a function that tries to renew the user's login session. Here, `getCredentialState` is called to find out whether the user is still authorized for Sign In With Apple. If they are **unauthorized**, you should consider their access revoked, their access and refresh tokens should be removed, and the user should log into the application again. Otherwise, the credentials that were previously stored inside `CredentialsManager` can be returned, as in the following example:
249+
250+
```swift
251+
func tryRenewAuth(_ callback: @escaping (Credentials?, Error?) -> ()) {
252+
let provider = ASAuthorizationAppleIDProvider()
253+
254+
// Try to fetch the user ID
255+
guard let userID = keychain.string(forKey: "userId") else {
256+
return callback(nil, nil)
257+
}
258+
259+
// Check the Apple credential state
260+
provider.getCredentialState(forUserID: userID) { state, error in
261+
switch state {
262+
case .authorized:
263+
// Try to get credentials from the creds manager (ID token, Access Token, etc)
264+
self.credentialsManager.credentials { error, credentials in
265+
guard error == nil, let credentials = credentials else {
266+
return callback(nil, error)
267+
}
268+
269+
self.credentials = credentials
270+
271+
callback(credentials, nil)
272+
}
273+
274+
default:
275+
// User is not authorized
276+
self.keychain.deleteEntry(forKey: "userId")
277+
278+
// Remove their credentials from the store
279+
self.credentialsManager.clear()
280+
281+
callback(nil, error)
282+
}
283+
}
284+
}
285+
```
286+
287+
:::note
288+
Calling `credentialsManager.credentials` _automatically renews_ the Access Token if it has expired, using the refresh token. This call should only be executed if `getCredentialState` returns `authorized`, so that the refresh token is only used by an authorized user. Otherwise, the credentials must be cleared and the login session thrown away.
289+
:::
290+
291+
Finally, call this function from `viewDidLoad`. If no credentials are found, the user should be shown the login screen once more. Otherwise, they should continue on into the app:
292+
293+
```swift
294+
tryRenewAuth { credentials, error in
295+
guard error == nil, credentials != nil else {
296+
print("Unable to renew auth: \(String(describing: error))")
297+
298+
// The user should be asked to log in again
299+
300+
return
301+
}
302+
303+
// Set up any post-login UI or segue here
304+
}
305+
```
306+
307+
## Next Steps
308+
309+
Now that you're able to use Sign In With Apple and exchange the authorization code for Auth0 credentials, you can use these credentials to perform various tasks:
310+
311+
* Use the Access Token to [Call APIs](/quickstart/native/ios-swift/04-calling-apis)
312+
* [Assign roles to users](/quickstart/native/ios-swift/05-authorization)
313+
* Use the Access Token to [link accounts](/quickstart/native/ios-swift/07-linking-accounts)
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
<!-- markdownlint-disable MD041 -->
2+
3+
To run the sample follow these steps:
4+
5+
1) Set the **Callback URL** in the [Application Settings](${manage_url}/#/applications/${account.clientId}/settings) to
6+
7+
```text
8+
auth0.samples.Auth0Sample://${account.namespace}/ios/auth0.samples.Auth0Sample/callback
9+
```
10+
11+
2) Open `Auth0Sample.xcworkspace` in [Xcode](https://developer.apple.com/xcode/).
12+
13+
2) Click the `Run` button or select the menu option `Product | Run` or keyboard shortcut `CMD + R`.

0 commit comments

Comments
 (0)