I'm developing an iOS app in Swift 3 (I'm a complete Swift n00b.) I am authenticating against an external app, to get an access token back, but the code is not redirecting back to my app after authenticating against the external app.
AppDelegate.swift
func application(_ application: UIApplication, openURL url: NSURL, sourceApplication: String?, annotation: Any) -> Bool {
print(url)
DispatchQueue.main.async {
print(url.scheme)
if (url.scheme == "myapp"){
print("i just caught a url and i feel fine and the url be says ", url)
}
}
return true
}
in class that handles response from external app:
Alamofire.request(url, method: .post, parameters: parameters, encoding: JSONEncoding.default)
.responseJSON { response in
print(response)
if((response.result.value) != nil) {
let swiftyJsonVar = JSON(response.result.value!)
if let resData = swiftyJsonVar[key].string {
let autostart_data = swiftyJsonVar[key].string
... snip ...
let url = URL(string: "extapp:///?key=\(data!)&redirect=\(myapp)://key")
UIApplication.shared.open(url!, options: [:])
... snip ...
}
}
}
Is it possible to redirect back to my app, with Alamofire, once the response is received? I have been researching online with Google for the past two days and have tried out many possible solutions, but have come to nothing so far. I'd appreciate any help!
Problem is not with Alamofire. It's to do with the external app's API and my iOS app not being able to redirect back.
Related
I am implementing social login with TikTok in my app, From official documentation I implemented Basic setup and connected with my AppDelegate https://developers.tiktok.com/doc/getting-started-ios-quickstart-swift. Implemented loginkit with there sample code but request.send completionBlock is not getting any response or do not enter into completion block after we authorised from TikTok app. Please help if any one has implemented tiktok login kit in iOS.
/* STEP 1 */
let scopes = "user.info.basic,video.list" // list your scopes
let scopesSet = NSOrderedSet(array:scopes)
let request = TikTokOpenSDKAuthRequest()
request.permissions = scopesSet
/* STEP 2 */
request.send(self, completion: { resp -> Void in
/* STEP 3 */
if resp.errCode == 0 {
/* STEP 3.a */
let clientKey = ... // you will receive this once you register in the Developer Portal
let responseCode = resp.code
// replace this baseURLstring with your own wrapper API
let baseURlString = "https://open-api.tiktok.com/demoapp/callback/?code=\(responseCode)&client_key=\(clientKey)"
let url = NSURL(string: baseURlstring)
/* STEP 3.b */
let session = URLSession(configuration: .default)
let urlRequest = NSMutableURLRequest(url: url! as URL)
let task = session.dataTask(with: urlRequest as URLRequest) { (data, response, error) -> Void in
/* STEP 3.c */
}
task.resume()
} else {
// handle error
}
}
Thanks to author's comment I figured that out too. In my case, there was no SceneDelegate in the project, so I had 3 url-related methods implemented in AppDelegate as per TikTok's documentation:
1:
func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any]) -> Bool
2:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any)
3:
func application(_ application: UIApplication, handleOpen url: URL) -> Bool
The docs also suggested that 1st method should use a default value of [:] for options, which is plainly wrong so I removed it.
I also had Firebase dynamic links implemented in the 1st method:
if let dynamicLink = DynamicLinks.dynamicLinks().dynamicLink(fromCustomSchemeURL: url) {
self.handleDynamicLink(dynamicLink)
return true
}
Turns out, if you remove the 1st method completely and move Firebase DL handling to method #2 everything starts working! Dynamic links are handled and TT's completion block finally gets called
I am using AppAuth on my code.
I manage to authenticate successful , but when the SFSafariViewController gets dismiss from my Controller , the redirect url does not trigger the AppDelegate func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool
The redirect URL is my Bundle Identifier name : BundleIdentifier://authenticate
I have setup in info.plist url Schemes and url identifier which they have the same name.
When I run my code setting a break point on this func I can see my redirect url correct for standarizedURL and standarizedRedirectURL
- (BOOL)shouldHandleURL:(NSURL *)URL {
NSURL *standardizedURL = [URL standardizedURL];
NSURL *standardizedRedirectURL = [_request.redirectURL standardizedURL];
return OIDIsEqualIncludingNil(standardizedURL.scheme, standardizedRedirectURL.scheme) &&
OIDIsEqualIncludingNil(standardizedURL.user, standardizedRedirectURL.user) &&
OIDIsEqualIncludingNil(standardizedURL.password, standardizedRedirectURL.password) &&
OIDIsEqualIncludingNil(standardizedURL.host, standardizedRedirectURL.host) &&
OIDIsEqualIncludingNil(standardizedURL.port, standardizedRedirectURL.port) &&
OIDIsEqualIncludingNil(standardizedURL.path, standardizedRedirectURL.path);
But when AppAuth finishes the authentication and I have an access token , func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool doesn't get triggered.
Any idea why?
Here is my code
class func signInAuth(discoveryURLstr: String,presenter : UIViewController,completionHandler: #escaping ( (OIDAuthState?,Error?) -> () )){
guard let discoveruURL = URL(string: discoveryURLstr) else{
completionHandler(nil,AuthErrors.InvalidDiscoveryURL)
return
}
appAuthDiscoverConfiguration(discoveryURL: discoveruURL) { (configurationFile, error) in
guard let configurationFile = configurationFile else {
completionHandler(nil,AuthErrors.InvalidConfigurationFile)
return
}
let authRequest = appAuthRequest(configurationFile: configurationFile)
self.appAuthenticationSession = OIDAuthState.authState(byPresenting: authRequest, presenting: presenter, callback: { (state, error) in
if let error = error {
//self.authState = nil
completionHandler(nil,error)
return
}
if let state = state {
self.authState = state
completionHandler(state,nil)
}else{
completionHandler(nil,AuthErrors.InvalideState)
}
})
}
}
class func appAuthDiscoverConfiguration(discoveryURL : URL, completionHandler: #escaping ((OIDServiceConfiguration?,Error?) -> ())) {
OIDAuthorizationService.discoverConfiguration(forDiscoveryURL: discoveryURL) { (configuration, error) in
if let error = error {
completionHandler(nil,error)
return
}else{
guard let configurationFile = configuration else {
completionHandler(nil,AuthErrors.InvalidConfigurationFile)
return
}
completionHandler(configurationFile,nil)
}
}
}
class func appAuthRequest(configurationFile : OIDServiceConfiguration) -> OIDAuthorizationRequest{
return OIDAuthorizationRequest(configuration: configurationFile, clientId: AppAuthConstants.clientId, clientSecret: nil, scope: AppAuthConstants.scope, redirectURL: AppAuthConstants.redirectURL, responseType: AppAuthConstants.responseType, state: nil, nonce: nil, codeVerifier: nil, codeChallenge: nil, codeChallengeMethod: nil, additionalParameters: AppAuthConstants.additionalParameters)
}
On iOS 12, App-Auth uses ASWebAuthenticationSession, and on iOS 11, it uses the now-deprecated SFAuthenticationSession instead of requiring the app to support handling the redirect manually. To support earlier versions of iOS, you still need your code in the func application(_ app: UIApplication, open url: URL, options: [UIApplication.OpenURLOptionsKey : Any] = [:]) -> Bool method.
For reference, you can see what AppAuth is doing under the covers here. Also, this is a great answer that explains how to generically get an OAuth token on iOS without using AppAuth.
I'm new in iOS dev and I do not understand one think. So I have gz file and inside gzip there is xml file. I need to download gz file, every time user start the app. First time when I start my app I use this code to get data. Problem is my xml file was offline on server for few day and my app always start without problem and show data with no problem. So all my files was cached on device?. I want my data is retrieved every time while the user start the application. I am not sure do I did something wrong? Thanks
let url = NSURL(string: "http://sitename/xxx.gz")
if url != nil {
let task = URLSession.shared.dataTask(with: url! as URL, completionHandler: { (data, response, error) -> Void in
if error == nil {
let nsdata = data as NSData?
let content = nsdata?.gunzipped()
let dataContent = content as Data?
let urlContent = NSString(data: dataContent!, encoding: String.Encoding.ascii.rawValue) as NSString!
let xml = XMLParser()
xml.getDataforTable(data: urlContent as! String)
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "XmlDataLoaded"), object: nil))
} else {
NotificationCenter.default.post(Notification(name: Notification.Name(rawValue: "DataNotLoaded"), object: nil))
}
})
task.resume()
}
enter code here
in AppDelegate.swift
func application(application: UIApplication, didFinishLaunchingWithOptions launchOptions: [NSObject: AnyObject]?) -> Bool {
call your web services method inside this method
}
I am developing a very basic iOS app with Swift. Just to read the heart rate data. I am using SFSafariViewController. As known, I first need to register my app on dev.fitbit.com. The registration form requires a callback URL to be entered.
After logging in successfully, FitBit always redirects me back to that entered callback URL. What should I do/code/configure to be able to redirect user back to my iOS app after logging in successfully?
What you need to do is to add the application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool function to your AppDelegate and then create an url scheme for your application as an identifier. To create an url scheme go to your application target > Info > URL Types (at the bottom). Then just add the following in your AppDelegate:
func application(_ application: UIApplication, open url: URL, sourceApplication: String?, annotation: Any) -> Bool {
DispatchQueue.main.async {
// Conctrol so that we´re coming from the right application
if (url.scheme == "The url scheme that you created"){
// Navigate to the viewController you want
let storyboard = UIStoryboard(name: "Main", bundle: nil)
let controller = storyboard.instantiateViewController(withIdentifier: "WebView") as! WebViewController
self.window?.rootViewController!.present(controller, animated: true, completion: { () -> Void in
})
}
}
return true
}
Try using below 3 steps in your application.
You are using "fitbit", so I am considering your are using OAuth2.0 for login and get access-token.
Step 1 : Set up you URL Schemes.
Step 2 : In your AppDelegate class
func application(_ app: UIApplication, open url: URL, options: [UIApplicationOpenURLOptionsKey : Any] = [:]) -> Bool {
if (url.host == "oauth-swift") {
OAuthSwift.handle(url: url)
}
return true
}
In Above function what we did is, we check the URL comes to handleOpenUrl method, and check weather it is the right url call back is coming or not.
Step 3 : Set the proper call back URL in you OAuth Handler.
oauthswift = OAuth2Swift(
consumerKey: "********",
consumerSecret: "********",
authorizeUrl: "your authorisation url",
responseType: "token"
)
let handle = oauthswift.authorize(
withCallbackURL: URL(string: "oauth-swift://oauth-callback/fitbit")!,
scope: "your application scope", state:"state",
success: { credential, response, parameters in
print(credential.oauth_token)
},
failure: { error in
print(error.localizedDescription)
}
)
In above step, we set the call back url starting with "oauth-swift:", so it will be work as a host of your call back url.
Image and Code Courtesy : I have tried to explain a solution of your
problem in easy words. And all information of this answers are
originally documented and explained on this URL :
https://github.com/OAuthSwift/OAuthSwift
I'm having this weird issue in which a newly created URLSessionUploadTask gets cancelled instantly. I'm not sure if it's a bug with the current beta of Xcode 8.
I suspect it might be a bug because the code I'm about to post ran fine exactly once. No changes were made to it afterwards and then it simply stopped working. Yes, it literally ran once, and then it stopped working. I will post the error near the end.
I will post the code below, but first I will summarize how the logic here works.
My test, or user-exposed API (IE for use in Playgrounds or directly on apps), calls the authorize method. This authorize method will in turn call buildPOSTTask, which will construct a valid URL and return a URLSessionUploadTask to be used by the authorize method.
With that said, the code is below:
The session:
internal let urlSession = URLSession(configuration: .default)
Function to create an upload task:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data? = nil
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
The authentication function, which uses a task created by the above function:
public func authorize(withCode code: String?, completion: AccessTokenExchangeCompletionHandler) {
// I have removed a lot of irrelevant code here, such as the dictionary building code, to make this snippet shorter.
let obtainTokenTask = buildPOSTTask(onURLSession: self.urlSession, appendingPath: "auth/access_token", withPostParameters: nil, getParameters: body, httpHeaders: nil) { (data, response, error) in
if let err = error {
completion(error: err)
} else {
print("Response is \(response)")
completion(error: nil)
}
}
obtainTokenTask.resume()
}
I caught this error in a test:
let testUser = Anilist(grantType: grant, name: "Test Session")
let exp = expectation(withDescription: "Waiting for authorization")
testUser.authorize(withCode: "a valid code") { (error) in
if let er = error {
XCTFail("Authentication error: \(er.localizedDescription)")
}
exp.fulfill()
}
self.waitForExpectations(withTimeout: 5) { (err) in
if let error = err {
XCTFail(error.localizedDescription)
}
}
It always fails instantly with this error:
Error Domain=NSURLErrorDomain Code=-999 "cancelled" UserInfo={NSErrorFailingURLKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED,
NSLocalizedDescription=cancelled,
NSErrorFailingURLStringKey=https://anilist.co/api/auth/access_token?client_secret=REMOVED&grant_type=authorization_code&redirect_uri=genericwebsitethatshouldntexist.bo&client_id=ibanez-hod6w&code=REMOVED}
Here's a few things to keep in mind:
The URL used by the session is valid.
All credentials are valid.
It fails instantly with a "cancelled" error, that simply did not happen before. I am not cancelling the task anywhere, so it's being cancelled by the system.
It also fails on Playgrounds with indefinite execution enabled. This is not limited to my tests.
Here's a list of things I have tried:
Because I suspect this is a bug, I first tried to clean my project, delete derived data, and reset all simulators. None of them worked.
Even went as far restarting my Mac...
Under the small suspicion that the upload task was getting deallocated due to it not having any strong pointers, and in turn calling cancel, I also rewrote authorize to return the task created by buildPOSTTask and assigned it to a variable in my test. The task was still getting cancelled.
Things I have yet to try (but I will accept any other ideas as I work through these):
Run it on a physical device. Currently downloading iOS 10 on an iPad as this is an iOS 10 project. EDIT: I just tried and it's not possible to do this.
I'm out of ideas of what to try. The generated logs don't seem to have any useful info.
EDIT:
I have decided to just post the entire project here. The thing will be open source anyway when it is finished, and the API credentials I got are for a test app.
ALCKit
After struggling non-stop with this for 6 days, and after googling non-stop for a solution, I'm really happy to say I have finally figured it out.
Turns out that, for whatever mysterious reason, the from: parameter in uploadTask(with:from:completionHandler) cannot be nil. Despite the fact that the parameter is marked as an optional Data, it gets cancelled instantly when it is missing. This is probably a bug on Apple's side, and I opened a bug when I couldn't get this to work, so I will update my bug report with this new information.
With that said, everything I had to do was to update my buildPOSTTask method to account for the possibility of the passed dictionary to be nil. With that in place, it works fine now:
internal func buildPOSTTask(onURLSession urlSession: URLSession, appendingPath path: String, withPostParameters postParams: [String : String]?, getParameters getParams: [String : String]?, httpHeaders: [String : String]?, completionHandler completion: URLSessionUploadTaskCompletionHandler) -> URLSessionUploadTask {
let fullURL: URL
if let gets = getParams {
fullURL = buildURL(appendingPath: path, withGetParameters: gets)
} else {
fullURL = URL(string: path, relativeTo: baseURL)!
}
var request = URLRequest(url: fullURL)
request.httpMethod = "POST"
var postParameters: Data
if let posts = postParams {
do {
postParameters = try JSONSerialization.data(withJSONObject: posts, options: [])
} catch let error as NSError {
fatalError("[\(#function) \(#line)]: Could not build POST task: \(error.localizedDescription)")
}
} else {
postParameters = Data()
}
let postTask = urlSession.uploadTask(with: request, from: postParameters, completionHandler: completion)
return postTask
}
Are you by any chance using a third party library such as Ensighten? I had the exact same problem in XCode 8 beta (works fine in XCode 7) and all of my blocks with nil parameters were causing crashes. Turns out it was the library doing some encoding causing the issue.
For me, this was a weak reference causing the issue, so I changed
completion: { [weak self] (response: Result<ResponseType, Error>)
to
completion: { [self] (response: Result<ResponseType, Error>)