How to implement the Enterprise Apple SSO: Final Part TV Providers and VSAccountManagerDelegate

In this post I will be talking about the implementation of the VSAccountManagerDelegate, this is the last part of the AppleSSO series, enjoy it.

Photo by Vasily Koloda @napr0tiv

What the documentation says?

The methods you use to respond to authentication view controller requests.

That means that we are almost done with the AppleSSO implementation.

Let’s first update the UI, we need to include two TextViews and a new button.

Your UI would be something like I show you below.

Then go to the ViewController and add the code below.

Then we need to connect those elements with their own IBOutlet or IBAction.

final class ViewController: UIViewController {  @IBOutlet private weak var supportedTextView: UITextView?  @IBOutlet private weak var featuredTextView: UITextView?   @IBAction private func checkStatusAction(_ sender: Any) { 
}
@IBAction func metadataRequestAction(_ sender: Any) {
}
}

Let’s start creating the ProviderManager, that object usually will be provided by a service but in our case we are using TextViews to set the supported providers and the featured providers, those are needed to request the Metadata.

The ProviderManager contains the code below.

struct ProviderManager {  let supportedProviders: [String]  let featuredProviders: [String]  init(supportedProviders: String, featuredProviders: String) {    self.supportedProviders = supportedProviders.split(separator: ",").compactMap({ String($0.trimmingCharacters(in: .whitespacesAndNewlines)) })    self.featuredProviders = featuredProviders.split(separator: ",").compactMap({ String($0.trimmingCharacters(in: .whitespacesAndNewlines)) })  }}

Now we can create a new file called AppleSSOHandler and then let’s refactor the ViewController code we need to move all that code into the AppleSSOHandler file.

In that file we need to create a class called AppleSSOHandler like I show you below.

import UIKit
import VideoSubscriberAccount
final class AppleSSOHandler { // Paste all the previous code here}

Note: Don’t forget to import UIKit and VideoSubscriberAccount frameworks.

Then you need to add the code below.

final class AppleSSOHandler: NSObject {
private lazy var accountManager: VSAccountManager = { // 1
let accountManager = VSAccountManager()
accountManager.delegate = self
return accountManager
}()

private let providerManager: ProviderManager // 2

weak var presentInViewController: UIViewController? // 3

required init(providerManager: ProviderManager) { // 4
self.providerManager = providerManager
}

func checkAccessStatus(accessStatus: ((VSAccountAccessStatus) -> Void)? = nil) { // 5
}
func requestMetadata() { // 6
}

private func enqueueMetadataRequest() { // 7
}
}

All this code will encapsulate the AppleSSO functionality.

  1. This code creates an instance of VSAccountManager, that instance needs to conforms the VSAccountManagerDelegate protocol.
  2. This property provides the supported and featured providers (usually you will get those from a service).
  3. This property will be use for present a controller provided by AppleSSO, that controller will be used in the VSAccountManagerDelegate methods.
  4. This code is used to create an instance with a ProviderManager that previously we created.
  5. This method allows to know if the application has enough permissions. I show you the implementation below.
func checkAccessStatus(accessStatus: ((VSAccountAccessStatus) -> Void)? = nil) {

accountManager.checkAccessStatus(options: [.prompt: true]) {. (status, error) in

if
let error = error {
preconditionFailure(error.localizedDescription)
}

accessStatus?(status)
}
}

6. This method will check the access status and then if the access is granted request the Metadata information.

func requestMetadata() {  checkAccessStatus { (status) in
guard
status == .granted else { return }

self.enqueueMetadataRequest()
}
}

7. This method will request the Metadata information provided by AppleSSO. I provided you the enough data for create the Metadata request.

private func enqueueMetadataRequest() {  let includeAccountProviderIdentifier = true  let includeAuthenticationExpirationDate = true  let isInterruptionAllowed = true  let metadataRequest = VSAccountMetadataRequest()  metadataRequest.supportedAccountProviderIdentifiers =        providerManager.supportedProviders  metadataRequest.featuredAccountProviderIdentifiers = providerManager.featuredProviders  metadataRequest.includeAccountProviderIdentifier = includeAccountProviderIdentifier  metadataRequest.includeAuthenticationExpirationDate = includeAuthenticationExpirationDate  metadataRequest.isInterruptionAllowed = isInterruptionAllowed  accountManager.enqueue(metadataRequest) { (metadata, error) in    if let error = error { 
preconditionFailure(error.localizedDescription)
}
}
}

Let’s conform the VSAccountManagerDelegate protocol into the AppleSSOHandler, we can do that in an extension like a show you below.

// MARK: - VSAccountManagerDelegate
extension AppleSSOHandler: VSAccountManagerDelegate {
func accountManager(_ accountManager: VSAccountManager, present viewController: UIViewController) {

presentInViewController?.present(viewController, animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, dismiss viewController: UIViewController) { viewController.dismiss(animated: true, completion: nil)
}
func accountManager(_ accountManager: VSAccountManager, shouldAuthenticateAccountProviderWithIdentifier accountProviderIdentifier: String) -> Bool { return true
}
}

Now let’s work in the ViewController, I give you the ViewController implementation below.

final class ViewController: UIViewController {  @IBOutlet private weak var supportedTextView: UITextView?  @IBOutlet private weak var featuredTextView: UITextView?  private var providerManager: ProviderManager {     guard let supportedProviders = supportedTextView?.text,
let featuredProviders = featuredTextView?.text
else {
preconditionFailure("Provider resources could not be found.")
}
let manager = ProviderManager(supportedProviders: supportedProviders, featuredProviders: featuredProviders)

return manager
} // 1
private lazy var appleSSOHandler: AppleSSOHandler = { let handler = AppleSSOHandler(providerManager: providerManager)
handler.presentInViewController = self
return
handler
}() // 2
@IBAction private func checkStatusAction(_ sender: Any) { appleSSOHandler.checkAccessStatus()
} // 3
@IBAction func metadataRequestAction(_ sender: Any) { appleSSOHandler.requestMetadata()
} // 4
}

Let’s explain that code.

