I'm trying to extend functionality of SocketRocket library. I want to add authentication feature.
Since this library is using CFNetwork
CFHTTPMessage*
API for HTTP functionality (needed to start web socket connection) I'm trying to utilize this API to provide authentication.
There is perfectly matching function for that: CFHTTPMessageAddAuthentication
, but it doesn't work as I'm expecting (as I understand documentation).
Here is sample of code showing the problem:
- (CFHTTPMessageRef)createAuthenticationHandShakeRequest: (CFHTTPMessageRef)chalengeMessage {
CFHTTPMessageRef request = [self createHandshakeRequest];
BOOL result = CFHTTPMessageAddAuthentication(request,
chalengeMessage,
(__bridge CFStringRef)self.credentials.user,
(__bridge CFStringRef)self.credentials.password,
kCFHTTPAuthenticationSchemeDigest, /* I've also tried NULL for use strongest supplied authentication */
NO);
if (!result) {
NSString *chalengeDescription = [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(chalengeMessage))
encoding: NSUTF8StringEncoding];
NSString *requestDescription = [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(request))
encoding: NSUTF8StringEncoding];
SRFastLog(@"Failed to add authentication data `%@` to a request:\n%@After a chalenge:\n%@",
self.credentials, requestDescription, chalengeDescription);
}
return request;
}
requestDescription
content is:
GET /digest-auth/auth/user/passwd HTTP/1.1
Host: httpbin.org
Sec-WebSocket-Version: 13
Upgrade: websocket
Sec-WebSocket-Key: 3P5YiQDt+g/wgxHe71Af5Q==
Connection: Upgrade
Origin: http://httpbin.org/
chalengeDescription
contains:
HTTP/1.1 401 UNAUTHORIZED
Server: nginx
Content-Type: text/html; charset=utf-8
Set-Cookie: fake=fake_value
Access-Control-Allow-Origin: http://httpbin.org/
Access-Control-Allow-Credentials: true
Date: Mon, 29 Jun 2015 12:21:33 GMT
Proxy-Support: Session-Based-Authentication
Www-Authenticate: Digest nonce="0c7479b412e665b8685bea67580cf391", opaque="4ac236a2cec0fc3b07ef4d628a4aa679", realm="[email protected]", qop=auth
Content-Length: 0
Connection: keep-alive
user
and password
values are valid ("user" "passwd").
Why CFHTTPMessageAddAuthentication
returns NO
? There is no clue what is the problem. I've also try updated with credentials an empty request but without luck.
I've used http://httpbin.org/
just for testing (functionality of web socket is irrelevant at this step).
Please not that used code doesn't use (and never will) NSURLRequst
or NSURLSession
or NSURLConnection
/
I've tried to use different functions:
CFHTTPAuthenticationCreateFromResponse
and CFHTTPMessageApplyCredentials
with same result.
At least CFHTTPMessageApplyCredentials
returns some error information in form of CFStreamError
. Problem is that this error information is useless: error.domain = 4
, error.error = -1000
where those values are not documented anywhere.The only documented values looks like this:
typedef CF_ENUM(CFIndex, CFStreamErrorDomain) {
kCFStreamErrorDomainCustom = -1L, /* custom to the kind of stream in question */
kCFStreamErrorDomainPOSIX = 1, /* POSIX errno; interpret using <sys/errno.h> */
kCFStreamErrorDomainMacOSStatus /* OSStatus type from Carbon APIs; interpret using <MacTypes.h> */
};
CFHTTPAuthenticationCreateFromResponse
returns invalid object, which description returns this:
<CFHTTPAuthentication 0x108810450>{state = Failed; scheme = <undecided>, forProxy = false}
I've found in documentation what those values means: domain=kCFStreamErrorDomainHTTP
, error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
(thanks @JensAlfke I've found it before your comment). Why it is unsupported? Documentation claims that digest is supported there is a constant kCFHTTPAuthenticationSchemeDigest
which is accepted and expected by CFHTTPMessageAddAuthentication
!
I've dig up source code of
CFNetwork
authentication and trying figure out what is the problem.
I have to do some mistake since this simple tast application also fails:
#import <Foundation/Foundation.h>
#import <CFNetwork/CFNetwork.h>
static NSString * const kHTTPAuthHeaderName = @"WWW-Authenticate";
static NSString * const kHTTPDigestChallengeExample1 = @"Digest realm=\"[email protected]\", "
"qop=\"auth,auth-int\", "
"nonce=\"dcd98b7102dd2f0e8b11d0f600bfb0c093\", "
"opaque=\"5ccc069c403ebaf9f0171e9517f40e41\"";
static NSString * const kHTTPDigestChallengeExample2 = @"Digest nonce=\"b6921981b6437a4f138ba7d631bcda37\", "
"opaque=\"3de7d2bd5708ac88904acbacbbebc4a2\", "
"realm=\"[email protected]\", "
"qop=auth";
static NSString * const kHTTPBasicChallengeExample1 = @"Basic realm=\"Fake Realm\"";
#define RETURN_STRING_IF_CONSTANT(a, x) if ((a) == (x)) return @ #x
NSString *NSStringFromCFErrorDomain(CFIndex domain) {
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainHTTP);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainFTP);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSSL);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSystemConfiguration);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainSOCKS);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainPOSIX);
RETURN_STRING_IF_CONSTANT(domain, kCFStreamErrorDomainMacOSStatus);
return [NSString stringWithFormat: @"UnknownDomain=%ld", domain];
}
NSString *NSStringFromCFErrorError(SInt32 error) {
RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationTypeUnsupported);
RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadUserName);
RETURN_STRING_IF_CONSTANT(error, kCFStreamErrorHTTPAuthenticationBadPassword);
return [NSString stringWithFormat: @"UnknownError=%d", (int)error];
}
NSString *NSStringFromCFHTTPMessage(CFHTTPMessageRef message) {
return [[NSString alloc] initWithData: CFBridgingRelease(CFHTTPMessageCopySerializedMessage(message))
encoding: NSUTF8StringEncoding];
}
void testAuthenticationHeader(NSString *authenticatiohHeader) {
CFHTTPMessageRef response = CFHTTPMessageCreateResponse(kCFAllocatorDefault,
401,
NULL,
kCFHTTPVersion1_1);
CFAutorelease(response);
CFHTTPMessageSetHeaderFieldValue(response,
(__bridge CFStringRef)kHTTPAuthHeaderName,
(__bridge CFStringRef)authenticatiohHeader);
CFHTTPAuthenticationRef authData = CFHTTPAuthenticationCreateFromResponse(kCFAllocatorDefault, response);
CFAutorelease(authData);
CFStreamError error;
BOOL validAuthData = CFHTTPAuthenticationIsValid(authData, &error);
NSLog(@"testing header value: %@\n%@authData are %@ error.domain=%@ error.error=%@\n\n",
authenticatiohHeader, NSStringFromCFHTTPMessage(response),
validAuthData?@"Valid":@"INVALID",
NSStringFromCFErrorDomain(error.domain), NSStringFromCFErrorError(error.error));
}
int main(int argc, const char * argv[]) {
@autoreleasepool {
testAuthenticationHeader(kHTTPDigestChallengeExample1);
testAuthenticationHeader(kHTTPDigestChallengeExample2);
testAuthenticationHeader(kHTTPBasicChallengeExample1);
}
return 0;
}
Logs show:
2015-07-01 16:33:57.659 cfauthtest[24742:600143] testing header value: Digest realm="[email protected]", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest realm="[email protected]", qop="auth,auth-int", nonce="dcd98b7102dd2f0e8b11d0f600bfb0c093", opaque="5ccc069c403ebaf9f0171e9517f40e41"
authData are INVALID error.domain=kCFStreamErrorDomainHTTP error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
2015-07-01 16:33:57.660 cfauthtest[24742:600143] testing header value: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="[email protected]", qop=auth
HTTP/1.1 401 Unauthorized
Www-Authenticate: Digest nonce="b6921981b6437a4f138ba7d631bcda37", opaque="3de7d2bd5708ac88904acbacbbebc4a2", realm="[email protected]", qop=auth
authData are INVALID error.domain=kCFStreamErrorDomainHTTP error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
2015-07-01 16:33:57.660 cfauthtest[24742:600143] testing header value: Basic realm="Fake Realm"
HTTP/1.1 401 Unauthorized
Www-Authenticate: Basic realm="Fake Realm"
authData are INVALID error.domain=kCFStreamErrorDomainHTTP error.error=kCFStreamErrorHTTPAuthenticationTypeUnsupported
edit after my own answer:
Alternative solution
Other possible solution is to manually parse WWW-Authenticate
response header and precess it and generate Authorization
header for new request.
Is there some simple library or sample code I could use in commercial application which will do this (only this)? I could do this my self but this will take a precious time. Bounty is still available :).
CFHTTPMessage
which operates onCFStream
and you are referring to higher level APINSURLConnection
orNSURLSession
. For some strange reasonCFHTTPMessageAddAuthentication
has refused to add authentication data to my request and there is no information why. – CheathamNSURLRequest
(actually anNSMutableURLRequest
object) with- (id)initWithURLRequest:(NSURLRequest *)request;
? Just not sure that when it runs through_urlRequest.allHTTPHeaderFields
it will correctly add the Auth object usingCFHTTPMessageSetHeaderFieldValue(request, (__bridge CFStringRef)key, (__bridge CFStringRef)obj);
– SandraNSURLRequest
here is not available since HTTP is used only as a handshake to start web socket connection so lower level API is used for HTTP. – CheathamSRWebSocket.h
) at line64
you will see- (id)initWithURLRequest:(NSURLRequest *)request;
soNSURLRequest
is in fact available. Like I say I don't know your implementation, so thats all the advise I can give. Good luck. – SandraNSURLRequest
is used only as temporary storage for headers and URL and nothing else. For HTTP protocol CFNetwork API is used only. – CheathamCFHTTPAuthentication.c
– Cheatham