400 error when downloading file with "Authorization" header - ios

The server returning a json file that is:
{"ctrl":{"code":400,"text":"Not valid Access token","ts":"2020-03-05T11:54:01.547Z"}}
Code:
public func startDownload(url: URL, pathURL: URL) {
let accessToken: String! = "Bearer \(Constants.access_token)"
self.dirURL = pathURL
var request = URLRequest(url: url)
guard let token = accessToken else { return }
request.addValue(token, forHTTPHeaderField: "Authorization")
downloadTask = backgroundSession.downloadTask(with: request)
downloadTask.resume()
}
FYI: access token is valid, it is working with Postman.

You're going to have a problem because, unfortunatelly, there's no good solution to this issue. Authorization is one of the Reserved HTTP Headers and setting it either in URLRequest header, or in URLSessionConfiguration.httpAdditionalHeaders may simply not work:
If you set a value for one of these reserved headers, the system may ignore the value you set, or overwrite it with its own value, or simply not send it.
One might expect you could provide this token in URLSessionTaskDelegate method urlSession(_:task:didReceive:completionHandler:) which handles authentication challenges, but in there you need to provide a URLCredential object, and sadly it doesn't have a constructor that takes a Bearer token, so that's a no-go.
So basically, short of writing your own URLProtocol implementation, your best bet would be to send the token in some additional, custom, header field and have the server grab it from there (if you have control over server code). Source

Related

HTTP DELETE Works From Browser But Not From Postman or IOS App

When attempting an http request to my rest api, I continually get a 401 error when using the following code. I don not get this error making any other type of request. I have provided the function that makes the request below.
func deleteEvent(id: Int){
eventUrl.append(String(id))
let request = NSMutableURLRequest(url: NSURL(string: eventUrl)! as URL)
request.httpMethod = "DELETE"
print(eventUrl)
eventUrl.removeLast()
print(self.token!)
request.allHTTPHeaderFields = ["Authorization": "Token \(self.token)"]
let task = URLSession.shared.dataTask(with: request as URLRequest) { data, response, error in
if error != nil {
print("error=\(String(describing: error))")
//put variable that triggers error try again view here
return
}
print("response = \(String(describing: response))")
}
task.resume()
}
When sending the delete request with postman, the rest api just returns the data I want to delete but does not delete it. For reference I have posted the view and permissions classes associated with this request Any help understanding why this may be resulting in an error is greatly appreciated!
Views.py
class UserProfileFeedViewSet(viewsets.ModelViewSet):
"""Handles creating, reading and updating profile feed items"""
authentication_classes = (TokenAuthentication,)
serializer_class = serializers.ProfileFeedItemSerializer
queryset = models.ProfileFeedItem.objects.all()
permission_classes = (permissions.UpdateOwnStatus, IsAuthenticated)
def perform_create(self, serializer):
"""Sets the user profile to the logged in user"""
#
serializer.save(user_profile=self.request.user)
Permissions.py
class UpdateOwnStatus(permissions.BasePermission):
"""Allow users to update their own status"""
def has_object_permission(self, request, view, obj):
"""Check the user is trying to update their own status"""
if request.method in permissions.SAFE_METHODS:
return True
return obj.user_profile.id == request.user.id
HEADER SENT WITH DELETE REQUEST VIA POSTMAN
Preface: You leave out too much relevant information from the question for it to be properly answered. Your Swift code looks, and please don't be offended, a bit beginner-ish or as if it had been migrated from Objective-C without much experience.
I don't know why POSTMAN fails, but I see some red flags in the Swift code you might want to look into to figure out why your iOS app fails.
I first noticed that eventUrl seems to be a String property of the type that contains the deleteEvent function. You mutate it by appending the event id, construct a URL from it (weirdly, see below), then mutate it back again. While this in itself is not necessarily wrong, it might open the doors for racing conditions depending how your app works overall.
More importantly: Does your eventUrl end in a "/"? I assume your DELETE endpoint is of the form https://somedomain.com/some/path/<id>, right? Now if eventUrl just contains https://somedomain.com/some/path your code constructs https://somedomain.com/some/path<id>. The last dash is missing, which definitely throws your backend off (how I cannot say, as that depends how the path is resolved in your server app).
It's hard to say what else is going from from the iOS app, but other than this potential pitfall I'd really recommend using proper Swift types where possible. Here's a cleaned up version of your method, hopefully that helps you a bit when debugging:
func deleteEvent(id: Int) {
guard let baseUrl = URL(string: eventUrl), let token = token else {
// add more error handling code here and/or put a breakpoint here to inspect
print("Could not create proper eventUrl or token is nil!")
return
}
let deletionUrl = baseUrl.appendingPathComponent("\(id)")
print("Deletion URL with appended id: \(deletionUrl.absoluteString)")
var request = URLRequest(url: deletionUrl)
request.httpMethod = "DELETE"
print(token) // ensure this is correct
request.allHTTPHeaderFields = ["Authorization": "Token \(token)"]
let task = URLSession.shared.dataTask(with: request) { data, response, error in
if let error = error {
print("Encountered network error: \(error)")
return
}
if let httpResponse = response as? HTTPURLResponse {
// this is basically also debugging code
print("Endpoint responded with status: \(httpResponse.statusCode)")
print(" with headers:\n\(httpResponse.allHeaderFields)")
}
// Debug output of the data:
if let data = data {
let payloadAsSimpleString = String(data: data, encoding: .utf8) ?? "(can't parse payload)"
print("Response contains payload\n\(payloadAsSimpleString)")
}
}
task.resume()
}
This is obviously still limited in terms of error handling, etc., but a little more swifty and contains more console output that will hopefully be helpful.
The last important thing is that you have to ensure iOS does not simply block your request due to Apple Transport Security: Make sure your plist has the expected entries if needed (see also here for a quick intro).

