NSURLSession.sharedSession().dataTaskWithRequest(request) being executed last - ios

I'm having a problem with NSURLSession.sharedSession().dataTaskWithRequest(request). I want to authenticate my app against some API and if the authentication is successful then the app will let the user to authorise health kit and finally fetch some data from there.
I have this in my viewDidLoad()
print ("AUTHORIZING APIGEE!!!")
authorizeApigee()
if (self.errorApigee == 0) {
print ("APIGEE AUTHORIZED!")
// We cannot access the user's HealthKit data without specific permission.
print ("AUTHORIZING HEALTHKIT!!")
getHealthKitPermission()
print ("HEALTHKIT AUTORIZED!")
} else {
print ("APIGEE UNAUTHORIZED!")
}
and this is the authorising function:
// Authenticating app with Apigee Health APIx
func authorizeApigee(){
// Send HTTP GET Request
let scriptUrl = "https://fhirsandbox-prod.apigee.net/oauth/v2"
let urlWithParams = scriptUrl + "/accesstoken?grant_type=client_credentials"
let myUrl = NSURL(string: urlWithParams);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST"
// Add Basic Authorization
let username = "****"
let password = "****"
let loginString = NSString(format: "%#:%#", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// Check for error
if error != nil
{
self.errorApigee = 1
print("error=\(error)")
return
}
// Print out response string
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
}
task.resume()
}
The problem is that even though authorizeApigee() is called, the app stops at NSURLSession.sharedSession().dataTaskWithRequest(request), goes out of the function, obviously errorApigee variable is not updated and therefore it goes to the next step of authorising health kit without authorising Apigee. Finally when health kit was authorised it goes back and authorises Apigee.
Any idea why this might happen at all?
Any tip would be greatly appreciated!

This is simple example how to use completion :
func authorizeApigee(completion: (auth: Bool) -> Void) {
// Send HTTP GET Request
let scriptUrl = "https://fhirsandbox-prod.apigee.net/oauth/v2"
let urlWithParams = scriptUrl + "/accesstoken?grant_type=client_credentials"
let myUrl = NSURL(string: urlWithParams);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST"
// Add Basic Authorization
let username = "****"
let password = "****"
let loginString = NSString(format: "%#:%#", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// Check for error
if error != nil
{
self.errorApigee = 1
print("error=\(error)")
completion(auth: false)
}
// Print out response string
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
completion(auth: true)
}
task.resume()
}
And how to use :
self.authorizeApigee { (auth) -> Void in
if (auth) {
getHealthKitPermission()
} else {
//print errors
}
}

NSURLSession.sharedSession().dataTaskWithRequest() is executed in another thread. It's so that the main application thread doesn't hang and wait for the request to complete. If you want a code block to run after the async task is complete, I suggest you use a completion block.

You need to use closure because api calling work in async manner so first change your function definition like this
func authorizeApigee(completion: (Int) -> ()){
let scriptUrl = "https://fhirsandbox-prod.apigee.net/oauth/v2"
let urlWithParams = scriptUrl + "/accesstoken?grant_type=client_credentials"
let myUrl = NSURL(string: urlWithParams);
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST"
// Add Basic Authorization
let username = "****"
let password = "****"
let loginString = NSString(format: "%#:%#", username, password)
let loginData: NSData = loginString.dataUsingEncoding(NSUTF8StringEncoding)!
let base64LoginString = loginData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions())
request.setValue(base64LoginString, forHTTPHeaderField: "Authorization")
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
// Check for error
if error != nil
{
completion(0)
print("error=\(error)")
return
}
// Print out response string
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("responseString = \(responseString)")
completion(1)
}
task.resume()
}
Now call your function like this
self.authorizeApigee() { (result) -> () in
if result == 0 {
print ("APIGEE AUTHORIZED!")
// We cannot access the user's HealthKit data without specific permission.
print ("AUTHORIZING HEALTHKIT!!")
getHealthKitPermission()
print ("HEALTHKIT AUTORIZED!")
} else {
print ("APIGEE UNAUTHORIZED!")
}
}

Related

Spotify API Authorization Error (Swift)

