Skip to main content

Overview

OwnID Boost is designed to enhance your existing native login and registration screens by adding OwnID as an add-on. It places OwnID side-by-side with the traditional password field, giving users the option to choose a passwordless experience without altering your current flows.

Prerequisites

Implementation

Login

The OwnID SDK offers two options to present the passwordless login button:
  • Side-by-side to password field (out-of-the box)
  • Your custom button (you control the UI)
Later, you will learn how to style them.
1

Create Login Handler

final class OwnIDLogin: LoginPerformer {
        func login(
            payload: OwnIDCoreSDK.OwnID.CoreSDK.Payload, 
            loginId: String
        ) -> OwnIDCoreSDK.OwnID.LoginResultPublisher {
            
            // Add your login logic here

            return // Your publisher
        }
    }
2

Set Up Login View Model

final class LoginViewModel: ObservableObject {
    @Published var email = ""
    private var bag = Set<AnyCancellable>()
    
    var ownIDViewModel: OwnID.FlowsSDK.LoginView.ViewModel!
    
    init() {
        ownIDViewModel = OwnID.FlowsSDK.LoginView.ViewModel(
            loginPerformer: OwnIDLogin(),
            loginIdPublisher: $email.eraseToAnyPublisher()
        )
        
        subscribeToEvents()
    }
        
    private func subscribeToEvents() {
        ownIDViewModel.integrationEventPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] event in
                switch event {
                case .success(let event):
                    switch event {
                    case .loggedIn:
                        // Handle success
                    case .loading:
                        // Show loading
                    }
                case .failure(let error):
                    // Handle error
                }
            }
            .store(in: &bag)
    }
}
3

Create Login View

Side-by-Side button
struct LoginView: View {
    @StateObject private var viewModel = LoginViewModel()
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("Email", text: $viewModel.email)
                .textContentType(.emailAddress)
                .autocapitalization(.none)
            
            OwnID.FlowsSDK.LoginView(viewModel: viewModel.ownIDViewModel)
        }
        .padding()
    }
}
1

Create Login Handler

final class OwnIDLogin: LoginPerformer {
        func login(
            payload: OwnIDCoreSDK.OwnID.CoreSDK.Payload, 
            loginId: String
        ) -> OwnIDCoreSDK.OwnID.LoginResultPublisher {
            
            // Add your login logic here

            return // Your publisher
        }
    }
2

Set Up Login View Model

  • Create an instance of the OwnID view model
  • Define a PassthroughSubject to handle login actions
  • Subscribe the ownIDViewModel to this publisher
  • Call send() on the subject from your button’s action to initiate the login process
final class LoginViewModel: ObservableObject {
    @Published var email = ""
    private var bag = Set<AnyCancellable>()
    
    var ownIDViewModel: OwnID.FlowsSDK.LoginView.ViewModel!
    private let resultPublisher = PassthroughSubject<Void, Never>()
    
    init() {
        ownIDViewModel = OwnID.FlowsSDK.LoginView.ViewModel(
            loginPerformer: OwnIDLogin(),
            loginIdPublisher: $email.eraseToAnyPublisher()
        )
        ownIDViewModel.subscribe(to: resultPublisher.eraseToAnyPublisher())
        
        subscribeToEvents()
    }
    
    func handleCustomButtonAction() {
        resultPublisher.send()
    }
        
    private func subscribeToEvents() {
        ownIDViewModel.integrationEventPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] event in
                switch event {
                case .success(let event):
                    switch event {
                    case .loggedIn:
                        // Handle success
                    case .loading:
                        // Show loading
                    }
                case .failure(let error):
                    // Handle error
                }
            }
            .store(in: &bag)
    }
}
3

Create Your Custom Button

Example (do your own - no OwnID code)
var body: some View {
    CustomButton() {
        viewModel.handleCustomButtonAction()
    }
}

Registration

In the registration flow, the only supported button is side-by-side
Integrate OwnID’s passwordless authentication into your existing registration screen:
1

Create Registration Handler

final class OwnIDRegistration: RegistrationPerformer {
    func register(
        configuration: OwnIDCoreSDK.OwnID.FlowsSDK.RegistrationConfiguration,
        parameters: any OwnIDCoreSDK.RegisterParameters
        ) -> OwnIDCoreSDK.OwnID.RegistrationResultPublisher {
        
        let ownIdData = configuration.payload.data

        // Add your registration logic here

        return // Your publisher
    }   
}
2

Set Up View Model

final class RegistrationViewModel: ObservableObject {
    @Published var email = ""
    private var bag = Set<AnyCancellable>()
    
    var ownIDViewModel: OwnID.FlowsSDK.RegisterView.ViewModel!

    init() {
        ownIDViewModel = OwnID.FlowsSDK.RegisterView.ViewModel(
            registrationPerformer: OwnIDRegistration(),
            loginPerformer: OwnIDLogin(),
            loginIdPublisher: $email.eraseToAnyPublisher()
        )
        
        subscribeToEvents()
    }
    
    private func subscribeToEvents() {
        ownIDViewModel.integrationEventPublisher
            .receive(on: DispatchQueue.main)
            .sink { [weak self] event in
                switch event {
                case .success(let event):
                    switch event {
                    case .readyToRegister:
                        self?.ownIDViewModel.register(registerParameters: params)
                    case .userRegisteredAndLoggedIn:
                        break
                        // Handle success
                    case .loading:
                        break
                        // Show loading
                    case .resetTapped:
                        break
                        // Handle reset
                    }
                case .failure(let error):
                    break
                    // Handle error
                }
            }
            .store(in: &bag)
    }
}
3

