title: Plaid OAuth deep link integration in SwiftUI
body: <p>Is there a tutorial on how to implement OAuth in iOS using pure SwiftUI?</p>
  <p>I read all the docs here (<a href="https://plaid.com/docs/link/oauth/" rel="nofollow noreferrer">https://plaid.com/docs/link/oauth/</a>) and studied the code here (<a href="https://github.com/plaid/plaid-link-ios" rel="nofollow noreferrer">https://github.com/plaid/plaid-link-ios</a>).</p>
  <p>The problem with the Plaid example above is that it shows a SwiftUI implementation of OAuth based a UIKit ViewController. My app is 100% SwiftUI and we don't use a ViewControllers.</p>
  <p>When I test the Plaid OAuth in sandbox (using the platypus account), I'm able to redirect to my app from their test website, but I'm unable to read the arguments passed in the parameters of the deep link (oauth_state_id=21a91b98-06ac-4c2c-9e5f-83c5fe9cbba5). Here is how I currently do it:</p>
  <p>1/ Below is a simplified version of our bankAccountView, where the Plaid modal is launched within our app. A few notes about this file:</p>
  <p>a. As you can see, I open the Plaid modal inside a .fullScreenCover() modifier.</p>
  <p>b. The LinkController object is a copy/paste of this: <a href="https://github.com/plaid/plaid-link-ios/blob/master/LinkDemo-Swift/LinkDemo-Swift/LinkKitSwiftUISupport.swift" rel="nofollow noreferrer">https://github.com/plaid/plaid-link-ios/blob/master/LinkDemo-Swift/LinkDemo-Swift/LinkKitSwiftUISupport.swift</a></p>
  <pre><code>import SwiftUI
  import LinkKit
  import FirebaseFunctions
  
  protocol LinkOAuthHandling {
      var linkHandler: Handler? { get }
      var oauthRedirectUri: URL? { get }
  }
  
  struct BankAccountsView: View, LinkOAuthHandling {
              
      @State var configurationForCreate: LinkTokenConfiguration?
      @State var configurationForUpdate: LinkTokenConfiguration?
      @State var plaidModal: LinkController?
      
      @State var linkHandler: Handler?
      var oauthRedirectUri: URL? = { URL(string: &quot;https://cruisedevelopment.page.link&quot;) }()
      
      var body: some View {
          VStack {
              
              // BUTTON TO OPEN PLAID MODAL
              Button(&quot;Link bank account&quot;) {
                  configurationForCreate = createLinkTokenConfigurationForCreate(with: plaid.tokenForCreate)
                  plaidModal = LinkController(configuration: .linkToken(configurationForCreate!), openOptions: [:]) { (error) in
                      print(&quot;Handle error: \(error)!&quot;)
                  }
              }
              
              // OPEN PLAID MODAL
              if plaidModal != nil { Rectangle().frame(width: 0, height: 0).onAppear { isPlaidModalOpen = true } }
              
              // ... MORE CONTENT
          
          }
          .onAppear {
              plaid.getLinkTokenForCreate()
          }
          // PLAID MODAL
          .fullScreenCover(isPresented: $isPlaidModalOpen, onDismiss: {
              isPlaidModalOpen = false
          }, content: {
              plaidModal
          })
      }
      
      private func createLinkTokenConfigurationForCreate(with linkToken: String) -&gt; LinkTokenConfiguration {
          var linkConfiguration = LinkTokenConfiguration(token: linkToken) { success in
              print(&quot;public-token: \(success.publicToken) metadata: \(success.metadata)&quot;)
              self.handleSuccessForCreate(success.publicToken, metadata: success.metadata)
          }
          linkConfiguration.onExit = { exit in
              if let error = exit.error {
                  print(&quot;LinkTokenConfigurationForCreate exit with \(error)\n\(exit.metadata)&quot;)
              } else {
                  print(&quot;LinkTokenConfigurationForCreate exit with \(exit.metadata)&quot;)
              }
              hidePlaidModal()
          }
          return linkConfiguration
      }
      
      private func handleSuccessForCreate(_ publicToken: String, metadata: SuccessMetadata) {
          let bankId = metadata.institution.id
          let bankName = metadata.institution.name
          var accountsIds = [String]()
          for account in metadata.accounts { accountsIds.append(account.id) }
          let payload: [String: Any] = [
              &quot;publicToken&quot;: publicToken,
              &quot;bankId&quot;: bankId,
              &quot;bankName&quot;: bankName,
              &quot;accountsIds&quot;: accountsIds
          ]
          isFinishingPlaidSetup = true
          Functions.functions().httpsCallable(finishPlaidSetupPath).call(payload) { (result, error) in
              if let error = error {
                  print(&quot;Error finishing Plaid setup: \(error.localizedDescription)&quot;)
              } else {
                  print(&quot;Success finishing Plaid setup: \(result!)&quot;)
              }
              isFinishingPlaidSetup = false
              hidePlaidModal()
          }
      }
  }
  </code></pre>
  <p>2/ After the App2App OAuth flow finishes and the user is redirected to my app (while the Plaid fullScreenCover is still open), I handle the deep link using the .onOpenUrl() modifier (this is the new way of doing it with SwiftUI - see here: <a href="https://www.donnywals.com/handling-deeplinks-in-ios-14-with-onopenurl/" rel="nofollow noreferrer">https://www.donnywals.com/handling-deeplinks-in-ios-14-with-onopenurl/</a>). I'm attaching a screenshot that shows the code we use.</p>
  <p>Notes about this file:</p>
  <p>a. My goal here is to replicate this: <a href="https://github.com/plaid/plaid-link-ios/blob/master/LinkDemo-Swift/LinkDemo-Swift/AppDelegate%2BOAuthSupport.swift" rel="nofollow noreferrer">https://github.com/plaid/plaid-link-ios/blob/master/LinkDemo-Swift/LinkDemo-Swift/AppDelegate%2BOAuthSupport.swift</a></p>
  <p>b. The code currently executes as expected until the &quot;Fail 1&quot; line. That is, I receive what looks like the right deep link URL from the platypus bank website and I'm able to grab the keyWindow, but I am unable to create a link0AuthHandler of type &quot;Link0AuthHandling&quot; using that keyWindow (even though the app still has the Plaid fullScreenCover open on the right screen).</p>
  <pre><code>import SwiftUI
  import LinkKit
  
  @main
  struct CruiseApp: App {
      
      @UIApplicationDelegateAdaptor var delegate: AppDelegate
          
      var body: some Scene {
          WindowGroup {
              ContentView()
                  .onOpenURL { url in
                      let window = UIApplication.shared.connectedScenes.flatMap {($0 as? UIWindowScene)?.windows ?? [] }.first { $0.isKeyWindow }
                      guard let linkOAuthHandler = window?.rootViewController as? LinkOAuthHandling else { let _ = print(&quot;&gt;&gt; FAIL 1&quot;); return }
                      guard let handler = linkOAuthHandler.linkHandler else { let _ = print(&quot;&gt;&gt; FAIL 2&quot;); return }
                      handler.continue(from: url)                    
                  }
          }
      }
  }
  </code></pre>
  <p>I think the issue has to do with the process described above.</p>