Search code examples
swiftswiftuibox-api

How to authenticate with the Box SDK using SwiftUI?


I have been having a hard time trying to figure out how to authenticate with the Box API using SwiftUI.

As far as I understand, SwiftUI does not currently have the ability to satisfy the ASWebAuthenticationPresentationContextProviding protocol required to show the Safari OAuth2 login sheet. I know that I can make a UIViewControllerRepresentable to use UIKit within SwiftUI, but I can't get this to work.


Solution

  • I have figured out how to get the OAuth2 login sheet for Dropbox to appear and authenticate the client using SwiftUI.

    The trick is to use a Coordinator to make the UIViewControllerRepresentable satisfy a protocol.

    import SwiftUI
    import BoxSDK
    import AuthenticationServices
    
    var boxSDK = BoxSDK(clientId: "<Client ID>", clientSecret: "<Client Secret>")
    var boxClient: BoxClient
    
    struct BoxLoginView: View {
        
        @State var showLogin = false
        
        var body: some View {
            VStack {
                Button {
                    showLogin = true
                } label: {
                    Text("Login")
                }
    
                BoxView(isShown: $showLogin)
                    // Arbitrary frame size so that this view does not take up the whole screen
                    .frame(width: 40, height: 40)
            }
        }
    }
    
    /// A UIViewController that will present the OAuth2 Safari login screen when the isShown is true.
    struct BoxView: UIViewControllerRepresentable {
    
        typealias UIViewControllerType = UIViewController
        
        let letsView = UIViewController()
        
        @Binding var isShown : Bool
        
        // Show the login Safari window when isShown
        func updateUIViewController(_ uiViewController: UIViewController, context: Context) {
            if(isShown) {
                getOAuthClient()
            }
        }
        
        func makeUIViewController(context _: Self.Context) -> UIViewController {
            return self.letsView
        }
        
        func makeCoordinator() -> Coordinator {
            return Coordinator(parent: self)
        }
        
        func getOAuthClient() {
            boxSDK.getOAuth2Client(tokenStore: KeychainTokenStore(), context:self.makeCoordinator()) { result in
                switch result {
                case let .success(client):
                    boxClient = client
                case let .failure(error):
                    print("error in getOAuth2Client: \(error)")
                }
            }
        }
        
        class Coordinator: NSObject, ASWebAuthenticationPresentationContextProviding {
            var parent: BoxView
            
            init(parent: BoxView) {
                self.parent = parent
            }
            
            func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
                return parent.letsView.view.window ?? ASPresentationAnchor()
            }
        }
    }