I've made a little App in Swift where a user can search the Spotify database for songs. I am using the Web API Console > Search for an Item. My problem is the new OAuth system where you have to sign-in and all that stuff. My authorization is ok, but when I'm trying to get an access token with the following code, it's returning me the following error: {"error":"server_error","error_description":"Unexpected status: 400"}. My code is:
let keys = "<MY_APPLICATION_KEYS>"
let url = NSURL(string: "https://accounts.spotify.com/api/token")
let session = URLSession.shared
let request = NSMutableURLRequest(url: url! as URL)
request.httpMethod = "POST"
request.setValue("Basic \(keys)", forHTTPHeaderField: "Authorization")
request.setValue("client_credentials", forHTTPHeaderField: "grant_type")
let task = session.dataTask(with: request as URLRequest) { (data, response, error) in
guard let _: Data = data, let _: URLResponse = response, error == nil else {
print(error!)
return
}
let dataString = NSString(data: data!, encoding: String.Encoding.utf8.rawValue)
print("Data: \(dataString!)")
self.parseData(JSONData: data!)
}
task.resume()
}
var accessToken = ""
func parseData(JSONData : Data) {
do {
var readableJSON = try JSONSerialization.jsonObject(with: JSONData, options: .mutableContainers) as! JSONStandard
if let token = readableJSON["access_token"] as? String {
accessToken = token
}
print("Access Token: \(accessToken)")
updateTokenInFirebase()
}
catch{
print(error)
}
Any help would be very appreciated, thank you very much in advance!
Documentation of the Web API: Web API Link
I am using on the Client Credentials Flow the first method.
I know it's been ~1 year since you posted this but I had the same issue and after a few tries was able to get it. You can test this in Playground.
import Foundation
import PlaygroundSupport
PlaygroundPage.current.needsIndefiniteExecution = true
if let url = URL(string: "https://accounts.spotify.com/api/token") {
var postRequest = URLRequest(url: url)
postRequest.httpMethod = "POST"
let bodyParams = "grant_type=client_credentials"
postRequest.httpBody = bodyParams.data(using: String.Encoding.ascii, allowLossyConversion: true)
let id = "your client id"
let secret = "your secret"
let combined = "\(id):\(secret)"
let combo = "\(id):\(secret)".toBase64()
postRequest.addValue("Basic \(combo)", forHTTPHeaderField: "Authorization")
let task = URLSession.shared.dataTask(with: postRequest) { (data, response, error) in
guard let data = data else {
return
}
print(String(data: data, encoding: String.Encoding.utf8)!)
}
task.resume()
}
extension String {
func fromBase64() -> String? {
guard let data = Data(base64Encoded: self) else {
return nil
}
return String(data: data, encoding: .utf8)
}
func toBase64() -> String {
return Data(self.utf8).base64EncodedString()
}
}
I know this is really late, but the issue is with this line:
request.setValue("client_credentials", forHTTPHeaderField: "grant_type")
According to the authorization guide, this should be in the body of the request, not the headers.

Swift - Multiple Parameters to webservice

I have the following code that should send a username and password off to a webservice, in return I get a single integer back:
func attemptLogin() {
let url:URL = URL(string: endpoint+"/LoginNew")!
let session = URLSession.shared
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let postString = "username="+txtUsername.text! + "; password="+txtPassword.text!
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request as URLRequest) {
(
data, response, error) in
guard let data = data, let _:URLResponse = response, error == nil else {
print("error")
return
}
let dataString = String(data: data, encoding: String.Encoding.utf8)
print(dataString)
}
task.resume()
}
In my function I need to add two parameters are I'm trying to do in this line:
let postString = "username="+txtUsername.text! + "; password="+txtPassword.text!
request.httpBody = postString.data(using: String.Encoding.utf8)
I am getting the following response from my web service when I run this however
Optional("Missing parameter: password.\r\n")
I am obviously not appending the parameters to the request properly but I'm not sure what I've done wrong.
It is good practice to avoid using explicit unwraps of optionals (using !), use guard let for text i UITextFields instead.
And why not separate into two methods, attemptLogin and login, which maybe can take a closure for code to execute when sign in completed? Maybe the closure can take an Result enum.
Like this:
typealias Done = (Result) -> Void
enum MyError: Error {
case unknown
}
enum Result {
case success(String)
case failure(MyError)
init(_ error: MyError) {
self = .failure(error)
}
init(_ dataString: String) {
self = .success(dataString)
}
}
func login(username: String, password: String, done: Done? = nil) {
let session = URLSession.shared
guard
let url = URL(string: endpoint+"/LoginNew"),
else { return }
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.cachePolicy = NSURLRequest.CachePolicy.reloadIgnoringCacheData
let postString = "username=\(username)&password=\(password)"
request.httpBody = postString.data(using: String.Encoding.utf8)
let task = session.dataTask(with: request) {
(data, response, error) in
guard let data = data else { done?(Result(.unknown)); return }
let dataString = String(data: data, encoding: String.Encoding.utf8)
done?(Result(dataString))
}
task.resume()
}
func attemptLogin() {
guard
let username = txtUsername.text,
let password = txtPassword.text
else { return }
login(username: username, password: password) {
result in
swicth result {
case .success(let dataString):
print(dataString)
case .failure(let error):
print("Failed with error: \(error)")
}
}
}
Disclaimer: Have not tested the code above, but hopefully it compiles (at least with very small changes).

