Search code examples
uiwebviewbasic-authenticationuiwebviewdelegate

UIWebView resource loading with Basic Authentication


For a (web)application I am creating, I need to use Basic Authentication to load pages in my UIWebView.

Now to set the authorization header I use:

NSString *result = [NSString stringWithFormat:@"%@:%@", username, password];
NSData *resultData = [result dataUsingEncoding:NSASCIIStringEncoding];

NSString *result64 = [NSString stringWithFormat:@"Basic %@", [resultData base64Encoding]];
[request setValue:result64 forHTTPHeaderField:@"Authorization"];

In my apache access logs I can see the login is a success. But then the UIWebView wants to load the resources like style.css and jquery.min.js but requests to these resources fail because they have no Authorization header set. How can I fix this?


Solution

  • The solution was to subclass NSURLProtocol to authenticate the resource loading:

    #import <Foundation/Foundation.h>
    
    #import "AuthenticationUtils.h"
    
    @interface CustomURLProtocol : NSURLProtocol <NSURLConnectionDelegate>
    {
        NSMutableURLRequest *_customRequest;
        NSURLConnection *_connection;
    }
    
    @end
    
    @implementation CustomURLProtocol
    
    static NSString *AUTHORIZED_REQUEST_HEADER = @"X-AUTHORIZED";
    
    +(BOOL) canInitWithRequest:(NSMutableURLRequest *)request
    {
        // check if the request is one you want to authorize
        BOOL canInit = (![request.URL.scheme isEqualToString:@"file"] && [request valueForHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]] == nil);
        return canInit;
    }
    
    -(id) initWithRequest:(NSURLRequest *)request cachedResponse:(NSCachedURLResponse *)cachedResponse client:(id<NSURLProtocolClient>)client
    {        
        _customRequest = [request mutableCopy];
        [_customRequest setValue:@"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
    
        self = [super initWithRequest:_customRequest cachedResponse:cachedResponse client:client];
    
        return self;
    }
    
    +(NSURLRequest *) canonicalRequestForRequest:(NSURLRequest *)request
    {
        NSMutableURLRequest *customRequest = [request mutableCopy];
        [customRequest setValue:@"" forHTTPHeaderField:[AUTHORIZED_REQUEST_HEADER stringByAppendingString:[request.URL absoluteString]]];
    
        NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
        [customRequest setValue:basicAuthentication forHTTPHeaderField:@"Authorization"];
    
        return customRequest;
    }
    
    - (void) startLoading
    {
        NSString *basicAuthentication = [AuthenticationUtils getBasicAuthentication];
        [_customRequest setValue:basicAuthentication forHTTPHeaderField:@"Authorization"];
    
        _connection = [NSURLConnection connectionWithRequest:_customRequest delegate:self];
    }
    
    - (void) stopLoading
    {
        [_connection cancel];
    }
    
    #pragma mark - NSURLConnectionDelegate
    
    -(void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response 
    {
        // This protocol forgets to store cookies, so do it manually
        if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
        {
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
        }
    
        [[self client] URLProtocol:self didReceiveResponse:response cacheStoragePolicy:NSURLCacheStorageNotAllowed];
    }
    
    - (void)connection:(NSURLConnection *)connection didReceiveData:(NSData *)data
    {
        [[self client] URLProtocol:self didLoadData:data];
    }
    
    - (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error 
    {
        [[self client] URLProtocol:self didFailWithError:error];
    }
    
    -(void)connectionDidFinishLoading:(NSURLConnection *)connection 
    {
        [[self client] URLProtocolDidFinishLoading:self];
    }
    
    -(NSURLRequest *) connection:(NSURLConnection *)connection willSendRequest:(NSURLRequest *)request redirectResponse:(NSURLResponse *)redirectResponse
    {    
        // This protocol forgets to store cookies, so do it manually
        if([redirectResponse isKindOfClass:[NSHTTPURLResponse class]])
        {
            [[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookies:[NSHTTPCookie cookiesWithResponseHeaderFields:[(NSHTTPURLResponse*)redirectResponse allHeaderFields] forURL:[redirectResponse URL]] forURL:[redirectResponse URL] mainDocumentURL:[request mainDocumentURL]];
        }
    
        // copy all headers to the new request
        NSMutableURLRequest *redirect = [request mutableCopy];
        for (NSString *header in [request allHTTPHeaderFields]) 
        {
            [redirect setValue:[[request allHTTPHeaderFields] objectForKey:header] forHTTPHeaderField:header];
        }
    
        return redirect;
    }
    
    @end
    

    And in your AppDelegate add this:

    [NSURLProtocol registerClass:[CustomURLProtocol class]];