Posting to a secure API with swift - ios

I'm trying to post to an API secured with a key (MailGun) with swift but it appears that my key is never utilized as I receive a Forbidden 401 error (Unauthorized - No valid API key provided) according to https://documentation.mailgun.com/api-intro.html#errors
I've verified the URL and key are correct by posting using curl, but I am unable to figure out why my key is not used here. I'm hoping someone can point in the right direction as to why this isn't authenticating correctly
My code is as such but I have replaced all the personal info with <>:
// Email the FBO with desired information
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.mailgun.net/v3/<My Domain>/messages")!)
request.HTTPMethod = "POST"
let data = "from: Excited User <scheduler#<mg.mydomain.com>>&to: [bar#example.com,<my email>]&subject:Hello&text:Testinggsome Mailgun awesomness!"
request.HTTPBody = data.dataUsingEncoding(NSASCIIStringEncoding)
request.setValue("key-<my key>", forHTTPHeaderField: "api")
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
Update:
Banged away at it for a few hours and I still can't get my head around it. Maybe I'm not exactly sure what you mean? I can successfully get a response with curl by using:
curl -s --user 'api:key-<my personal key>' https://api.mailgun.net/v3/mg.<my domain>.com/messages -F from='Reservation Scheduler <scheduler#mg.<my domain>.com>' -F to=reservations#<my domain>.com -F subject='Curl Test' -F text='Test from terminal'
I tried inputting it explicitly like so:
request.setValue("api", forHTTPHeaderField: "username")
request.setValue("key-<my key>", forHTTPHeaderField: "password")
It looks to me like the basic auth credentials are never sent? How can I be sure that the fields are "user" and "password"?

After verifying my header appeared to be missing the authentication section of the header I was able to get this working properly with a large HTTP response. I put the full path into Keys.plist so that I can upload my code to github and broke out some of the arguments into variables so I can have them programmatically set later down the road.
// Email the FBO with desired information
// Parse our Keys.plist so we can use our path
var keys: NSDictionary?
if let path = NSBundle.mainBundle().pathForResource("Keys", ofType: "plist") {
keys = NSDictionary(contentsOfFile: path)
}
if let dict = keys {
// variablize our https path with API key, recipient and message text
let mailgunAPIPath = dict["mailgunAPIPath"] as? String
let emailRecipient = "bar#foo.com"
let emailMessage = "Testing%20email%20sender%20variables"
// Create a session and fill it with our request
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: NSURL(string: mailgunAPIPath! + "from=FBOGo%20Reservation%20%3Cscheduler#<my domain>.com%3E&to=reservations#<my domain>.com&to=\(emailRecipient)&subject=A%20New%20Reservation%21&text=\(emailMessage)")!)
// POST and report back with any errors and response codes
request.HTTPMethod = "POST"
let task = session.dataTaskWithRequest(request, completionHandler: {(data, response, error) in
if let error = error {
print(error)
}
if let response = response {
print("url = \(response.URL!)")
print("response = \(response)")
let httpResponse = response as! NSHTTPURLResponse
print("response code = \(httpResponse.statusCode)")
}
})
task.resume()
}
The Mailgun Path is in Keys.plist as a string called mailgunAPIPath with the value:
https://API:key-<my key>#api.mailgun.net/v3/<my domain>.com/messages?
Hope this offers a solution to anyone else having issues with MailGun and wanting to avoid a 3rd party solution!

Related

Sending HTTP POST request with Swift to Discord Webhook results in Response Error

