Using ASWebAuthentication in SwiftUI

Related searches

Having a bit of trouble getting authentication to work from within a SwiftUI view. I’m using ASWebAuthentication and whenever I run I get an error:

Cannot start ASWebAuthenticationSession without providing presentation context. Set presentationContextProvider before calling -start.

I’m creating a ViewController and passing in a reference to the Scene Delegate window based on this stack overflow post but that answer doesn’t seem to be working for me. I’ve also found this reddit post, but I’m a little unclear as to how they were able to initialize the view with the window before the scene delegate’s window is set up.

This is the code I’m using for the SwiftUI view:

import SwiftUI
import AuthenticationServices

struct Spotify: View {
  var body: some View {
    Button(action: {
        self.authWithSpotify()
    }) {
        Text("Authorize Spotify")
    }
  }

  func authWithSpotify() {

    let authUrlString = "https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response_type=code&redirect_uri=http://redirectexample.com/callback&scope=user-read-private%20user-read-email"
    guard let url = URL(string: authUrlString) else { return }

    let session = ASWebAuthenticationSession(
        url: url,
        callbackURLScheme: "http://redirectexample.com/callback",
        completionHandler: { callback, error in

            guard error == nil, let success = callback else { return }

            let code = NSURLComponents(string: (success.absoluteString))?.queryItems?.filter({ $0.name == "code" }).first

            self.getSpotifyAuthToken(code)
    })

    session.presentationContextProvider = ShimViewController()
    session.start()
  }

  func getSpotifyAuthToken(_ code: URLQueryItem?) {
    // Get Token
  }

}

struct Spotify_Previews: PreviewProvider {
  static var previews: some View {
    Spotify()
  }
}

class ShimViewController: UIViewController, ASWebAuthenticationPresentationContextProviding {
  func presentationAnchor(for session: ASWebAuthenticationSession) -> ASPresentationAnchor {
    return globalPresentationAnchor ?? ASPresentationAnchor()
  }
}

And in the SceneDelegate:

var globalPresentationAnchor: ASPresentationAnchor? = nil

class SceneDelegate: UIResponder, UIWindowSceneDelegate {

var window: UIWindow?

func scene(_ scene: UIScene, willConnectTo session: UISceneSession, options connectionOptions: UIScene.ConnectionOptions) {
    // Use this method to optionally configure and attach the UIWindow `window` to the provided UIWindowScene `scene`.
    // If using a storyboard, the `window` property will automatically be initialized and attached to the scene.
    // This delegate does not imply the connecting scene or session are new (see `application:configurationForConnectingSceneSession` instead).

    // Use a UIHostingController as window root view controller
    if let windowScene = scene as? UIWindowScene {
        let window = UIWindow(windowScene: windowScene)
        window.rootViewController = UIHostingController(rootView: Spotify())
        self.window = window
        window.makeKeyAndVisible()
    }

    globalPresentationAnchor =  window
}

Any idea how I can make this work?

Regarding the reddit post, I got it to work as is. My misunderstanding was that the AuthView isn't being used as an 'interface' View. I created a normal SwiftUI View for my authentication view, and I have a Button with the action creating an instance of the AuthView, and calling the function that handles the session. I'm storing the globalPositionAnchor in an @EnvironmentObject, but you should be able to use it from the global variable as well. Hope this helps!

struct SignedOutView: View {
    @EnvironmentObject var contentManager: ContentManager