How to check if the user is a registered user so that he can log in using ios swift

I am trying to enable a user to login only if he is registered. I have a registration page, where I have written a web service to successfully register the user, then the user tries to login. So basically I need to write a web service to POST login details and check if the user is registered- if yes then direct to home page else show error. I managed to write code for posting the data which is as follows:
LoginButton action:
#IBAction func loginButton(sender: UIButton) {
if userNameTextField.isValidEmail(userNameTextField.text!)
{
let userName = userNameTextField.text
let password = passwordTextField.text
//Data dictionary to save user data
let parameterDictionary:[String : String] = [
"first_name": userName!,
"password": password!]
let additionalUrlString = "users/register"
//Calling postDataRequest to send data to the web service and wait for response
CommonFunctions.sharedInstance.postdataRequest(parameterDictionary, accessToken: "", urlStr: additionalUrlString, Block: {
(AnyObject) -> Void in
dispatch_async(dispatch_get_main_queue(), {
if (AnyObject.isKindOfClass(NSDictionary)){
if(AnyObject["status"] as! NSObject == 200) /*Code:200 for success in the api*/{
self.view.makeToast("Login Successfully")
let delayTime = dispatch_time(DISPATCH_TIME_NOW, Int64(2 * Double(NSEC_PER_SEC)))
dispatch_after(delayTime, dispatch_get_main_queue()) {
let nextViewController = self.storyboard?.instantiateViewControllerWithIdentifier("HomePageViewController") as! HomePageViewController
self.navigationController?.pushViewController(nextViewController, animated: true)
}
}else{
print("Login Unsuccessful")
dispatch_async(dispatch_get_main_queue(), {
// let mwsg: String = AnyObject["message"]! as! String
self.view.makeToast(AnyObject["message"]! as! String)
})
}
}else{
print("No server response")
}
})
})
} else {
userNameTextField.showAlertDialog("Invalid email ID", viewController: self)
}
}
PostDataRequest:
func postdataRequest(parameter: Dictionary<String, String>, accessToken: String, urlStr: String, Block block: completionBlockType){
let session = NSURLSession.sharedSession()
let url:NSURL = NSURL(string: baseURL + urlStr)! //Create url of the api
let request:NSMutableURLRequest = NSMutableURLRequest(URL: url) //create request object to be sent
request.HTTPMethod = "POST" //set http method as POST
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
setBodyContent(parameter, request: request) //Convert data dictionary to string to be sent to the server
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error in
// dispatch_async(dispatch_get_main_queue(), {
// IJProgressView.shared.hideProgressView()
// })
let strData = NSString(data: data!, encoding: NSUTF8StringEncoding)
print("Body: \(strData)!") //Displays user data sent back by the server after saving
let datastring = try! NSJSONSerialization.JSONObjectWithData(data!, options:NSJSONReadingOptions.MutableContainers) as! NSDictionary
block(datastring)
})
task.resume()
}
Can someone help me to check if the user is registered or not

How to securely pass variables to PHP script from Swift application, or make this code more secure

I am using NSURL to call a PHP script that interacts with with a MySQL database. I am passing the variables through the URL, which can be intercepted and hacked. Is there any way to pass form data with Swift, or securely pass variables to a URL using a similar structure that I have now? I have completed the application only to realize this vulnerability. If there is no way to alter this code, I guess I will have to rewrite a bunch... Here is the structure code I have:
let username = "bob"
let myUrl = NSURL(string: "http://127.0.0.1/phpscript.php?username=\(username)")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
}
}
}
task.resume()
As you can see, it would be simple to gather the username just by seeing the URL. Do you know of a way to pass variables other than through the URL, without having to rewrite all of this code?Thank you very much
You could pass username through post by adding between ..."POST" and let task... this code:
let postString = "username=\(username)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
end result:
let username = "bob"
let myUrl = NSURL(string: "http://127.0.0.1/phpscript.php")
let request = NSMutableURLRequest(URL: myUrl!)
request.HTTPMethod = "POST"
let postString = "username=\(username)"
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding)
let task = NSURLSession.sharedSession().dataTaskWithRequest(request) {
data, response, error in
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_HIGH, 0)) {
let responseString = NSString(data: data!, encoding: NSUTF8StringEncoding)
if error != nil {
print("Error: \(error)")
}
dispatch_async(dispatch_get_main_queue()) {
self.testLabel.text = "\(responseString!)"
}
}
}
task.resume()