How to set a 'Token xxxxxxxxxx' for 'Authorization' in HTTP header in URLSession

The backend APIs I'm working with require a token to be sent with every request against the HTTP header key Authorization in this format - Token xxxxxxxxxx.
Right now, I'm doing the following.
var getRequest = URLRequest(url: url)
getRequest.addValue("Token xxxxxxxx", forHTTPHeaderField: "Authorization")
This works sometimes and some other times, the header field Authorization is stripped when the request is sent out. I checked this using Charles proxy.
Apple's documentation states the following.
An NSURLSession object is designed to handle various aspects of the
HTTP protocol for you. As a result, you should not modify the
following headers: Authorization, Connection, Host, WWW-Authenticate
As a solution to this, many suggest using the didReceiveAuthenticationChallenge delegate method for URLSession.
Here, you need to pass a URLSession.AuthChallengeDisposition instance to tell how you want to respond to the challenge and a URLCredential instance to pass the credentials to respond to the authenticate challenge.
I do not know how to and if I can create a URLCredential instance that will add Token xxxxxxxx for the header field Authorization.
Can somebody more knowledgeable please help me how to go about solving this?
PS - All code mentioned in this question is in Swift 3.
This question asks something similar to what I have. But, the answers given there don't work for me. And, some of the questions asked under the questions regarding Apple not allowing headers for Authorization to be added have gone unanswered.
Edit:
Posting relevant code.
var getRequest = URLRequest(url: url)
getRequest.httpMethod = "GET"
getRequest.addValue("application/json", forHTTPHeaderField: "Content-Type")
getRequest.addValue("application/json", forHTTPHeaderField: "Accept")
let token = DataProvider.sharedInstance.token
getRequest.addValue("Token \(token)", forHTTPHeaderField: "Authorization")
let getTask = URLSession.shared.dataTask(with: getRequest) { (data, response, error) in
if let data = data {
print("--------GET REQUEST RESPONSE START--------")
print("CODE: \((response as? HTTPURLResponse)?.statusCode ?? 0)")
print("Response Data:")
print(String(data: data, encoding: .utf8) ?? "")
print("--------GET REQUEST RESPONSE END--------")
}
}
getTask.resume()
Here, I can confirm that the header field for 'Authorization' is getting added to the request's header dictionary.
But, when I check what request hits the server, the header field for 'Authorization' is missing. Any thoughts?
I ran into this exact same issue and discovered that my lack of a trailing slash, /, was the problem.
The server was sending back a 301 Redirect response. URLSession automatically follows the redirect, but will also drop the Authorization header. This is likely due to Authorization's "special status". According to URLSessionConfiguration's documentation:
An URLSession object is designed to handle various aspects of the HTTP protocol for you. As a result, you should not modify the following headers:
Authorization
Connection
Host
WWW-Authenticate
If the Authorization header is required, implement urlSession(_:task:willPerformHTTPRedirection:newRequest:completionHandler:) from URLSessionTaskDelegate. That method is passed the new request on redirect, allowing you to add back the header.
e.g.
class APIClient: URLSessionTaskDelegate {
let session: URLSession!
initializeSession() {
// Create a new session with APIClient as the delegate
session = URLSession(configuration: URLSessionConfiguration.default,
delegate: self,
delegateQueue: nil)
}
// Perform the request
func fetchRecords(handler: () => void) {
var request = URLRequest(url: URL(string: "http://127.0.0.1:8000/api/employee/records")!)
request.setValue(retrieveToken(), forHTTPHeaderField: "Authorization")
session.dataTask(with: request, completionHandler: handler).resume()
}
// Implement URLSessionTaskDelegate's HTTP Redirection method
func urlSession(_ session: URLSession, task: URLSessionTask, willPerformHTTPRedirection response: HTTPURLResponse, newRequest request: URLRequest, completionHandler: #escaping (URLRequest?) -> Void) {
// Create a mutable copy of the new request, add the header, call completionHandler
var newRequest = request
newRequest.addValue(retrieveToken(), forHTTPHeaderField: "Authorization")
completionHandler(newRequest)
}
}
IMPORTANT NOTE!
It's a bad idea to blindly trust a redirect. You should ensure that the URL you're being redirected to is the same domain as the original request. I've left that out of my code example for brevity.

