Alamofire request fails with nil response - ios

I am getting numerous failed requests with Alamofire 5.3, where the response object itself is nil, or the error is "cannot parse response". I can see from the server logs that all of those requests are returning valid.
Here is my setup:
API manager class:
let config = Alamofire.Session.default.session.configuration
self.session = Alamofire.Session(configuration: config, interceptor: AccessTokenInterceptor())
AccessTokenInterceptor:
class AccessTokenInterceptor: RequestInterceptor {
func adapt(_ urlRequest: URLRequest, for session: Alamofire.Session, completion: #escaping (AdapterResult<URLRequest>) -> Void) {
var adaptedRequest = urlRequest
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Accept")
adaptedRequest.setValue("application/json", forHTTPHeaderField: "Content-Type")
if let token = SettingsManager.shared.userToken {
adaptedRequest.setValue("Bearer " + token, forHTTPHeaderField: "Authorization")
}
completion(.success(adaptedRequest))
}
}
This interceptor inserts my auth token from SettingsManager
I am also using the standard router for URLRequestConvertible where encoding is done by JSON serialization (dictionary) or Codable protocol (objects)
case .login(let body):
request.httpBody = try JSONSerialization.data(withJSONObject: body, options: [])
case .register(let object):
request.httpBody = try JSONEncoder().encode(object)
What is strange is that I don't think I'm doing anything different from the many other times I've used Alamofire and now the first request I make fails but the following one succeeds. If I remove the interceptor, there is no change.
If I inspect the outgoing headers or body content, it all seems normal, but the response from Alamofire is nil.
UPDATE: By using OS_ACTIVITY_MODE and iOS 13 I was able to see that it was complaining about the request headers and protocol. The server is on Elastic Beanstalk so I've been trying to mess with the SSL policy but still the first request fails every time.

This turned into quite the rabbit hole, so in the interest of community improvement, here is what I found.
After searching through the activity log errors, I noticed that iOS was complaining about an invalid header type -- upgrade. Searching for that value I found this question about removing the header. I learned that Apache acts as a proxy on Elastic Beanstalk but there is a mix up for HTTP/2 headers in the request, and iOS does not like that.
To get away from the header value, I ended up switching to Nginx proxy. Since my application uses Laravel, I then needed to deal with correcting the pretty URLs. To do that I found this answer. Now my web and mobile application both seem to be getting along nicely.

Related

How to handle '€' in a password in a HTTP post request