Swift JSON Parse execution on viewLoad

Im currently struggling with a small issue on my viewController. I have stored user information to hold the users logged in status, anyways when the user opens the app they are shown a "Profile" page. Im trying to (in this example return the email) return values related to that user as the app is opened but dont know how one would execute the code on the controller itself, my JSON is posted below and returns the correct needed information. However my Label will still not get the value of the users Email stored when they open the app. Label stays blank!
let myUrl = NSURL(string: "http://www.mywebsite.co.za/php/scripts/getuserEmail.php");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST";
let postString = "user_id=\(userId)";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
println(" response = \(response)")
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("response data = \(responseString)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json
{
var userEmail = parseJSON["userEmail"] as? String
self.usersEmail.text = userEmail
}
}
Would basically like the code to execute and update my label as that view opens, as the user has the ability to change the email from another viewController already so the email should update accordingly. Any other information will gladly be provided.
Added: println to console will not execute past the assigning of the myUrl variable. The reply below will still not work however this is my full code on viewWillAppear:
override func viewWillAppear(animated: Bool) {
super.viewDidAppear(animated)
let userFirstName = NSUserDefaults.standardUserDefaults().stringForKey("userFirstName")
let userLastName = NSUserDefaults.standardUserDefaults().stringForKey("userLastName")
var userFullName = userFirstName!
userFullNameLabel.text = userFullName
let userId:String? = NSUserDefaults.standardUserDefaults().stringForKey("userId")
let imageUrl = NSURL(string:"http://www.mywebsite.co.za/profile-pictures/\(userId!)/user-profile.jpg")
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
let imageData = NSData(contentsOfURL: imageUrl!)
if(imageData != nil )
{
dispatch_async(dispatch_get_main_queue(),{
self.profilePictureImageVIew.image = UIImage(data: imageData!)
self.profilePictureImageVIew.backgroundColor = UIColor.clearColor()
})
}
}
// Send HTTP POST
let myUrl = NSURL(string: "http://www.mywebsite.co.za/php/scripts/getuserEmail.php");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST";
let postString = "user_id=\(userId)";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
// You can print out response object
println("******* response = \(response)")
// Print out reponse body
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("****** response data = \(responseString)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json
{
var userEmail = parseJSON["userEmail"] as? String
self.usersEmail.text = userEmail
}
}
}
If I understand correctly, you're saying the page appears and your call to the web has not finished and assigned to the self.usersEmail.text yet. What I did in my project was dispatch the commands in a block like yours asynchronously on the main queue. Try doing this inside your if statement:
dispatch_async(dispatch_get_main_queue()) {
var userEmail = parseJSON["userEmail"] as? String
self.usersEmail.text = userEmail
}
Hopefully this helps. It may be improper, but hopefully somebody can let me know if it is. It works for me so far.
You also need to resume the task after its block:
let myUrl = NSURL(string: "http://www.mywebsite.co.za/php/scripts/getuserEmail.php");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST";
let postString = "user_id=\(userId)";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
// You can print out response object
println("******* response = \(response)")
// Print out reponse body
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("****** response data = \(responseString)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json
{
var userEmail = parseJSON["userEmail"] as? String
self.usersEmail.text = userEmail
}
}
task.resume()
Thank you grdavis, marked as answer, I had however needed to change a few things to get the result I wanted and I just wanted to post the updated answer.
Moved to viewdidLoad and not viewWillAppear
When monitoring console the userID was not alone when being sent over the URL thus adding a '!' to the string fixed this too before sending the url
The resume worked
Below is my updated code, thank you again. Label updates like desired:
// Send HTTP POST
let userId:String? = NSUserDefaults.standardUserDefaults().stringForKey("userId")
let myUrl = NSURL(string: "http://www.mywebsite.co.za/php/scripts/getuserEmail.php");
let request = NSMutableURLRequest(URL:myUrl!);
request.HTTPMethod = "POST";
let postString = "user_id=\(userId!)";
request.HTTPBody = postString.dataUsingEncoding(NSUTF8StringEncoding);
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)){
let task = NSURLSession.sharedSession().dataTaskWithRequest(request)
{
data, response, error in
// You can print out response object
println("******* response = \(response)")
// Print out reponse body
let responseString = NSString(data: data, encoding: NSUTF8StringEncoding)
println("****** response data = \(responseString)")
var err: NSError?
var json = NSJSONSerialization.JSONObjectWithData(data, options: .MutableContainers, error: &err) as? NSDictionary
if let parseJSON = json
{
var userEmail = parseJSON["userEmail"] as? String
println("******* userEmail = \(userEmail)")
self.usersEmail.text = userEmail
}
}
task.resume()
}

Resources