I'm trying to create a URL Request in Swift to send an HTTP POST request to a Discord Webhook, and the request completes in a failure defined as 'responseProblem'. I'm not sure where the response problem is originating from.
Discord should technically be able to accept HTTP requests, and a bunch of research into the issue has led me nowhere. I've worked with Discord webhooks before, but I've never played around with this kind of stuff before, so I'm kinda stuck for what I should do now.
import Foundation
enum APIError:Error {
case responseProblem
case decodingProblem
case encodingProblem
}
struct APIRequest {
let resourceURL: URL
init(endpoint: String) {
let resourceString = "<discord-webhook-url-removed-for-privacy>"
guard let resourceURL = URL(string: resourceString) else {fatalError()}
self.resourceURL = resourceURL
}
func save (_ messageToSave:Message, completion: #escaping(Result<Message, APIError>) -> Void ) {
do {
var urlRequest = URLRequest(url: resourceURL)
urlRequest.httpMethod = "POST"
urlRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
urlRequest.httpBody = try JSONEncoder().encode(messageToSave)
let dataTask = URLSession.shared.dataTask(with: urlRequest) { data, response, _ in
guard let httpResponse = response as? HTTPURLResponse, httpResponse.statusCode == 200, let
jsonData = data else {
completion(.failure(.responseProblem)) // <<< 'responseProblem' is outputted in console as the error
return
}
do {
let messageData = try JSONDecoder().decode(Message.self, from: jsonData)
completion(.success(messageData))
} catch {
completion(.failure(.decodingProblem))
}
}
dataTask.resume()
} catch {
completion(.failure(.encodingProblem))
}
}
}
When I run this Swift program, I expected it to send a request to the Discord Webhook to send a message into it's designated channel. However, the only error outputted into the console is responseProblem. Please let me know if there is anything further I need to add to get the root cause of the problem (I'm still semi-fresh to Swift, and I normally work with JavaScript, so I'm not sure how to properly debug in Swift and Xcode.)
Swift app is built in iOS 12.2 because Xcode doesn't like this stuff in iOS 13
This is a simplified version of how I post to a Discord webhook with Swift. From your post I can't see how you're converting your custom Message struct into a JSON dictionary, but you need at least the key/value pair for "content" to successfully post a Discord Webhook. And of course the posts can be customized in many other ways (with a custom "username" etc.).
var messageString: String = ""
guard let url = URL(string: "your-full-discord-webhook-url") else { return }
let messageJson: [String: Any] = ["content": messageString]
let jsonData = try? JSONSerialization.data(withJSONObject: messageJson)
var request = URLRequest(url: url)
request.httpMethod = "POST"
request.addValue("application/json", forHTTPHeaderField: "content-type")
request.httpBody = jsonData
let task = URLSession.shared.dataTask(with: request)
task.resume()

How to pass bearer token to make Yelp API call with URLSessoin

