Fast download of Alamofire file with signed request: how to send authorization headers?

Scenario:

  • iPhone iOS 8+ app
  • Recorded user upload profile picture

The application already uses Alamofire to execute signed API requests. Actually simple: the application sends three specific HTTP headers ( Authorization , X-Api-Key and timestamp ) for the request to be signed. Calling Alamofire.request easily send headers as a parameter to make it work beautifully.

Users should now be able to upload their profile picture. Since the user is already registered in the application, the backend API will know which user sends the image using a signed request , and this is the difficult part that I have struggled with over the past few hours. Alamofire.upload accepts completely different parameters from .request , so I cannot figure out how to send headers when downloading a file.

I tried the old Alamofire.Manager.session.configuration.HTTPAdditionalHeaders , but was no longer supported . Found code examples for downloading files, no one is considering sending custom headers.

How can I send custom headers when using the Alamofire.upload method?

 typealias requestDataType = [String:AnyObject] private func signRequest(data: requestDataType) -> [String:String] { var headers = [String:String]() var authString = "" var signatureHeaders = "" // Iterates over SORTED data dictionary to build headers for (k,v) in (data.sort{$0.0 < $1.0}) { if !authString.isEmpty { authString += "\n" signatureHeaders += " " } authString += "\(k): \(v)" signatureHeaders += "\(k)" headers[k] = "\(v)" } let userApiKey = _loggedInUser!["api_key"].string! let signature = authString.sha256(_loggedInUser!["api_secret"].string!) headers["X-Api-Key"] = userApiKey headers["Authorization"] = "Signature headers=\"\(signatureHeaders)\",keyId=\"\(userApiKey)\",algorithm=\"hmac-sha256\",signature=\"\(signature)\"" return headers } func uploadProfilePicture(photo: UIImage, callback: apiCallback){ guard let userId = _loggedInUser?["pk"].int else { callback(Response(success: false, responseMessage: "User not logged in")) return } let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"] let aManager = Manager.sharedInstance print(self.signRequest(requestData)) // Prints correct headers (Authorization, X-Api-Key, timestamp) aManager.session.configuration.HTTPAdditionalHeaders = self.signRequest(requestData) print(aManager.session.configuration.HTTPAdditionalHeaders) // Prints default headers, completely ignoring my custom headers aManager.upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", multipartFormData: { multipartFormData in if let imageData = UIImageJPEGRepresentation(photo, 0.8) { multipartFormData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpeg") } for (key, value) in requestData { multipartFormData.appendBodyPart(data: value.dataUsingEncoding(NSUTF8StringEncoding)!, name: key) } }, encodingCompletion: { encodingResult in debugPrint(encodingResult) }) } 

Requests pass. In the backend log I see an HTTP 403 return request - not allowed because it was not possible to sign the request. By printing request headers, user headers did not receive the server.

+6
source share
2 answers

Before starting the initialization, I want to use a free tool (chrome application), very useful during this type of work: DHC Rest Client : with this tool, you can check whether your parameters, headers and your download files work with the type of request that you want to do on the server.

So this work with Swift 2.x and Alamofire 3.x :

First, prepare the headers:

 let headers = [ "Content-Type": "application/zip", "X-Api-Key": userApiKey, ...whatever you need on headers.. ] 

So, suppose you have to send a zip file, and the response will be a TEXT / HTML response type (simple line with SUCCESS or ERROR):

 let filePath: String! = "/Users/admin.../Documents/myZipFile.zip" var zipData: NSData! = NSData() do { zipData = try NSData(contentsOfFile: filePath, options: NSDataReadingOptions.DataReadingMappedIfSafe) } catch { print("- error during get nsdata from zip file\(error)") } let url :String! = String(format:"...myUrl?key1=%@&key2=%@",value1,value2) Alamofire.upload(.POST, url, headers: headers, data: zipData) .responseString { response in if response.result.isSuccess { let responseValue = response.result.value print("Response value is: \(responseValue)") } else { var statusCode = 0 if (response.response != nil) { statusCode = (response.response?.statusCode)! } print("Error: \(response.result.error!) with statusCode: \(statusCode)") } 

That's all, but if you want to use multipartformdata, you can do this by passing the headers through the headers dictionary with

.upload (<# T ## method: Method ## Method #>, <# T ## URLString: URLStringConvertible ## URLStringConvertible #>, headers: <#T ## [String: String]? #>, MultipartFormData: < # T ## MultipartFormData β†’ Void # >

+2
source

Using the @ alessandro-ornano response, I was able to download the signed request using multipartFormData :

 func uploadProfilePicture(photo: UIImage, callback: apiCallback){ guard let userId = _loggedInUser?["pk"].int else { callback(Response(success: false, responseMessage: "User not logged in")) return } let requestData: requestDataType = ["timestamp": "\(Int(NSDate().timeIntervalSince1970))"] let headers = self.signRequest(requestData) _alamofireManager .upload(.POST, "\(_apiBaseUrl)profiles/\(userId)/photo/", headers: headers, multipartFormData: { formData in if let imageData = UIImageJPEGRepresentation(photo, 1){ formData.appendBodyPart(data: imageData, name: "upload", fileName: "userphoto.jpg", mimeType: "image/jpg") } for (k, v) in requestData { formData.appendBodyPart(data: v.dataUsingEncoding(NSUTF8StringEncoding)!, name: k) } }, encodingCompletion: { encodingResult in switch encodingResult { case .Success(let upload, _, _): upload.responseJSON { response in self.responseHandler(response, callback: callback) // Class' private method } case .Failure(let encodingError): print(encodingError) self.dispatch_callback(callback, response: Response(success: false, responseMessage: "Unable to encode files for upload")) // Class' private method } }) } 
0
source

All Articles