iOS - Alamofire v2 Basic Auth not working - ios

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.

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.

Alamofire request fails with nil response

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.

How to send `apikey` in header in Alamofire 4.5,Swift 4?

I want to make a HTTP post request via Alamofire 4.5. The request need an authorization header(which is a Api key). But whenever I fired the request,my server cant detect the ApiKey.'
Here is how I make the Alamofire request
let params : [String : Any] =["param1":param1,"param2":param2]
let headers : HTTPHeaders = ["authorization" : apiKey]
Alamofire.request(MY_URL, method: .post, parameters: params, headers: headers).responseJSON {
response in
switch response.result{
case .success(let result):
//other code here
}
I triple checked the value of apiKey ,the value is correct,but the request sent,my server cant detect the authorization at all.
I totally no idea whether I do anything wrong here,cause I very new in Swift.Kindly provide a proper solution.Thanks
Edit :
In my server code,I using Slim 2
$app->map('/MY_URL','authenticate',function ()use($app){
}
'authenticate' is the point that scan for the authorization: apiKey in the headers,so now the problem is my server cant get the value of apiKey therefore always giving the same error "Api Key is missing" which I set when no Api Key found.
I tried the method below in Alamofire Documentation,but the result still the same.
What I tried:
let headers: HTTPHeaders = [
"Authorization": "Basic QWxhZGRpbjpvcGVuIHNlc2FtZQ==",
"Accept": "application/json"
]
Alamofire.request("https://httpbin.org/headers", headers: headers).responseJSON { response in
debugPrint(response)
}
What I missing here?Somebody please give me some hints to do it..Thank you.
EDIT:
To be more clear on my I mean for authorization : apiKey I show the way I make request in Postman.
Normally I just insert the "authorization": apiKey in the Headers in the request
but in Swift,the web service cant get the value of apiKey,therefore the server always return this following response :
{
"error": true,
"message": "Api key is missing"
}
This is working fine for me with Alamofire 4.6.0
let url = "WEB API URL"
let headers = [
"Content-Type":"application/x-www-form-urlencoded",
"authorization" : "apiKey"
]
let configuration = URLSessionConfiguration.default
configuration.requestCachePolicy = .reloadIgnoringLocalCacheData
let params : [String : Any] = ["param1":param1,"param2":param2]
Alamofire.request(url, method: .post, parameters: params as? Parameters, encoding: URLEncoding.httpBody, headers: headers).responseJSON { response in
if let JSON = response.result.value {
print("JSON: \(JSON)")
}else{
print("Request failed with error: ",response.result.error ?? "Description not available :(")
}
}
TLDR;
The problem is that iOS's URLRequest automatically capitalize headers. At the same time you API does not follow best practices.
Change your API to comply to RFC 7230 and allow it to accept headers case-insensitively.
The whole story:
At first, your question seemed a bit odd since there is no obviously wrong code in what you provided. Nevertheless I tried to reproduce your request in Postman.
Now we should stop and I must warn you to never post what you did in your "Here is my request" section. The information given there allowed me to completely reproduce your request in Postman (including headers and exact fields' names and values), which is good to solve your problem. But at the same time you shared your presumably private and maybe even bought API key to everyone who see your question. Which is obviously not good and I would recommend you to change your API key if it is possible.
Then I tried your code and noticed exactly the same behavior you talking about. I debugged responseJSON closure and observed response.request?.allHTTPHeaderFields property:
(lldb) po response.request?.allHTTPHeaderFields
▿ Optional<Dictionary<String, String>>
▿ some : 2 elements
▿ 0 : 2 elements
- key : "Content-Type"
- value : "application/x-www-form-urlencoded; charset=utf-8"
▿ 1 : 2 elements
- key : "Authorization"
- value : "f8f99f9506d14f0590863d5883aaac9b"
(if you don't understand what I wrote read about debugging in xcode and in particular for lldb's po command)
As you can see, authorization header's name start with a capital A letter even though I passed it all lowercased.
I tried to send new request with postman with capital A and yes - I learned that your API accepts only lower-cased authorization header name.
"It isn't really a problem" you think right now. "We should just change our authorization header name somewhere and it should be just fine, right?"
NOT SO EASY.
I tried a few things which all lead me to the URLRequest's setValue(_:forHTTPHeaderField:) method. Alamofire calls it and I tried it too. Surprisingly enough after calling this method "authorization" header always changes to "Authorization". Then I found the thing that particularly interesting for us:
Note that, in keeping with the HTTP RFC, HTTP header field names are case-insensitive.
Keep in mind that I even tried to change URLRequest's allHTTPHeaderFields directly. Had the same result.
Which leads us to the following conclusion: Apple intentionally ignores input headers' case and very irresponsibly changes it (again intentionally since it takes at least a few lines of code somewhere instead of just plugging given headers directly into request). As of now I know no possible solution to this problem (if we want to classify it as a problem which is a bit controversial). Search says that is exists from earlier days of iOS (http://0xced.blogspot.com.by/2010/06/fixing-nsmutableurlrequest.html). You could call some private objective-c APIs which could help, but in fact you'll get unstable or undefined behavior and would likely get rejected from App Store.
So my conclusion, and probably the only right choice in this situation is to change your API.
Configuration is optional, the only thing you need is to setup request right. Make sure (double sure) that you format your auth correctly.
In some (not that rare cases this should be formatted like this:
["Authorization": "Bearer <#your_token#>"]
And what I found about Slim 2 it's also with Bearer so maybe you missing this.
https://github.com/dyorg/slim-token-authentication/tree/master/example#making-authentication-via-header
Example from this:
$ curl -i http://localhost/slim-token-authentication/example/restrict -H "Authorization: Bearer usertokensecret"
With this, you can also check if this working with simple curl command. It should. If not, there is definitely a problem with fields you're sending rather than Alamofire itself.
In docs for Alamofire you can find:
/// Creates a `DataRequest` using the default `SessionManager` to retrieve the contents of the specified `url`,
/// `method`, `parameters`, `encoding` and `headers`.
///
/// - parameter url: The URL.
/// - parameter method: The HTTP method. `.get` by default.
/// - parameter parameters: The parameters. `nil` by default.
/// - parameter encoding: The parameter encoding. `URLEncoding.default` by default.
/// - parameter headers: The HTTP headers. `nil` by default.
///
/// - returns: The created `DataRequest`.
public func request(_ url: URLConvertible, method: Alamofire.HTTPMethod = default, parameters: Parameters? = default, encoding: ParameterEncoding = default, headers: HTTPHeaders? = default) -> Alamofire.DataRequest
Here is an example:
Alamofire.request("https://...",
method: .get,
parameters: ["myKey1": "myValue1"],
encoding: JSONEncoding.default,
headers: self.authHeader).responseJSON { response in
//your response
}

How to set body type to JSON in Alamofire?

I'm working online with different people from different projects who take care of backend API webservice. Usually I don't have problems with sending and receiving JSON, but this time, I can't seem to be able to send JSON properly to the server.
Usually I use Alamofire to receive and send JSON message, and the usual call go like this:
let param = populateParamWithDictionary();
let url = "https://www.example.com";
Alamofire.request(.POST, url, parameters: param, headers: nil)
.responseJSON { response in {
// take care of response here
}
But this time, I got project which the backend programmer requires me to use OAuth v2. So, let's say I've develop a function which already take care of getting the access_token string. The function now become like this:
let param = populateParamWithDictionary();
let url = "https://www.example.com";
let headers : Dictionary<String, String> = [
"Content-Type":"application/json",
"Authorization":"Bearer \(access_token)"
];
Alamofire.request(.POST, url, parameters: param, headers: headers)
.responseJSON { response in {
// take care of response here
}
But instead of the result, I get 400 bad request error. I also even try this:
let param = populateParamWithDictionary();
let url = "https://www.example.com";
let headers : Dictionary<String, String> = [
"Content-Type":"application/json",
"Authorization":"Bearer \(access_token)"
];
Alamofire.request(.POST, url, parameters: param, encoding: ParameterEncoding.JSON, headers: headers)
.responseJSON { response in {
// take care of response here
}
But the result is even worse. This is what I get when I print the response.
FAILURE: Error Domain=NSURLErrorDomain Code=-1017 "cannot parse
response" UserInfo={NSUnderlyingError=0x7fbb505788f0 {Error
Domain=kCFErrorDomainCFNetwork Code=-1017 "(null)"
UserInfo={_kCFStreamErrorCodeKey=-1, _kCFStreamErrorDomainKey=4}},
NSErrorFailingURLStringKey=http://lfapp.learnflux.net/v1/me,
NSErrorFailingURLKey=http://lfapp.learnflux.net/v1/me,
_kCFStreamErrorDomainKey=4, _kCFStreamErrorCodeKey=-1, NSLocalizedDescription=cannot parse response}
But the request works if I use REST client, by setting the headers to have the authentication and Content-Type, and have the parameters to be written as plain Content, e.g. in plain API in the body content.
How can I fix this?
EDIT: The part with the access token is already clear. The access token works. I can call an API successfully if the API doesn't requires any parameters (maybe because on the server, the code doesn't bother to even check or validate the body at all because it doesn't need anything from there, hence no error raised). The problem is when I make a request which needs any parameters.
The error you have is probably because of encoding: ParameterEncoding.JSON in the request. Try to change it to encoding: .URLEncodedInURL. If this doesn't help you, do add your parameters to the question and if you´re make a request to get the token do the following:
if let access_token = json["access_token"]!{
// Make the request here when you know that you have your token
}

Post Method with NSDictionary Values using Swift

I'm completely new toSwift. I need to hit a Post Method webservice with NSDictionary parameters & get the JSON response. I tried usingAlamofire & also NSMutableUrlRequest. Nothing seems to workout for me. I either get 'JSON text did not start with array or object and option to allow fragments not set' error or 'Undefined Variable' response from the server. The same service works fine when I try using Objective-C. As I said earlier, I am completely new toSwift & need your assistance.
My base url: http://myofficeit.in/bizfeed/webservices/client.php
Parameter I wanna Pass:
Parameter =
{
UserName = xyz;
deviceModel = iPhone;
deviceToken = "949264bc cd9c6c851ee64cc74db9078770dd7d971618ec20ce91d2e6eb9f155e";
emailid = "xyz#gmail.com";
location = Asia;
userMobileNo = 1234567890;
};
functionName = register;
The code I used for hitting the service is: http://pastebin.com/aaT4uhS7
Thanks
you can use like
let param: [String:AnyObject] = [
"UserName": iPhone,
"deviceToken": "949264bc cd9c6c851ee64cc74db9078770dd7d971618ec20ce91d2e6eb9f155e",
"emailid": "xyz#gmail.com",
"location": Asia,
"userMobileNo": 1234567890
]
Alamofire.request(.POST, "http://myofficeit.in/bizfeed/webservices/client.php/register", parameters: param).responseJSON { (req, res, json, error) in
print(req)
print(res)
print(json)
print(error)
}
for sample request in Alamofire
As broad as your question is, the broad will be my answer:
The first thing to do, is to get a clear idea about the web service API, which also requires a basic knowledge of the HTTP protocol. So, what you need to understand is, what the server expects in HTTP terminology.
You eventually will find out, how the server will expect its "parameters". Note, that there is no term like "parameters" in the HTTP protocol. So, you need to map them into something the HTTP protocol provides.
Most likely, in a POST request, "parameters" are transferred as the body of the HTTP message, as a content-type which is application/x-www-form-urlencoded, multipart/form-data or application/json.
According to the needs of the server, and with your basic knowledge of HTTP and NSURLSession, NSURLComponents etc., you compose the URL and the body of the request, set Content-Type header and possibly other headers and you are ready to go.
How this eventually looks like is given in the answer of #AnbyKarthik, which used Alamofire, and a command that composes a POST request whose parameters are send in the body whose content-type is x-www-form-urlencoded.

Resources