SafariViewController: How to grab OAuth token from URL?
Figured it out. Some of the methods were pre iOS 9 and now deprecated. I also had the application
function in the ViewController I created when it should have been defined in the AppDelagate.swift
. For example
Added at end of AppDelegate.swift
func application(app: UIApplication, openURL url: NSURL, options: [String : AnyObject]) -> Bool {
print("app: \(app)")
// print OAuth response URL
print("url: \(url)")
print("options: \(options)")
if let sourceApplication = options["UIApplicationOpenURLOptionsSourceApplicationKey"] {
if (String(sourceApplication) == "com.testApp.Incognito") {
NSNotificationCenter.defaultCenter().postNotificationName(kSafariViewControllerCloseNotification, object: url)
return true
}
}
return true
}
ViewController.swift
import SafariServices
let kSafariViewControllerCloseNotification = "kSafariViewControllerCloseNotification"
// facebook OAuth URL
let authURL = NSURL(string: "https://www.facebook.com/dialog/oauth?client_id=3627644767&redirect_uri=https://www.facebook.com/connect/login_success.html&scope=basic_info,email,public_profile,user_about_me,user_activities,user_birthday,user_education_history,user_friends,user_interests,user_likes,user_location,user_photos,user_relationship_details&response_type=token")
class ViewController: UIViewController, SFSafariViewControllerDelegate {
var safariVC: SFSafariViewController?
@IBOutlet weak var loginButton: UIButton!
@IBAction func loginButtonTapped(sender: AnyObject) {
safariVC = SFSafariViewController(URL: authURL!)
safariVC!.delegate = self
self.presentViewController(safariVC!, animated: true, completion: nil)
}
override func viewDidLoad() {
super.viewDidLoad()
NSNotificationCenter.defaultCenter().addObserver(self, selector: #selector(ViewController.safariLogin(_:)), name: kSafariViewControllerCloseNotification, object: nil)
}
func safariLogin(notification: NSNotification) {
// get the url from the auth callback
let url = notification.object as! NSURL
// Finally dismiss the Safari View Controller with:
self.safariVC!.dismissViewControllerAnimated(true, completion: nil)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
func safariViewControllerDidFinish(controller: SFSafariViewController) {
controller.dismissViewControllerAnimated(true) { () -> Void in
print("You just dismissed the login view.")
}
}
func safariViewController(controller: SFSafariViewController, didCompleteInitialLoad didLoadSuccessfully: Bool) {
print("didLoadSuccessfully: \(didLoadSuccessfully)")
}
}
open url function not called in AppDelegate when trying to grab token from oauth url
So it was actually a mixture of things that I did not notice...
- The URL I was trying to use there was wrong: It should not be
http://
butyourApp://
since you want yourApp to handle the
callback. On some forum I read that Strava only allows http redirect
uris, which made me try it that way, but that's actually wrong, as
the documentation states:
URL to which the user will be redirected with the authorization code, must be to the callback domain associated with the application, or its sub-domain,
localhost
and127.0.0.1
are white-listed.
And we got to the next thing, namely:
- You should check what you name your application on the Strava admin page at Settings/My Api Application.
Following the yourApp example, it should be something like this:
(My mistake was, that I did not provide a valid/matching callback domain.)
- Finally, your plist file should be also set accordingly:
<key>CFBundleURLTypes</key>
<array>
<dict>
<key>CFBundleURLName</key>
<string>com.yourdomain.yourApp</string>
<key>CFBundleURLSchemes</key>
<array>
<string>yourApp</string>
</array>
</dict>
</array>
And it works like charm: it does not actually matter if it's iOS 9 or 11, or you use SFSafariViewController
VS you leave the app with UIApplication.shared.openURL()
etc...
Good luck ;)
Swift OAuth2.0 with redirectURI
If you are targeting iOS 13 you can use the new AuthenticationServices library provided by Apple.
It will work on both macOS and iOS.
Maybe this would help other developers, I create a simple and small swift package to handle OAuth2 in Swift, you can check the demo project it works very well /p>
https://github.com/hadiidbouk/SimpleOAuth2
Edit:
You are passing the wrong URLs, they should be like this
let request: OAuth2Request = .init(authUrl: "https://stackoverflow.com/oauth",
tokenUrl: "https://stackoverflow.com/oauth/access_token/json",
clientId: "<<your client id>>",
redirectUri: "redirect-uri://stackexchange.com",
clientSecret: "<<your client secret>>",
scopes: ["private_info"])
Can I get cookies from Safari in a SFSafariViewController?
REQUIREMENTS
So it seems you want a solution to invoke secured web content from a mobile app, and to avoid an extra login. It is a common requirement and I will be adding some stuff to my blog on this topic over the next month or so.
STATE OF THE INDUSTRY
The problem with the above is that third party cookies, such as those issued by Identity Providers, are often dropped by default these days due to browser security initiatives such as Intelligent Tracking Prevention changes - which is ON by default in Safari:
COOKIE PROPERTIES
Worth checking that your cookies are issued with SameSite=None, which will give you the best options for a third party cookie based solution.
MOBILE FIRST DESIGNS
In an OAuth world, in order to meet the requirements, it is likely to be necessary to send a token from the mobile UI to the web UI, which of course has prerequisites that need to be designed for:
- Web UI must use tokens
- Web UI must use different strategies for token handling depending on the host
OPTION 1
One option is to use a mobile web view to show the web content - see my code below:
- Web UI Code to ask the host for tokens
- Mobile UI Code to service these requests
OPTION 2
Another option is to send something representing the token in a query string parameter from the mobile app to the Web UI, in which case you need to ensure that:
- No usable tokens are recorded in web server logs
- The token has a one time use only
A typical implementation would look like this:
- Mobile UI calls an /api/token/encrypt endpoint
- API stores a token hash in a database and returns an encrypted value with a short time to live
- Token is sent from the Mobile App to the Web UI
- Web UI calls an /api/token/decrypt endpoint to get the real token
- The API's decrypt implementation deletes the database entry
Handle completion if no success when opening url with SafariViewController
I don't think SFSafariViewController
provides much interaction/feedback with your app.
As per Apple documentation:
The user's activity and interaction with SFSafariViewController are
not visible to your app, which cannot access AutoFill data, browsing
history, or website data.
Choosing the Best Web Viewing Class
If your app lets users view
websites from anywhere on the Internet, use the SFSafariViewController
class. If your app customizes, interacts with, or controls the display
of web content, use the WKWebView class.
As Apple suggests, you may want to take a look at WKWebView class.
Related Topics
How to Left Align the Title of a Navigation Bar in Xcode
Use Queue and Semaphore for Concurrency and Property Wrapper
Swift Extension: Same Extension Function in Two Modules
Swift Nspredicate Throwing Exc_Bad_Access(Code=1, Address=0X1) When Compounding Statements
How to Dispatch Functions in Swift the Right Way
Generating Resource_Bundle_Accessor, Type 'Bundle' Has No Member 'Module'
Hide Navigation Bar Without Losing Swipe Back Gesture in Swiftui
Convert Between Decimal, Binary and Hexadecimal in Swift
Init(Start:End:)' Is Deprecated: It Will Be Removed in Swift 3. Use the '..<' Operator
Why Non Optional Any Can Hold Nil
Delete Data from Coredata Swift
Simpliest Solution to Check If File Exists on a Webserver. (Swift)
Variable 'Xxx' Was Never Mutated, Consider Changing to 'Let'
Println Dictionary Has "Optional"
Why Is the Shorthand Argument Name $0 Returning a Tuple of All Parameters