U P D A T E D... The function with what works!
I would like to incorporate the yelp api into an app but can't successfully pass my authorization token on the URL string. Do I need to do something to connect the URLRequest to the URLSessoin call and its not using the header? Maybe the key value pairs is wrong? The below function returns:
error = {
code = "TOKEN_MISSING";
description = "An access token must be supplied in order to use this endpoint.";
};
I was able to use postman to get the yelp API call working, but only by clicking the "Header" section on postman and putting in Bearer and then my yelp key. I googled around a bit and found some links that indicate that you can add a header to the URLSession that I assume would work the way postman does but I haven't been able to get it to work.
I know there are some githubs with yelp API repos but I am trying to not install a large set of code that I don't understand into my app, when all I want is the JSON that I can see is coming through on postman. Can anyone help me understand how I would edit code similar to the Here example below so that I can get the Authorization/Bearer that yelp requires?
func getYelp() {
let appSecret = "Bearer <YELP APIKEY>"
let link = "https://api.yelp.com/v3/businesses/search?latitude=37.786882&longitude=-122.399972"
if let url = URL(string: link) {
// Set headers
var request = URLRequest(url: url)
request.setValue("Accept-Language", forHTTPHeaderField: "en-us")
request.setValue(appSecret, forHTTPHeaderField: "Authorization")
print("Attempting to get places around location from Yelp")
let task = URLSession.shared.dataTask(with: request) { (data, response, error) in
if error != nil {
print(error!)
} else {
if let urlContent = data {
do {
let jsonResult = try JSONSerialization.jsonObject(with: urlContent, options: JSONSerialization.ReadingOptions.mutableContainers) as AnyObject // Added "as anyObject" to fix syntax error in Xcode 8 Beta 6
print("Printing all JSON/n/n//n--------------------------")
print(jsonResult)
print("Printing from results/n/n//n--------------------------")
if let description = ((jsonResult["search"] as? NSDictionary)?["context"] as? NSDictionary)?["href"] as? String {
} else {
print("JSON pull failed/n/n//n--------------------------")
}
} catch {
print("JSON Processing Failed/n/n//n--------------------------")
}
}
}
}
task.resume()
} else {
resultLabel.text = "Couldn't get results from Here"
}
}
You're mixing up between the headers and the url, you need to set your headers correctly
if let url = URL(string: "https://places.cit.api.here.com/places/v1/discover/around?at=37.776169%2C-122.421267&app_id=\(app_id)&app_code=\(app_code)") {
var request = URLRequest(url: url)
// Set headers
request.setValue("Accept-Language", forHTTPHeaderField: "en-us")
request.setValue("Authorization", forHTTPHeaderField: "Bearer " + token // Token here)
print("Attempting to get places around location")
let task = URLSession.shared.dataTask(with: url) { (data, response, error) in
// ...
Lets say you have an api with "https://google.com" (this is just an example with fake keys)
and an api key that is "ApiKey: 92927839238293d92d98d98d92".
You would then take this information and do this.
let uri = URL(string:"https://google.com")
if let unwrappedURL = uri {
var request = URLRequest(url: unwrappedURL)request.addValue("92927839238293d92d98d98d92", forHTTPHeaderField: "ApiKey")
let dataTask = URLSession.shared.dataTask(with: request) { (data, response, error) in
// you should put in error handling code, too
if let data = data {
do {
let json = try JSONSerialization.jsonObject(with: data, options: [])
// HERE'S WHERE YOUR DATA IS
print(json)
} catch {
print(error.localizedDescription)
}
}
}
dataTask.resume()
}
Please remember that you would replace the google.com with your GET address and the APIKey header with your own api key values.
Also, this will print out all the JSON like in PostMan.
If this works for you, then I also have a link on accessing the JSON Objects.

Getting error In posting Data in swift

I am sending the data using post method like this
let login = ["user_name":usernameTextField.text,"password":passwordTextField.text]
//["user":"ords_user#gmail.com", "pass":"ords_password"]
let url = NSURL(string: "http://localhost:8300")!
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: url)
do {
// JSON all the things
let auth = try NSJSONSerialization.dataWithJSONObject(login, options: .PrettyPrinted)
// Set the request content type to JSON
request.setValue("application/json", forHTTPHeaderField: "Content-Type")
// The magic...set the HTTP request method to POST
request.HTTPMethod = "POST"
// Add the JSON serialized login data to the body
request.HTTPBody = auth
// Create the task that will send our login request (asynchronously)
let task = session.dataTaskWithRequest(request, completionHandler: { (data, response, error) -> Void in
// Do something with the HTTP response
print("Got response \(response) with error \(error)")
print("Done.")
})
// Start the task on a background thread
task.resume()
} catch {
// Handle your errors folks...
print("Error")
}
But I am getting the error message like
Argument type '[String : String?]' does not conform to expected type ‘AnyObject’
If I give the direct string it is accepting. If I am giving dynamically using the TextFields it is not coming. I don’t know what is the mistake I have done.
Can anyone Please help to solve this issue?
Thanks in advance.
I think your issue is that you are putting optional strings into the dictionary.
Try doing this:
guard
let username = usernameTextField.text,
let password = passwordTextField.text else {
return print("Need username & password")
}
let login = ["user_name": username,"password": password]
...
UITextField's text property returns an optional value, so compiler can't convert it to AnyObject.
You have to unwrap optionals before.
Try this
let login = ["user_name":usernameTextField.text,"password":passwordTextField.text] as Dictionary<String, AnyObject>

Swift 2 How do you add authorization header to POST request

When making a particular POST request, Firefox (FF) dev tools shows a req. header named "Authorization" with a value of "Bearer X" where X is the access token received upon login. When I edit this request in FF and remove the "Authorization" line, I get a 400 error. When I put it back in, 200 and all is well. I haven't yet, however, figured out how to set this request header programmatically without getting 400.
Also, FF tools as a "Request Body" of {"source":"desktop - profile 2015"}. I'm assuming this is JSON. I've tried posting this in several ways (see code) but have had no success.
// the following fields are set in the object "Request"'s initialization
let accessToken = "1,2,3456789012,3x4f560fa7a89e01a2;33ab4b4e5e67e8e9b9f0e1a23db45678f9a9a0ff" // replaced some characters for this StackOF posting
let authorization = "Bearer \(accessToken)"
let method = "POST"
let userID = "1234567"
let URL = NSURL(string: "https://www.somesite.com/apitun/profile/\(userID)hide")
// tried setting params to all of the following 4:
let params = ""
let params = "&_json={}"
let params = "&_json={\"source\":\"desktop profile - 2015\"}
let params = "&_json=%7B%22source%22%3A%22desktop%2Dprofile%202015%22%7D"
func execute() {
let session = NSURLSession.sharedSession()
let request = NSMutableURLRequest(URL: URL)
if authorization != "" {
request.addValue(authorization, forHTTPHeaderField: "Authorization")
}
request.HTTPMethod = self.method
request.HTTPBody = self.params.dataUsingEncoding(NSUTF8StringEncoding)
self.task = session.dataTaskWithRequest(request) {
(data, response, error) in
NSHTTPCookieStorage.sharedHTTPCookieStorage().setCookies(self.cookies, forURL: self.URL, mainDocumentURL: nil)
if error == nil {
do {
self.responseHeaders = response as! NSHTTPURLResponse
self.cookies = NSHTTPCookieStorage.sharedHTTPCookieStorage().cookiesForURL(self.URL)!
self.statusCode = self.responseHeaders.statusCode
switch self.statusCode {
case 200:
self.contentsOfURL = try NSString(contentsOfURL: self.URL, encoding: NSUTF8StringEncoding)
case 400:
print("400: page not found")
case 404:
print("404: page not found")
case 407:
print("407: failed authenticate proxy credentials")
default:
print("unable to get statusCode")
}
} catch {
}
self.isRequesting = false
} else {
print(error)
}
}
self.task.resume()
}
let request = NSMutableURLRequest(URL: NSURL(string: fullURL)!)
let accessToken = "your access token"
request.setValue("Bearer \(accessToken)", forHTTPHeaderField: "Authorization")

Can any one tell me why i am getting Bad authentication error while executing this code(Swift)?

I am using Fabric SDK to add the twitter login button in my app.......
i add the authentication header in my URL but still it is showing Bad authentication error while executing.
Suggest me how to add Header in the URL in Swift.
let twitter = Twitter.sharedInstance()
let oauthSigning = TWTROAuthSigning(authConfig:twitter.authConfig, authSession:twitter.session())
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
let request = NSMutableURLRequest(URL: NSURL(string: "https://api.twitter.com/1.1/search/tweets.json?q=Himan_dhawan")!)
request.allHTTPHeaderFields = authHeaders
println(request)
var session = NSURLSession.sharedSession()
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
if((error) != nil) {
println(error.localizedDescription)
}
var strData = NSString(data: data, encoding: NSASCIIStringEncoding)
println(strData)
})
task.resume()
It's to do with the way that you're setting the headers on the request.
The Fabric doc's don't quite give you the full picture about creating the OAuth signing headers when wanting to use your own NSMutableURLRequest.
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
The return [NSObject : AnyObject]! dictionary gives you the values you need for the request. However, what it provides for the headers are different to what needs to be sent with the NSMutableURLRequest.
This is how you should be setting the headers for this request:
let twitter = Twitter.sharedInstance()
let oauthSigning = TWTROAuthSigning(authConfig:twitter.authConfig, authSession:twitter.session())
let authHeaders = oauthSigning.OAuthEchoHeadersToVerifyCredentials()
let mutableUrlWithUsableUrlAddress = NSMutableURLRequest(URL: usableUrlForRequest)
mutableUrlWithUsableUrlAddress.addValue(authHeaders[TWTROAuthEchoAuthorizationHeaderKey] as? String, forHTTPHeaderField: "Authorization")
This sets the required Authorisation Key as a value for the "Authorization" header on the request, opposed to when you pass in the authHeaders dictionary, it gets set for "X-Verify-Credentials-Authorization".
The Fabric doc's do go into this, but it's slightly more tucked away than it should be.

Resources