1

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?

Fryndorfer
  • 73
  • 10
  • 1
    Is the password in the web application encoded the same way? What are the content type headers in both cases? Could you provide a raw example of both request? – burnsi Feb 01 '23 at 12:39
  • The base64 encoding is not able to encode the '€' character. You can check the allowed characters here: https://en.wikipedia.org/wiki/Base64 It's not clear why it's encoded-decoded correctly on your machine though. Since you mentioned that the bug doesn't occur in the web app, are you sure that also the web app is using base64 encoding? – Ramy Al Zuhouri Feb 01 '23 at 12:43
  • @RamyAlZuhouri: Base64 can encode arbitrary data, in particular the UTF-8 sequence of arbitrary strings (including the Euro sign). – Martin R Feb 01 '23 at 12:57
  • 1
    @burnsi: Yes, the password is encoded the same way and both requests are exactly the same, me and my colleagues did check that multiple times. We actually found the issue by now: As I said, both the iOS app and the web application are sending the request to the same webserver, but it turned out that the webserver is redirecting them to different services - for example: the request from the app gets redirected to "GetAuth1" and the applications gets redirected to "GetAuth2". These two services are handling the password differently and that's the issue. Thank you so much for your help though! – Fryndorfer Feb 01 '23 at 15:50

1 Answers1

0

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.

CouchDeveloper
  • 18,174
  • 3
  • 45
  • 67