31
votes

I'm trying to request a HTTP resource that requires basic authorization headers from within an Adobe AIR application. I've tried manually adding the headers to the request, as well as using the setRemoteCredentials() method to set them, to no avail.

Here's the code:

<mx:Script>
    <![CDATA[
        import mx.rpc.events.ResultEvent;
        import mx.rpc.events.FaultEvent;

        private function authAndSend(service:HTTPService):void
        {
            service.setRemoteCredentials('someusername', 'somepassword');
            service.send();
        }

        private function resultHandler(event:ResultEvent):void
        {
            apiResult.text = event.result.toString();
        }

        private function resultFailed(event:FaultEvent):void
        {
            apiResult.text = event.fault.toString();
        }
    ]]>
</mx:Script>

<mx:HTTPService id="apiService"
    url="https://mywebservice.com/someFileThatRequiresBasicAuth.xml"
    resultFormat="text"
    result="resultHandler(event)"
    fault="resultFailed(event)" />

<mx:Button id="apiButton"
    label="Test API Command"
    click="authAndSend(apiService)" />

<mx:TextArea id="apiResult" />

However, a standard basic auth dialog box still pops up prompting the user for their username and password. I have a feeling I'm not doing this the right way, but all the info I could find (Flex docs, blogs, Google, etc.) either hasn't worked or was too vague to help.

Any black magic, oh Flex gurus? Thanks.


EDIT: Changing setRemoteCredentials() to setCredentials() yields the following ActionScript error:

[MessagingError message='Authentication not supported on DirectHTTPChannel (no proxy).']

EDIT: Problem solved, after some attention from Adobe. See the posts below for a full explanation. This code will work for HTTP Authentication headers of arbitrary length.

import mx.utils.Base64Encoder;
private function authAndSend(service:HTTPService):void
{
        var encoder:Base64Encoder = new Base64Encoder();
        encoder.insertNewLines = false; // see below for why you need to do this
        encoder.encode("someusername:somepassword");

        service.headers = {Authorization:"Basic " + encoder.toString()};                                                
        service.send();
}
8

8 Answers

13
votes

Finally received some attention from Adobe and got an answer on this. The problem with long HTTP Authentication headers is that, by default, the Base64Encoder class will inject newline characters every 72 characters. Obviously that causes a chunk of the base-64 encoded string to be interpreted as a new header attribute, which causes the error.

You can fix this by setting (in the above example) encoder.insertNewLines = false; The default setting is true.

I've fixed the above code to work for arbitrarily long Authentication strings.

9
votes

Ah. The pain, the suffering. The sheer misery.

While you've figured out how to add a header before making your call, the nasty truth is that somewhere deep down in the Flash/browser integration space your headers are being removed again.

From my blogpost last year at verveguy.blogspot.com

So I have unraveled the Truth. (I think) It's more tortured than one would imagine

1/ All HTTP GET requests are stripped of headers. It's not in the Flex stack so it's probably the underlying Flash player runtime

2/ All HTTP GET requests that have content type other than application/x-www-form-urlencoded are turned into POST requests

3/ All HTTP POST requests that have no actual posted data are turned into GET requests. See 1/ and 2/

4/ All HTTP PUT and HTTP DELETE requests are turned into POST requests. This appears to be a browser limitation that the Flash player is stuck with. (?)

What this boils down to in practical terms is that if you want to pass headers in all requests, you should always use POST and you should find another way to communicate the semantics of the operation you "really wanted". The Rails community have settled on passing ?_method=PUT/DELETE as a work around for the browser problems underlying 4/

Since Flash adds the wonderful header stripping pain on GET, I'm also using ?_method=GET as a workaround for that. However, since this trips up on 3/, I am passing a dummy object as the encoded POST data. Which means my service needs to ignore dummy posted data on a ?_method=GET request.

Crucial at this point to know about 2/. That wasted a bunch of my time.

I've built all of this handling into a new RESTService class with MXML markup support so it's possible to pretend this doesn't exist on the client side.

Hope this helps someone.

1
votes

The setCredentials() & setRemoteCredentials() methods are intended for use with Flex/LiveCycle Data Services, so they probably don't apply in your case.

This ought to work for you. I was able to reproduce this behavior on my server, and this fix seems to have done the trick; it still seems a bit odd this isn't more API-user-friendly, considering how common a use case you'd think it were, but nonetheless, I've tested and verified this works, given a valid SSL cert:

private function authAndSend(service:HTTPService):void
{
        var encoder:Base64Encoder = new Base64Encoder();
        encoder.encode("someusername:somepassword");

        service.headers = {Authorization:"Basic " + encoder.toString()};                            
        service.send();
}

Hope it helps! And thanks for posting -- I'm sure I would've run into this one sooner or later myself. ;)

1
votes

This really has helped me! Thanks! I use Flex Builder 3

One note: WebService's property headers is read only. So I tried to use httpHeaders. It works!

    var encoder:Base64Encoder = new Base64Encoder();
    encoder.insertNewLines = false;
    encoder.encode("test:test");

    sfWS.httpHeaders = {Authorization:"Basic " + encoder.toString()};   
1
votes

I had the same problem while consuming HTTP Basic Authenticated Webservice. This is my solution; it works fine:

private function authAndSend(service:WebService):void
{
    var encoder:Base64Encoder = new Base64Encoder();
        encoder.insertNewLines = false; 
        encoder.encode("user:password");
    service.httpHeaders = { Authorization:"Basic " + encoder.ToString() };
    service.initialize();
}

usage

authAndSend(WebService( aWebServiceWrapper.serviceControl));
0
votes

Try using setCredentials rather than setRemoteCredentials and failing that, using Fiddler/Charles to find out what headers are being sent with the request.

0
votes

Also, just so other people don't spend 10 minutes working out why the correct example doesn't quite work asis, you need to import the mx.utils.Base64Encoder package eg:

        import mx.utils.Base64Encoder;

At the beginning or somewhere within the CDATA area. I'm new to flex so this wasn't quite obvious at first.

0
votes

This is how its done.

import mx.utils.Base64Encoder;
import mx.rpc.events.FaultEvent;
import mx.rpc.events.ResultEvent;
import mx.rpc.http.HTTPService;

var _oHttp:HTTPService = new HTTPService;
var sUsername:String = "theusername"
var sPassword:String = "thepassword";

var oEncoder:Base64Encoder = new Base64Encoder(); 
oEncoder.insertNewLines = false; 
oEncoder.encode(sUsername + ":" + sPassword); 

_oHttp.method = "POST";
_oHttp.headers = {Authorization:"Basic " + oEncoder.toString()};