Reputation: 65
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?
Upvotes: 4
Views: 7858
Reputation: 21
Ronni - I ran into the same problem but finally got the ShimController() to work and avoid the warning. I got sucked into the solution but forgot to instantiate the class. Look for my "<<" comments below. Now the auth is working and the callback is firing like clockwork. The only caveat here is I'm authorizing something else - not Spotify.
var session: ASWebAuthenticationSession?
var shimController = ShimViewController() // << instantiate your object here
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 = shimController // << then reference it here
session.start()
}
Upvotes: 2
Reputation: 303
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
}
}
}
}
Upvotes: 1
Reputation: 71
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()
}
}
}
Upvotes: 3
Reputation: 1703
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()
}
Upvotes: 3