Pusher Client Error: Invalid Signature

I recently looked in my pusher error logs and noticed:
Invalid signature: Expected HMAC SHA256 hex digest of
217478.6054950:private-production1_xxxxx_1232:{"user_id":xxxx}, but got 707d39519ca7f971a134524d8fe2ebafbddd64f42b6af0a20d6a73fxxxxxxx
In general our websockets have been working fine. We have many clients working completely fine and sockets in general seem to be working without issue. This is the first time I've noticed this error and I check the error logs fairly frequently. Is this something I should be concerned about? I can confirm that private channels are working properly in general.
On the frontend the code is as follows:
let options = PusherClientOptions(
authMethod: AuthMethod.authRequestBuilder(authRequestBuilder: AuthRequestBuilder()
)
pusher = Pusher(key: pusherKey!, options: options)
class AuthRequestBuilder: AuthRequestBuilderProtocol {
func requestFor(socketID: String, channel: PusherChannel) -> NSMutableURLRequest? {
let request = NSMutableURLRequest(url: URL(string: "https://\(baseURLPrefix).xxxxxx.com/xxxxx/xxxxx")!)
request.httpMethod = "POST"
request.httpBody = "socket_id=\(socketID)&channel_name=\(channel.name)".data(using: String.Encoding.utf8)
request.addValue(
"Bearer " + authToken, forHTTPHeaderField: "Authorization"
)
return request
}
}
On the backend(Laravel application):
// Controller
public function presence_auth(Request $request)
{
$pusher = new Pusher(
config('broadcasting.connections.pusher.key'),
config('broadcasting.connections.pusher.secret'),
config('broadcasting.connections.pusher.app_id')
);
return $pusher->presence_auth($request->input('channel_name'), $request->input('socket_id'), AuthUser()->id);
}
Would this error occur if they had passed up a bad Bearer token to our backend?
You're using $pusher->presence_auth to create a signature for a private channel, i.e. a channel prefixed with private-. But presence_auth is intended to authenticate presence channels, i.e. channels prefixed with presence-.
If you wish to use presence data, you can use a presence- channel prefix. If you wish to use a private- channel without presence information, you can just use:
$pusher->socket_auth($request->input('channel_name'), $request->input('socket_id'))

Set Cookies for URL Request

Currently I have an iOS app that pulls prices and data from websites. So far its been working well, but I want to make it more accurate. To do so, I need to set the cookies for the URL request that I'm currently using String(contentsOf: _) for.
Current Process
let requestUrl: URL = URL(string: "http://www.samsclub.com/sams/search/searchResults.jsp?searchTerm=Apple")!
var content: String?
do {
content = try String(contentsOf: requestUrl)
} catch {
print("Error while converting an NSURL to String: \(error)")
}
if content != "" {
// I do things with the content of the requestUrl...
}
Could Use?
I thought that maybe I should use Alamofire instead to pull those website, and then parse the data.
I need to set the cookie that changes the store number to search, but have been unable to find a way to do so. Bellow is the code I have for pulling the websites data without setting a cookie.
let requestUrl: String = "http://www.samsclub.com/sams/search/searchResults.jsp?searchTerm=Apple"
Alamofire.request(requestUrl, method: .post).responseString { response in
if let content: String = response.result.value {
// I do things with the content of the requestUrl...
}
}
Other Claims
I have found many different ways to set cookies through Alamofire that don't work, but if Alamofire isn't the way to do it, please inform me. I really need this to work, and I'm open to any and every suggestion.
It took four weeks to the day, but I figured it out! URLRequest and Alamofire were my glorious answers!
Create the URL to call.
let requestUrl: String = "http://www.samsclub.com/sams/search/searchResults.jsp?searchTerm=Apple"
Next make the URLRequest with the URL string, and set its http method.
var urlRequest = URLRequest(url: requestUrl)
urlRequest.httpMethod = "POST"
Then set the cookies for the URLRequest.
urlRequest.setValue("myPreferredClub=4969", forHTTPHeaderField: "Cookie")
urlRequest.httpShouldHandleCookies = true
Finally send the URLRequest with Alamofire, and use the response data in whatever way I wish.
Alamofire.request(urlRequest).responseString { response in
if let content: String = response.result.value {
// I do things with the content of the urlRequest...
}
}

How to get a MS Translator access token from Swift 3?

I am trying to work with a MS Translator API from Swift 3 (right now playing in playgrounds, but the target platform is iOS). However, I got stuck when I was trying to get an access token for OAuth2. I have following code (I tried to port the code from example at Obtaining an access token):
let clientId = "id".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let clientSecret = "secret".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let scope = "http://api.microsofttranslator.com".addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
let translatorAccessURI = "https://datamarket.accesscontrol.windows.net/v2/OAuth2-13"
let requestDetails = "grant_type=client_credentials&client_id=\(clientId)&client_secret=\(clientSecret)&scope=\(scope)"
let postData = requestDetails.data(using: .ascii)!
let postLength = postData.count
var request = URLRequest(url: URL(string: translatorAccessURI)!)
request.httpMethod = "POST"
request.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")
request.setValue("\(postLength)", forHTTPHeaderField: "Content-Length")
request.httpBody = postData
URLSession.shared.dataTask(with: webRequest) { (returnedData, response, error) in
let data = String(data: returnedData!, encoding: .ascii)
print(data)
print("**************")
print(response)
print("**************")
print(error)
}.resume()
Of course, I used a valid clientId and a valid clientSecret.
Now the callback prints following information. First, the returnedData contain a message that the request was invalid, along with a following message:
"ACS90004: The request is not properly formatted."
Second, the response comes with a 400 code (which fits the fact that the request is not properly formatted).
Third, the error is nil.
Now I was testing the call using Postman, and when I used the same URI, and put the requestDetails string as a raw body message (I added the Content-Type header manually), I got the same response. However, when I changed the body type in Postman UI to application/x-www-form-urlencoded and typed in the request details as key value pairs through its UI, the call succeeded. Now it seems that I am doing something wrong with the message formatting, or maybe even something bad with the Swift URLRequest/URLSession API, however, I cannot get a hold on to what. Can somebody help me out, please? Thanks.
OK, so after some more desperate googling and experimenting I have found my error. For the future generations:
The problem resided in encoding the parameters in the body of the PUT http request. Instead of:
let scope = "http://api.microsofttranslator.com"
.addingPercentEncoding(withAllowedCharacters: .urlQueryAllowed)!
I have to use the following:
let scope = "http://api.microsofttranslator.com"
.addingPercentEncoding(withAllowedCharacters:
CharacterSet(charactersIn: ";/?:#&=$+{}<>,").inverted)!
Seems that the API (or the HTTP protocol, I am not an expert in this) have problems with / and : characters in the request body. I have to give credit to Studiosus' answer on Polyglot issue report.

Resources