Implement a custom provider

Our built-in provider creators cover the most common use cases for provider creation:

  • GoogleProvider

  • FacebookProvider

  • AppleProvider

  • WeChat

  • WebProvider

This custom provider guide instructs you how to implement a provider that none of the built-ins model such as a social/SSO provider with its own native SDK, or a login flow that none of the built-ins model.

If your provider has no native SDK component and only needs to choose how its ASWebAuthenticationSession returns to the app (custom scheme, universal link, out-of-band), you don’t need a custom provider, instead configure a WebProvider.

The two protocols

A provider is made of two pieces, both found in Sources/Core/Classes/Provider.swift:

  • ProviderCreator: a lightweight factory you list in ReachFive(providersCreators:). It carries a name and an optional variant and builds the actual Provider once ReachFive has fetched that provider’s configuration from the backend. Its create method also receives the shared ReachFive instance. See Example: wrapping a native SDK for how to hold that reference safely in the Provider you return.

  • Provider: the object doing the actual work: login, logout, and the app-lifecycle hooks it needs to intercept its own callback.

    public protocol ProviderCreator {
        var name: String { get }
        var variant: String? { get }
    
        func create(reachFive: ReachFive, providerConfig: ProviderConfig, clientConfigResponse: ClientConfigResponse) -> Provider
    }
    
    public protocol Provider {
        var name: String { get }
        func login(scope: [String]?, origin: String, presenting: Presentation) async throws -> AuthToken
        func logout() async throws
    
        // Default (no-op) implementations provided — override only what your provider needs:
        func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey: Any]) -> Bool
        func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool
        func applicationDidBecomeActive(_ application: UIApplication)
        func application(_ application: UIApplication, continue userActivity: NSUserActivity, restorationHandler: @escaping ([UIUserActivityRestoring]?) -> Void) -> Bool
    }
name must match the provider’s name as configured on your ReachFive Console. This is how ReachFive matches your ProviderCreator against the provider configurations it fetches from the backend. A configured provider with no matching creator still logs in, falling back to either native Sign in with Apple or a generic web-based provider.

Register the custom provider

let reachfive = ReachFive(
    sdkConfig: SdkConfig(domain: DOMAIN, clientId: CLIENT_ID),
    providersCreators: [MyProvider(variant: "ios")]
)

Example: wrapping a native SDK

This is the shape used by our own (AppleProvider.swift):

  • The creator is a thin factory.

  • The actual Provider calls into the third-party native SDK, then exchanges its result for a ReachFive AuthToken through reachFive.authWithCode, which is the same helper used internally by native login.

Your create method receives the shared ReachFive instance, which the SDK also stores in ReachFive.providers. When you build your Provider, decide how to access it from login:

When create(reachFive:) runs, the SDK has already decided to keep your Provider in ReachFive.providers. If your provider also keeps a strong reference back to that same ReachFive instance, neither object can ever be released — a retain cycle (ReachFiveProvider) that pins the whole SDK object graph in memory.

Use weak var reachFive in your provider, or copy only the properties you need at init time (e.g. sdkConfig, reachFiveApi, scope).

For background on why this happens and how weak breaks the cycle, see Apple: strong reference cycles between class instances and weak references.

public class MyProvider: ProviderCreator {
    public var name: String = "my-provider"
    public var variant: String?

    public init(variant: String? = nil) {
        self.variant = variant
    }

    public func create(reachFive: ReachFive, providerConfig: ProviderConfig, clientConfigResponse: ClientConfigResponse) -> Provider {
        ConfiguredMyProvider(reachFive: reachFive, providerConfig: providerConfig)
    }
}

class ConfiguredMyProvider: NSObject, Provider {
    let name: String
    private weak var reachFive: ReachFive? // weak: see the retain-cycle note above

    init(reachFive: ReachFive, providerConfig: ProviderConfig) {
        self.name = providerConfig.provider
        self.reachFive = reachFive
    }

    func login(scope: [String]?, origin: String, presenting: Presentation) async throws -> AuthToken {
        // 1. Drive your native SDK's own login UI/flow here, e.g.:
        let code = try await MyNativeSDK.shared.login(presenting: presenting.presentingViewController())

        // 2. Exchange its authorization code for a ReachFive AuthToken.
        guard let reachFive else { throw ReachFiveError.TechnicalError(reason: "ReachFive instance was deallocated") }
        return try await reachFive.authWithCode(code: code, pkce: Pkce.generate())
    }

    func logout() {
        MyNativeSDK.shared.logout()
    }
}

Example: a fully custom web flow

If your provider has no native SDK but needs behaviour WebProvider doesn’t offer, drive ASWebAuthenticationSession yourself (or reuse reachFive.webviewLogin(_:), see webviewLogin) inside login, then return the resulting AuthToken the same way.

App-lifecycle hooks

Only implement the hooks your provider actually needs to intercept its own callback; every other hook has a default no-op implementation.

Hook When to implement it

application(_:open:options:)

Your provider’s callback arrives through a custom URL scheme (myapp://…​) rather than the completion handler of ASWebAuthenticationSession.

application(_:didFinishLaunchingWithOptions:)

Your provider’s native SDK needs an explicit startup call.

An example of this is registering itself before the app finishes launching.

applicationDidBecomeActive(_:)

Your provider needs to react to the app returning to the foreground.

An example of this is resuming a paused native SDK session.

application(_:continue:restorationHandler:)

Your provider’s callback arrives as a universal link (NSUserActivityTypeBrowsingWeb).

An example of this is an out-of-band flow like WebProvider .externalApp mode.

Return true only if you actually consumed the activity. ReachFive relies on that return value (see application(_:continue:restorationHandler:) for details).

R5 AI Assistant

Confirm Deletion