  1. This code creates the ProviderManager instances that provides the supported and featured TV Providers.
  2. This code creates a lazy instantiation of AppleSSOHandler.
  3. This code makes the checkAccessStatus call.
  4. This code makes the requestMetadata call.

Now let’s run the App.

After you touch the Metadata Request button that will call the present viewController method, that method is provided by the VSAccountManagerDelegate

Then that controller will show the supported and featured Tv providers that we already set into the MetadataRequest.

Note: In this example we are going to use the DirectTv provider.

Then the image below shows the next method called that is shouldAuthenticatorAccountProviderWithIdentifier method, this method allows us to use that provider or not.

Now we just need to set our credentials to the DirectTv provider.

After set the correct credentials we can se that our App has access to the DirecTv provider.

And when we back to the app the dismiss viewController method is called.

Then next time you call the Metadata request, you will see the Provider data into the response.

The common mistakes

  1. If you see the error below, it is because you are not conforming the VSAccountManagerDelegate.
*** Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'This kind of request requires a delegate.'

To fix that just be sure that you are setting the delegate, something like this:

private lazy var accountManager: VSAccountManager = {  let accountManager = VSAccountManager()  accountManager.delegate = self  return accountManager}()

2. If you try to build your project using the simulator and tried to call the method (enqueue).

func enqueue(_ request: VSAccountMetadataRequest, completionHandler: @escaping (VSAccountMetadata?, Error?) -> Void) -> VSAccountManagerResult

You will see the error below, I know that error doesn’t give us enough information for fix it, but the problem is AppleSSO only can be used in a real device.

Terminating app due to uncaught exception 'NSInvalidArgumentException', reason: 'The errorOrNil parameter must not be nil.'terminating with uncaught exception of type NSExceptionCoreSimulator 732.17 - Device: iPhone 11 (1D7AC154-A506-4A33-996C-F72158BAC5FB) - Runtime: iOS 13.1 (17A844) - DeviceType: iPhone 11

3. The error below happens when your TV Providers are wrong, you need to provide an array of the Supported Account Provider Identifiers.

View service failed: Error Domain=VSErrorDomain Code=1 "Your TV provider is not supported." UserInfo={NSLocalizedDescription=Your TV 
provider is not supported.}

Let me give you an example:

let providerIdentifiers = [“\“ATT\“”, “\“Cox\“”, “\“DTV\“”, “\“Dish4\“”]

In the example above the identifiers contain an extra quotes (\“), below I show you the correct way.

let providerIdentifiers = [“ATT”, “Cox”, “DTV”, “Dish4”]

4. Another error is try to request the Metadata without giving the authorization to your App.

VSErrorDomain Code=6 “This application is not authorized for this request type.

To fix that remember to call the checkAccessStatus before use the enqueue.

Additional resources

  • Lazy var in Swift
  • AppleSSO project
  • How to implement the Enterprise Apple SSO: Part Two Check Access Status
  • How to implement the Enterprise Apple SSO: Part One Entitlements

Steve Jobs: “For the past 33 years, I have looked in the mirror every morning and asked myself: ‘If today were the last day of my life, would I want to do what I am about to do today?

Congratulations!! we finally finished this AppleSSO series, I hope I was able to help you, don’t forget to leave many claps and see you soon with new tutorials.

Experienced Software Engineer with 12+ years of experience. https://www.linkedin.com/in/antoniotrejof/

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store