Search code examples
djangoswiftcurloauth-2.0alamofire

Django oauth2 token request fails on Swift Alamofire


I am building both an iOS client and a django backend service. The connection made between the systems is OAUTH2, implemented by the django-oauth2-toolkit.

Although the following command done in curl works (returns an access token):

curl -X POST -d "grant_type=password&username=<user>&password=<password>" http://<clientID>:<clientSecret>@localhost:8000/o/token/

The following Swift snippet, that uses Alamofire, receives "invalid_client", as a response.

let request = "http://\(Authentication.clientId):\(Authentication.clientSecret)@localhost:8000/o/token/"
var URLRequest = NSMutableURLRequest(URL: NSURL(string: request)!)
URLRequest.HTTPMethod = "POST"

let parameters = ["grant_type": "password", "username": in_username.text!, "password": in_password.text!]

let encoding = Alamofire.ParameterEncoding.URL
(URLRequest, _) = encoding.encode(URLRequest, parameters: parameters)
URLRequest.setValue("application/x-www-form-urlencoded", forHTTPHeaderField: "Content-Type")

Alamofire.request(URLRequest)
    .responseJSON { response in
        let data = response
        print(data)
    }

I then traced the InvalidClientError in the django-oauth2-toolkit source, and found that the exception was raised in the highlighted snippet of the following file: oauthlib/oauth2/rfc6749/grant_types/resource_owner_password_credentials.py

if self.request_validator.client_authentication_required(request):
    log.debug('Authenticating client, %r.', request)
    print(request) # I included this print message to inspect the request variable in console.
    if not self.request_validator.authenticate_client(request):
        log.debug('Client authentication failed, %r.', request)
        raise errors.InvalidClientError(request=request) # RAISED!

I included the print(request) line to inspect the differences between the request made by curl and by Alamofire. The major difference was that the curl version included an authorization key:

'Authorization': 'Basic Z3ZFSjVXejloUGgybUJmdDNRaGhXZnlhNHpETG5KY3V6djJldWMwcjpSbVNPMkpwRFQ4bHp1UVFDYXN3T3dvVFkzRTBia01YWWxHVHNMcG5JUGZCUHFjbHJSZE5EOXQzd3RCS2xwR09MNWs1bEE4S2hmRUkydEhvWmx3ZVRKZkFXUDM4OERZa1NTZ0RvS0p3WjUyejRSQ29WRkZBS01RS1lydEpsTWNXag=='

and the Alamofire request didn't.

I highly suspect this is the culprit, but I really don't know else to do though from here on. I would really appreciate any wisdom.


Solution

  • Found the answer!

    Was reading through a RFC document on the HTTP protocol, when this section caught my eye.

    https://www.rfc-editor.org/rfc/rfc1945#section-11.1

    Specifically,

    To receive authorization, the client sends the user-ID and password, separated by a single colon (":") character, within a base64 [5] encoded string in the credentials.

    It seems that Alamofire does not encode in 64 bits the clientId and clientSecret, as expected. Obviously, curl does this automatically. So I did the following:

    First encode:

     static let clientData: NSData = "\(clientId):\(clientSecret)".dataUsingEncoding(NSUTF8StringEncoding)!
     static let client64String = clientData.base64EncodedStringWithOptions(NSDataBase64EncodingOptions.init(rawValue: 0))
    

    Then set the request header using the resulting value:

    let request = "http://localhost:8000/o/token/"
    var URLRequest = NSMutableURLRequest(URL: NSURL(string: request)!)
    URLRequest.HTTPMethod = "POST"
    
    let parameters = ["grant_type": "password",
                      "username": in_username.text!,
                      "password": in_password.text!,
                      ]
    
    let encoding = Alamofire.ParameterEncoding.URL
    (URLRequest, _) = encoding.encode(URLRequest, parameters: parameters)
    
    // SOLUTION!
    URLRequest.setValue("Basic \(Authentication.client64String)", forHTTPHeaderField: "Authorization")
    
    Alamofire.request(URLRequest)
        .responseJSON { response in
            let data = response
            print(data)
        }
    

    I then received the expected token as a response.