    var body: some View {
        VStack {
            Text("Title")
            .font(.largeTitle)

            Spacer()

            Button(action: {AuthProviderView(window: self.contentManager.globalPresentationAnchor!).signIn()}) {
                Text("Sign In")
                    .padding()
                    .foregroundColor(.white)
                    .background(Color.orange)
                    .cornerRadius(CGFloat(5))
                    .font(.headline)
            }.padding()
        }
    }
}

ASWebAuthentication in SwiftUI, g authentication to work from within a SwiftUI view. I'm using ASWebAuthentication and whenever I run I get an error: Cannot start� Overview Use an ASWebAuthenticationSession instance to authenticate a user through a web service, including one run by a third party. Initialize the session with a URL that points to the authentication webpage. A browser loads and displays the page, from which the user can authenticate.

I've run into something similar before when implementing ASWebAuthenticationSession. One thing I didn't realize, is you have to have a strong reference to the session variable. So I would make you session variable a property of your class and see if that fixes the issue. A short snippet of what I mean:

// initialize as a property of the class
var session: ASWebAuthenticationSession?

func authWithSpotify() {
    let authUrlString = "https://accounts.spotify.com/authorize?client_id=\(spotifyID)&response_type=code&redirect_uri=http://redirectexample.com/callback&scope=user-read-private%20user-read-email"
    guard let url = URL(string: authUrlString) else { return }

    // assign session here
    session = ASWebAuthenticationSession(url: url, callbackURLScheme: "http://redirectexample.com/callback", completionHandler: { callback, error in

            guard error == nil, let success = callback else { return }

            let code = NSURLComponents(string: (success.absoluteString))?.queryItems?.filter({ $0.name == "code" }).first

            self.getSpotifyAuthToken(code)
    })

    session.presentationContextProvider = ShimViewController()
    session.start()
}

Sign in with Apple using SwiftUI, Tagged with ios, swift, oauth, wwdc. to ASWebAuthenticationSession and how I went about converting my iOS app to use the new API. SwiftUI is a framework to build User Interfaces (UI) for iOS apps. With SwiftUI, you create the UIs of your iOS apps entirely with Swift code, using a novel declarative approach. SwiftUI was introduced during WWDC 2019. It’s a completely new paradigm and it changes how we think about building User Interfaces for iOS apps.

Using .webAuthenticationSession(isPresented:content) modifier in BetterSafariView, you can easily start a web authentication session in SwiftUI. It doesn't need to hook SceneDelegate.

import SwiftUI
import BetterSafariView

struct SpotifyLoginView: View {
    
    @State private var showingSession = false
    
    var body: some View {
        Button("Authorize Spotify") {
            self.showingSession = true
        }
        .webAuthenticationSession(isPresented: $showingSession) {
            WebAuthenticationSession(
                url: URL(string: "https://accounts.spotify.com/authorize")!,
                callbackURLScheme: "myapp"
            ) { callbackURL, error in
                // Handle callbackURL
            }
        }
    }
}

Quick Guide to ASWebAuthenticationSession API Changes in iOS , By default SwiftUI apps come with a remarkably high level of accessibility, which is no accident – it was planned into the framework from the� SwiftUI is truly native, so your apps directly access the proven technologies of each platform with a small amount of code and an interactive design canvas. SwiftUI Tutorials Receive step-by-step guidance and in-depth details on using SwiftUI with comprehensive tutorials.

Introduction to accessibility with SwiftUI, SwiftUI was inevitably going to see big changes this year, and I'm really excited to experiment with them all – text views, color pickers, progress� Chapter 1 SwiftUI Essentials Learn how to use SwiftUI to compose rich views out of simple ones, set up data flow, and build the navigation while watching it unfold in Xcode’s preview. Creating and Combining Views

What's new in SwiftUI for iOS 14 – Hacking with Swift, While technically not required if you’re using the default UIWindow, it’s good to know how to handle. If you’re not using SwiftUI, it’s pretty simple to grab the window property from your SceneDelegate and return the value. In SwiftUI, it becomes much harder. The Environment. A new concept with SwiftUI is the Environment. It’s an area

Listening for authentication states with SwiftUI. Here’s our basic strategy: We want to create a SessionStore class which adheres to the BindableObject protocol. This class listens for authentication state changes (using a Firebase provided function) and updates our session information accordingly.

Comments
  • Is globalPresentationAnchor nil when presentationAnchor(for session:) is called?
  • Just doubled-checked and globalPresentationAnchor isn’t nil when presentationAnchor(for session:) gets called
  • Thanks, this is the one that made it click for me!
  • Thanks! There does seem to be an Instance will be immediately deallocated because property 'presentationContextProvider' is 'weak’ warning but I’ve added @State var session: ASWebAuthenticationSession? as a property of the struct and it’s still there (and I’m still getting the error). When I add the property to the struct without @State I get Cannot assign to property: 'self' is immutable errors that I can seem to resolve.
  • That error is because you are using a Struct not a Class. I would try switching it to class to see if that helps at all (won't need @State in this case) unless there is a specific reason you're using Struct.
  • I guess the specific reason is that thought SwiftUI views had to be structs that conformed to the View protocol? I’m not really sure now to switch this out for a class and haven’t been able to track down any examples other than this reddit answer from my OP that uses final class, but I wasn’t able to get it working (and am wondering if Previews still work in that scenario?).
  • Yeah, I saw that reddit post too. It seems like if that doesn't work, your only option would be to mix UIKit with SwiftUI. I don't have a ton of SwiftUI experience but that seems like the only option if the reddit answer didn't solve it for you. I do know that for UIKit, you need to hold onto the session as a property of the class otherwise it won't work.