I have an iOS app which sends a HTTP request for the login to our Webserver. The login basically works fine, but as soon as someone got a '€' in his password the login fails.
This bug only happens in the app. We also have a web application, which sends the same login request to the same webserver and I can perfectly log in when I do that in my browser, even if there is a '€' in my password.
Here's the function that generates the request:
func SignOn() {
var request = Helper.getURLRequest(str: str, method: "POST")
guard let httpBody = try? JSONEncoder().encode(Helper.Logon.init(domain: String(userDomain[0]), user: String(userDomain[1]), p: ""))else { return }
request.httpBody = httpBody
let urlSession = URLSession(configuration: .default, delegate: self, delegateQueue: nil)
urlSession.dataTask(with: request) { (data, response, error) in
do {
guard let data = data else { throw Helper.MyError.NoConnection }
Helper.isAuthenticated = try JSONDecoder().decode(Helper.Authentication.self, from: data)
task.leave()
} catch {
[...]
}
static func getURLRequest(str: String, method: String) -> URLRequest {
let url = URL(string: str)
var request = URLRequest(url: url!)
let loginString = "\(Helper.loggedOnUserWithDomain):\(Helper.loggedOnUserPassword)"
let loginData = loginString.data(using: String.Encoding.utf8)
let base64LoginString = loginData!.base64EncodedString()
request.setValue("Basic \(base64LoginString)", forHTTPHeaderField: "Authorization")
request.httpMethod = method
request.setValue("application/json; charset=UTF-8", forHTTPHeaderField: "Content-Type")
return request
}
SignOn() gets called as soon as the user presses the "login" button in the app. Username and password are stored in two variables in my Helper class.
SignOn() will then call a function that generates the request - also in my Helper class.
I double checked every step in getURLRequest(). loginString and loginData both keep the € and they are perfectly displaying the character when I let Xcode print the variables.
I then checked the base64 string. Let's say someone enters "t€stpassword". The encoded base64 string should be VOKCrHN0cGFzc3dvcmQ=, which the function got right. I then let the function decode the base64 string again and checked if "t€stpassword" was the result, which again was true.
Then I checked the request with HTTP interception, but it also had the '€' in his body.
I already tried to percent escape the '€' character but that does also not work. The '€' gets percent escaped correctly, but I think the web server can't handle it then, I don't really know tbh. I used this method: how to http post special chars in swift
I'm out of ideas what I'm doing wrong here. I'm pretty new to Swift so I don't want to rule out, that I'm missing something obvious. Could the web server be the issue? But as I said, the login is working when doing it in a browser, so the server cannot be the issue, right?
According "The 'Basic' HTTP Authentication Scheme" in RFC 7617, section 3:
3. Internationalization Consideration
User-ids or passwords containing characters outside the US-ASCII
character repertoire will cause interoperability issues, unless both
communication partners agree on what character encoding scheme is to
be used. Servers can use the new 'charset' parameter (Section 2.1)
to indicate a preference of "UTF-8", increasing the probability that
clients will switch to that encoding.
Furthermore,
For the user-id, recipients MUST support all characters defined in
the "UsernameCasePreserved" profile defined in Section 3.3 of
RFC7613, with the exception of the colon (":") character.
For the password, recipients MUST support all characters defined in
the "OpaqueString" profile defined in Section 4.2 of RFC7613.
The "recipient" here is the backend. The referenced RFCs in the cited paragraphs clearly describe how the backend should process the Unicode characters and how to perform the comparison operator. You might test the server against the specification to figure out whether the server behaves correctly.
The client however, should at least check for a semicolen in either the password or user-id which would be an invalid credential for Basic HTTP Authentication.
So, your code should work, unless the backend does not want to handle Unicode. If this is the case, only allow ASCII on the client side.
When the authentication fails, a server might message the expected charset in the response in the Authenticate header:
WWW-Authenticate: Basic realm="foo", charset="UTF-8"
However, specifying a charset parameter is "purely advisory". We can't rely on the server sending this.
Basic HTTP is what the name suggests: a basic authentication scheme. It has been deprecated for a while now.
If possible, use a more secure and a more resilient authentication scheme.

How to call REST API deployed in AWS on iOS app?

I am new to AWS and trying to implement a webservice API GET call on iOS app. So following is the postman I have and trying to implement the same in iOS app;
But I am confused in setting this header on URLSession requests. I am not seeing much documentations regarding this. Looking forward for some help in implementing this on iOS app.
Tried to implement the Swift code generated in Postman :
But this implementation returns a Forbidden error message , so i believe some different implementation is needed to connect AWS
Can you show us what you have tried with URLSession so far ?
As said by #burnsi, you show look at the raw headers that are working for you in Postman and try to replicate those.
Some headers should always be used for all requests (given a particular URLSession), so you should consider configuring your session using httpAdditionalHeaders:
let configuration = URLSessionConfiguration.default
configuration.httpAdditionalHeaders = [
"Accept-Encoding": "application/json",
"Content-Type": "application/json"
]
let session: URLSession = URLSession(configuration: configuration)
For headers that are request-specific or likely to change over time (like an authorization token), you should add them to the request itself using setValue(_:forHTTPHeaderField:):
var request: URLRequest = URLRequest(url: url)
request.setValue("XYZ", forHTTPHeaderField: "Authorization")
Then you should perform the request using:
session.dataTask(with: request, completionHandler: { (data, response, error) in
print("Data: \(data?.debugDescription)\nResponse: \(response?.debugDescription)\nError: \(error?.debugDescription)")
})
Let us know what this code prints out for you and I'll try to help more!

NSURLSession treats http url as https, only on some devices

I have an app running on a test server that doesn't support https. I added the ATS exceptions in the info.plist and it worked well on the devices I tested on.
Now some of my test flight users started complaining about the app not being able to connect to the server. One of the devices in my office started having this problem too, so I had the change to look into it.
I am making a server request like this:
let path = "http://myurl.com"
let request = NSMutableURLRequest(URL: NSURL(string: path)!)
let configuration = NSURLSessionConfiguration.defaultSessionConfiguration()
let session = NSURLSession(configuration: configuration, delegate: self, delegateQueue: NSOperationQueue.mainQueue())
let task = session.dataTaskWithRequest(request, completionHandler: {data, response, error -> Void in
let httpResponse = response as? NSHTTPURLResponse
print(httpResponse)
print(NSString.init(data: data!, encoding: NSUTF8StringEncoding)!)
})
task.resume()
Now when I look at the URL from the NSHTTPURLResponse it is printed as "https://myurl.com". Somehow it just added the 's' to it. This only happens on some devices, I can't seem to find a pattern.
When I open the https url in a browser I get redirected to some other project we're working on. When I look at the response data that's exactly what I see. So somehow on some devices when you enter a http url it just converts it to https or something...
So far it's only been on iOS9 devices, but that may very well be a coincidence. Does anyone have an idea why this happens and what to do about it?

Does Alamofire store the cookies automatically?

I'm new to Alamofire so I'm sorry if this it's a noob question: this framework stores the cookies automatically?
This is because I have a simple request like this:
Alamofire.request(.POST, loginURL, parameters: ["fb_id": fbId, "fb_access_token": fbToken])
.responseJSON { response in
//print(response.request) // original URL request
//print(response.response) // URL response
//print(response.data) // server data
//print(response.result) // result of response serialization
if let JSON = response.result.value {
print("loginURL - JSON: \(JSON)")
}
}
this request response with a cookie session that I need to do other requests for security reason; the strange thing is that like magic I already can do the other requests after this first POST without read manually the cookie and store it. I'm sure the other requests need the cookie session because they fail on postman for example but not here.
It's just a feature? Because I can't find anything on that also on the official GitHub page.
Yes! Alamofire is basically a wrapper around NSURLSession. Its manager uses a default NSURLSessionConfiguration by calling defaultSessionConfiguration().
As its github page says under Advanced Usage section:
Alamofire is built on NSURLSession and the Foundation URL Loading System. To make the most of this framework, it is recommended that you be familiar with the concepts and capabilities of the underlying networking stack.
And under Manager section:
Top-level convenience methods like Alamofire.request use a shared instance of Alamofire.Manager, which is configured with the default NSURLSessionConfiguration.
And the NSURLSessionConfiguration reference for defaultSessionConfiguration() says:
The default session configuration uses a persistent disk-based cache (except when the result is downloaded to a file) and stores credentials in the user’s keychain. It also stores cookies (by default) in the same shared cookie store as the NSURLConnection and NSURLDownload classes.
For those who use Moya and want to disable stored cookies
(fixing the X-CSRF-Token request header is missing)
Very basic example:
public final class DisableCookiePlugin: PluginType {
public init() {
}
public func prepare(_ request: URLRequest, target: TargetType) -> URLRequest {
var mutableRequest = request
mutableRequest.httpShouldHandleCookies = false
return mutableRequest
}
}
And then use it
MoyaProvider<Api>(
plugins: [
//NetworkLoggerPlugin(configuration: .init(logOptions: .verbose)),
DisableCookiePlugin()
]

iOS - Alamofire v2 Basic Auth not working

So I'm sending a basic auth request to Bing Image Search to grab some image data, and it was working great, right until I updated to the latest version of Alamofire (1.3 -> 2.0.2), which I had to do because 1.3 wasn't even close to compatible with XCode 7.
Anyway, here is my code:
let credentials = ":\(Settings.bingApiKey)"
let plainText = credentials.dataUsingEncoding(NSUTF8StringEncoding, allowLossyConversion: false)
let base64 = plainText!.base64EncodedStringWithOptions(NSDataBase64EncodingOptions(rawValue: 0))
manager = Alamofire.Manager.sharedInstance
manager!.session.configuration.HTTPAdditionalHeaders = [
"Authorization": "Basic \(base64)"
]
let url = NSURL(string: Settings.bingImageApi + "&Query=" + keyword + "&$top=15&$skip=" + String(skip))!
manager!
.request(.POST, url, parameters: nil, encoding: .JSON)
.responseJSON { request, response, result in
...
And I'm getting the error:
FAILURE: Error Domain=NSCocoaErrorDomain Code=3840 "Invalid value around character 0." UserInfo={NSDebugDescription=Invalid value around character 0.}
The authorization type you provided is not supported. Only Basic and OAuth are supported
I had the same issue while moving from Alamofire 1.x to 2.x.
One workaround I found (and that works), is to pass the headers when performing the request:
let headers = ["Authorization": "Basic \(base64)"]
Alamofire.request(.POST, url, parameters: nil, encoding: .JSON, headers: headers)
For more information you can take a look at the documentation.
please read here http://ste.vn/2015/06/10/configuring-app-transport-security-ios-9-osx-10-11/
"App Transport Security (ATS) lets an app add a declaration to its Info.plist file that specifies the domains with which it needs secure communication. ATS prevents accidental disclosure, provides secure default behavior, and is easy to adopt. You should adopt ATS as soon as possible, regardless of whether you’re creating a new app or updating an existing one."
The first part of the error is due to you not receiving valid JSON in the response. You can use response, responseData or responseString to help debug.
The second part of the error is due to how you are setting the header. You cannot set an Authorization header after the session configuration has been created. You can either create your own session configuration and your own Manager, or you can pass the Authorization header in the request.

Resources