Create Registration View

struct RegistrationView: View {
    @StateObject private var viewModel = RegistrationViewModel()
    
    var body: some View {
        VStack(spacing: 16) {
            TextField("Email", text: $viewModel.email)
                .textContentType(.emailAddress)
                .autocapitalization(.none)
            
            OwnID.FlowsSDK.RegisterView(viewModel: viewModel.ownIDViewModel)
        }
        .padding()
    }
}

Returning Users Experience

In some cases, you might want to start the login flow for returning users programmatically. For example, when a returning user opens the app or opens the login page. For that, you can use the auth() function provided by the LoginViewModel:
Example
ownIDViewModel.auth(onlyReturningUser: true)

Customization

Button Styling

Side-by-Side Button
let orConfig = OwnID.UISDK.OrViewConfig(
    textSize: 20,
    textColor: .red
)

let loaderConfig = OwnID.UISDK.LoaderViewConfig(
    spinnerColor: .red,
    circleColor: .green
)

let buttonConfig = OwnID.UISDK.IconButtonViewConfig(
    widgetPosition: .trailing,
    height: 40,
    iconColor: .red,
    borderColor: .red,
    backgroundColor: .white,
    orViewConfig: orConfig,
    loaderViewConfig: loaderConfig
)

let config = OwnID.UISDK.VisualLookConfig(iconButtonConfig: buttonConfig)

OwnID.FlowsSDK.LoginView(
    viewModel: viewModel.ownIDViewModel,
    visualConfig: config
)

Error Handling

The SDK uses OwnID.CoreSDK.Error for all errors:
switch error {
case .flowCancelled(let flow):
    // User cancelled the OwnID flow
    // Usually safe to ignore
     
case .userError(let errorModel):
    // Show errorModel.userMessage to the user
    // This message is already localized
     
case .integrationError(underlying: Swift.Error):
    // Handle integration error
    // Log for debugging
}

Advanced Configuration

<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
   <key>OwnIDAppID</key>
   <string>your-app-id</string>
   <key>OwnIDEnv</key>
   <string>uat</string>
   <key>OwnIDRegion</key>
   <string>us</string>
   <key>EnableLogging</key>
   <true/>
</dict>
</plist>
Data Center By default, OwnID SDK connects to the US datacenter. For EU region support, specify it in your configuration.
<key>OwnIDRegion</key>
<string>eu</string>
Logging
It is strongly recommended to disable logging in production builds.
OwnID.CoreSDK.logger.isEnabled = true

Language

By default, the SDK uses the device’s locale settings. You can override this behavior to specify supported languages.
OwnID.CoreSDK.configure(
    userFacingSDK: info,
    supportedLanguages: ["en","fr"]
)

UIKit Examples

final class RegisterViewController: UIViewController {
    @IBOutlet weak var emailTextField: UITextField!
    @IBOutlet weak var ownIDContainerView: UIView!
    
    private var ownIDViewModel: OwnID.FlowsSDK.RegisterView.ViewModel!
    private var bag = Set<AnyCancellable>()
    
    private lazy var ownIDButton: UIHostingController<OwnID.FlowsSDK.RegisterView> = {
        let ownIDView = OwnID.FlowsSDK.RegisterView(viewModel: ownIDViewModel)
        let ownIDVC = UIHostingController(rootView: ownIDView)
        ownIDVC.view.translatesAutoresizingMaskIntoConstraints = false
        return ownIDVC
    }()
    
    override func viewDidLoad() {
        super.viewDidLoad()
        
        let emailPublisher = NotificationCenter.default
            .publisher(for: UITextField.textDidChangeNotification, object: emailTextField)
            .map({ ($0.object as? UITextField)?.text ?? "" })
        
        ownIDViewModel = OwnID.FlowsSDK.RegisterView.ViewModel(registrationPerformer: OwnIDRegistration(),
                                                               loginPerformer: OwnIDLogin(),
                                                               loginIdPublisher: emailPublisher.eraseToAnyPublisher())
        subscribe(to: ownIDViewModel.integrationEventPublisher)
        addChild(ownIDButton)
        ownIDContainerView.addSubview(ownIDButton.view)
        ownIDButton.didMove(toParent: self)
    }
    
    private func subscribe(to eventsPublisher: OwnID.RegistrationPublisher) {
        eventsPublisher
            .receive(on: DispatchQueue.main)
            .sink { [unowned self] event in
                switch event {
                case .success(let event):
                    switch event {
                    // Event when user successfully finishes OwnID registration flow
                    case .readyToRegister:
                        // Call register(:) function and
                        // pass the custom user's parameters if needed
                        
                    // Event when OwnID creates
                    // account in your system
                    // and logs in user
                    case .userRegisteredAndLoggedIn:
                        // User is registered and logged in with OwnID
                        
                    case .loading:
                        // Display loading indicator according to your designs
                        
                    case .resetTapped:
                        // Event when user select "Undo" option in ready-to-register state
                    }
                    
                case .failure(let error):
                    // Handle OwnID.CoreSDK.Error here
                }
            }
            .store(in: &bag)
    }
}