I have the somewhat unusual use case and apple's useCredential:forAuthenticationChallenge can't cope (or maybe it's me?).
The issue is, that I am making two consecutive connections (the second is called after the first one has been completed) each with different credentials (different client certificate):
[[NSURLConnection alloc] initWithRequest:request delegate:self startImmediately:YES];
URLs of requests are:
https://my.url.com/ws/service1
https://my.url.com/ws/service2
Then I implement the delegate's method:
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] != 0) {
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
NSURLCredential *newCredential = nil;
NSURLProtectionSpace *protectionSpace = [challenge protectionSpace];
if ([protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodClientCertificate]) {
if (isFirstRequest) {
[[challenge sender] useCredential:credentials1 forAuthenticationChallenge:challenge];
} else {
[[challenge sender] useCredential:credentials2 forAuthenticationChallenge:challenge];
}
}
}
Credentials are created from identity (persistence flag has no effect whatsoever):
NSURLCredential* credential = [NSURLCredential credentialWithIdentity:identityRef
certificates:certificates
persistence:NSURLCredentialPersistenceNone];
I'm sure that both credentials are valid and that they should be working. But only the first call is successful. After that when the second request is made, willSendRequestForAuthenticationChallenge method is called and the correct useCredential:forAuthenticationChallange is called but it does not change the credentials and the first ones are used anyway!
From the documentation of - useCredential:forAuthenticationChallenge:
Attempt to use a given credential for a given authentication challenge. (required)
This method has no effect if it is called with an authentication challenge that has already been handled.
Is there a way to check if the challenge has already been handled or to reset the challenge so that the system does not use any cached credentials?
I already tried to erase cached credentials but the code below (from SO) always returned zero credentials:
- (void)eraseCredentials {
NSString *urlString = #"my.url.com";
NSURLCredentialStorage *credentialsStorage = [NSURLCredentialStorage sharedCredentialStorage];
NSDictionary *allCredentials = [credentialsStorage allCredentials];
if ([allCredentials count] > 0)
{
for (NSURLProtectionSpace *protectionSpace in allCredentials)
{
if ([[protectionSpace host] isEqualToString:urlString])
{
NSDictionary *credentials = [credentialsStorage credentialsForProtectionSpace:protectionSpace];
for (NSString *credentialKey in credentials)
{
[credentialsStorage removeCredential:[credentials objectForKey:credentialKey] forProtectionSpace:protectionSpace];
}
}
}
}
}
Note: What I already tried is to add the . or # after the URL as mentioned in TLS Session Cache or here at StackOverflow but I don't think that's the issue here because my willSendRequestForAuthenticationChallenge is called correctly multiple times as expected...jest the given credentials doesn't work.
Update: the request itself is being built as follows:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self resourceURLWithName:name]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30];
[request setHTTPShouldHandleCookies:NO];
Previously it was only:
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[self resourceURLWithName:name]];
But there was no change in behaviour.
Update 2: In the end it wasn't issue with useCredential:forAuthenticationChallenge: but it was an issue with identities. The one I used seemed to be ok but it wasn't. See my answer in this SO question
Implement below delegate method
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection
{
return NO;
}
Related
I'm making an iOS application that allows 2 users interact with each other. When first user navigates to a URL on his app using UIWebView, I will send this url to the second user. The second user receive this link and use UIWebView to load it.
Everything works well until one person goes into a private zone. For example, when the first user goes to www.google.com and logs into his gmail account. Now I capture this link and send to the second user, who tries to load it, but this fails, because it's a private zone of the first user.
How can the UIWebView of second user know that the received url is in private zone or must authenticate to access it?
Here some illustration codes:
On the first user, when navigates an url like www.google.com. I call this function
- (void)loadRequestUrl:(NSString *)url {
NSLog(#"load request");
//Load request
self.websiteUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:websiteUrl]];
[wbWebview loadRequest:request];
//Send url to partner
//I'm using GCDAsyncSocket to send this url string to our server
//And server will send it to my current partner
//Just example code for sending
[asyncSocket send:url];
}
//On second user. I received url string from a delegate of GCDAsyncSocket. In this delegate I call this function
- (void)receiveUrl:(NSString *)url {
//Just Load request
self.websiteUrl = [url stringByAddingPercentEscapesUsingEncoding:NSUTF8StringEncoding];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:websiteUrl]];
[wbWebview loadRequest:request];
}
Here an example of url that on first user after he logins his google account
https://accounts.google.com.vn/accounts/SetSID?ssdc=1&sidt=ALWU2cscH%252BVqOOjXXIRKgtz4Q8qmIcJ5lE0dy7xh7MISa%252Bw75BNSeOrF3cO91IED8Cy6PfREuDjuXLzdOMEPaaP0p6XZpCzJFQi4w2xAZa9VKubLQ5xk5%252FF%252FOj8KR0f6e5PSav%252Fww0mKEAuPoI0Dtnve600Pj6PERFtvlH3kbt2Y0hk4KEBpn6nk7zAXUdt2wc%252FaHK0%252FufzyfIMI2hkLpCFu1W1XaOIS3zwuGttA5tXjyLb3AeBLmPgfBbsd7hwZWp7IRVJGglde8gAJ%252F%252BmIhQD4eMQa1s7LD8tnuoagx%252BmRzQ4EGqtcAlE%252FGE3e8b1itkh2HXZQZYB612X1NpcPgta1XbgO7IHd0g7HsDEnsqodhHtr9F7vGl4fO%252BCcHFYaHjH3dT2mCjnOwBn%252Bbh0%252FykYpxqbx2W8K%252BHcZp3B4KI166qCPCZvgnvq7QACPsPuWGVrll4Nw2yLK%252FE2bdmFVILfgIpVbY9SheA%253D%253D&continue=http%253A%252F%252Fwww.google.com.vn%252F%253Fgfe_rd%253Dcr%2526ei%253Dgf7EU-TLL-zV8ge_rYCIAw
The second user receives it, but cannot open. I can NOT capture error on any delegate
IMPORTANCE: I don't need to force second user open it, I just want to capture the event to tell the second user that first user went into private zone
To receive and handle authentication challenge in uiwebview , here is the code.
- (void)viewDidLoad
{
// Load the web view with your url
[webView loadRequest:[NSURLRequest requestWithURL:[NSURL URLWithString:#"yoururl"]]];
isauth = NO;
}
Now for the delegates. This one is called before a new request is loaded into the webview.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType;
{
if (!isauth) {
isauth = NO;
[[[NSURLConnection alloc] initWithRequest:request delegate:self] autorelease];
return NO;
}
return YES;
}
//in nsurlconnections delegate. This one deals with the authentication challenge. this time set isauth to YES
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
if ([challenge previousFailureCount] == 0) {
isauth = YES;
[[challenge sender] useCredential:[NSURLCredential credentialWithUser:#"username" password:#"password" persistence:NSURLCredentialPersistencePermanent] forAuthenticationChallenge:challenge];
}
else
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
// if the authentication is successfully handled than you will get data in this method in which you can reload web view with same request again.
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
NSLog(#"received response via nsurlconnection");
NSURLRequest *urlRequest = [NSURLRequest requestWithURL:[NSURL URLWithString:#"yoururl"]];
[webView loadRequest:urlRequest];
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
return NO;
}
Hope it helps.
thank you.
to get user name and password , there are multiple ways of doing it , you can create your own mechanism for the same .One simple way is.
-
(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge: (NSURLAuthenticationChallenge *)challenge{
NSLog(#"Need Authentication");
UIAlertView *webLogin = [[UIAlertView alloc] initWithTitle:#"Auth"
message:#"Enter User and Password"
delegate:self
cancelButtonTitle:#"Cancel"
otherButtonTitles:#"OK"
, nil];
self. challenge = challenge;
webLogin.alertViewStyle = UIAlertViewStyleLoginAndPasswordInput;
[webLogin show];
}
-(void)alertView:(UIAlertView *)alertView clickedButtonAtIndex:(NSInteger)buttonIndex{
user = [[alertView textFieldAtIndex:0]text];
pass = [[alertView textFieldAtIndex:1]text];
if (buttonIndex == [alertView cancelButtonIndex]) {
[self dismissModalViewControllerAnimated:YES];
}
else if (buttonIndex != [alertView cancelButtonIndex]) {
[self handleChallenge:self.challenge withUser:user password:pass];
}
}
- (void)handleChallenge:(NSURLAuthenticationChallenge *)aChallenge withUser:(NSString *)userName password:(NSString *)password {
NSURLCredential *credential = [[NSURLCredential alloc]
initWithUser:userName password:password
persistence:NSURLCredentialPersistenceForSession];
[[aChallenge sender] useCredential:credential forAuthenticationChallenge:aChallenge];
}
Here's an issue: I need to implement both HTTP basic authorization and MS NTLM authorization. I use asynchronous NSURLConnection so I receive
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
callback. The full code of this method looks like that:
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSString* authMethod = [[challenge protectionSpace] authenticationMethod];
NSLog(#"Authentication method: %#", authMethod);
NSString *userName = self.login;
if ([authMethod isEqualToString:NSURLAuthenticationMethodNTLM]) {
userName = [userName lastElementAfterSlash];
}
else if ([authMethod isEqualToString:NSURLAuthenticationMethodNegotiate]) {
userName = [NSString stringWithFormat:#"%##%#", userName, self.url.host];
}
if ([challenge previousFailureCount] <= 1) {
NSURLCredential *credential = [NSURLCredential credentialWithUser:userName password:self.password persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:[self.url host]
port:0
protocol:self.url.scheme
realm:[self.url host]
authenticationMethod:authMethod];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
}
else {
NSLog(#"Authentication error");
NSLog(#"Failed login with status code: %d", [(NSHTTPURLResponse*)[challenge failureResponse]statusCode]);
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
The two global issues I've met so far using this method:
1) As per Charles sniffer, all the requests to the server are doubled (as per Apple Docs, it's an expected behaviour). However, it leads to lack of performance (two requests instead of just one) comparing to setting header directly, using setValue:forHTTPHeader:.
2) It's being called not for every request. For example, when I'm trying to grab an image from server, it returns 302 (redirect to login web page) HTTP code instead of 401 HTTP code, so this does not work at all.
What I want to accomplish, is to grab the correct Authorization header once and then put it manually in every subsequent NSMutableURLRequest I make. I can compose HTTP Basic authorization manually, it's pretty simple, but I can't compose NTLM header in the same manner, that's why I was relying on didReceiveAuthenticationChallenge: method.
Could you please point me out, how can I receive the Authorization header, that is set automatically by NSURLProtectionSpace, so each request will INITIALLY go authorized?
P.S. I tried to receive it from
[connection.currentRequest allHTTPHeaderFields];
but it returns an empty array. I'm fighting with that for more than two days, any help will be kindly appreciated!
**I am new to iOS development. I am developing small application which can open specified SharePoint site URL without manually passing require credential. The URL I am trying to open needs credential but I want to embed these credential to the request I will make to open the URL ins UIWebView control. I don't want to open the URL in Safari.
Would you please help me finding the solution?**
You can use -connection:didReceiveAuthenticationChallenge: delegate for your problem. First make a normal NSURLConnection as follow,
- (void) someMethod
{
NSURLRequest* request = [[NSURLRequest alloc]
initWithURL:[NSURL urlWithString:#"Your sharepoint web url"]
NSURLConnection* connection = [[NSURLConnection alloc]
initWithRequest:request delegate:self];
[connection release];
[request release];
}
After that your receive the call back. In here you should handle the challenge of credentials.
- (void) connection:(NSURLConnection *)connection
didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
// Make sure to use the appropriate authentication method for the server to
// which you are connecting.
if ([[challenge protectionSpace] authenticationMethod] ==
NSURLAuthenticationMethodBasicAuth)
{
// This is very, very important to check. Depending on how your
// security policies are setup, you could lock your user out of his
// or her account by trying to use the wrong credentials too many
// times in a row.
if ([challenge previousFailureCount] > 0)
{
[[challenge sender] cancelAuthenticationChallenge:challenge];
UIAlertView* alert = [[UIAlertView alloc]
initWithTitle:#"Invalid Credentials"
message:#"The credentials are invalid."
delegate:nil
cancelButtonTitle:#"OK"
otherButtonTitles:nil];
[alert show];
[alert release];
}
else
{
[challenge useCredential:[NSURLCredential
credentialWithUser:#"someUser"
password:#"somePassword"
persistence:NSURLCredentialPersistenceForSession
forAuthenticationChallenge:challenge]];
}
}
else
{
// Do whatever you want here, for educational purposes,
// I'm just going to cancel the challenge
[[challenge sender] cancelAuthenticationChallenge:challenge];
}
}
Update
Use this code for this link.
-(void)viewDidLoad{
NSString *strWebsiteUlr = [NSString stringWithFormat:#"http://www.roseindia.net"];
NSURL *url = [NSURL URLWithString:strWebsiteUlr];
NSURLRequest *requestObj = [NSURLRequest requestWithURL:url];
[webView loadRequest:requestObj];
[webview setDelegate:self]
}
In header file
#interface yourViewController : UIViewController<UIWebViewDelegate>{
Bool _authed;
}
#property (strong, nonatomic) IBOutlet UIWebView *webView;
I would like to support HTTP Basic Authentication in my UIWebView.
At the moment, I am canceling requests in
webView:shouldStartLoadWithRequest:navigationType: then handle them in my own NSURLConnectionDelegate to check for and provide credentials if needed. I then use loadData:MIMEType:textEncodingName:baseURL: to present HTML in the web view. That works fine for any URLs that are passed to the delegate.
My problem is that the delegate is never called for embedded elements, like images, JavaScript or CSS files. So if I have an HTML page which references an image which is protected with basic authentication, that image cannot be loaded properly. Additionally, webView:didFinishLoad: is never called, because the web view could not fully load the page.
I have checked that case with Terra, a third-party browser available on the App Store, and it can fully cope with that situation. I think it would be possible to solve this by providing my own NSURLProtocol, but that seems too complicated. What am I missing?
Try to use sharedCredentialStorage for all domains you need to authenticate.
Here is working sample for UIWebView it was tested against Windows IIS having only BasicAuthentication enabled
This is how to add your site credentials:
NSString* login = #"MYDOMAIN\\myname";
NSURLCredential *credential = [NSURLCredential credentialWithUser:login
password:#"mypassword"
persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:#"myhost"
port:80
protocol:#"http"
realm:#"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge
authenticationMethod:NSURLAuthenticationMethodDefault];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential
forProtectionSpace:protectionSpace];
[protectionSpace release];
Edit: same code in Swift 4
let login = "MYDOMAIN\\myname"
let credential = URLCredential(user:login, password:"mypassword", persistence:.forSession)
let protectionSpace = URLProtectionSpace(host:"myhost", port:80, protocol:"http", realm:"myhost", authenticationMethod:NSURLAuthenticationMethodDefault)
URLCredentialStorage.shared.setDefaultCredential(credential, for:protectionSpace)
Your webView is supposed to work now, if it does not work use next code to debug, especially check log messages of didReceiveAuthenticationChallenge.
#import "TheSplitAppDelegate.h"
#import "RootViewController.h"
#implementation TheSplitAppDelegate
#synthesize window = _window;
#synthesize splitViewController = _splitViewController;
#synthesize rootViewController = _rootViewController;
#synthesize detailViewController = _detailViewController;
- (BOOL)application:(UIApplication *)application didFinishLaunchingWithOptions:(NSDictionary *)launchOptions
{
// Override point for customization after application launch.
// Add the split view controller's view to the window and display.
self.window.rootViewController = self.splitViewController;
[self.window makeKeyAndVisible];
NSLog(#"CONNECTION: Add credentials");
NSString* login = #"MYDOMAIN\\myname";
NSURLCredential *credential = [NSURLCredential credentialWithUser:login
password:#"mypassword"
persistence:NSURLCredentialPersistenceForSession];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc]
initWithHost:#"myhost"
port:80
protocol:#"http"
realm:#"myhost" // check your web site settigns or log messages of didReceiveAuthenticationChallenge
authenticationMethod:NSURLAuthenticationMethodDefault];
[[NSURLCredentialStorage sharedCredentialStorage] setDefaultCredential:credential forProtectionSpace:protectionSpace];
[protectionSpace release];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:[NSURL URLWithString:#"http://myhost/index.html"]
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:12
];
NSLog(#"CONNECTION: Run request");
[[NSURLConnection alloc] initWithRequest:request delegate:self];
return YES;
}
- (void)applicationWillResignActive:(UIApplication *)application
{
}
- (void)applicationDidEnterBackground:(UIApplication *)application
{
}
- (void)applicationWillEnterForeground:(UIApplication *)application
{
}
- (void)applicationDidBecomeActive:(UIApplication *)application
{
}
- (void)applicationWillTerminate:(UIApplication *)application
{
}
- (void)dealloc
{
[_window release];
[_splitViewController release];
[_rootViewController release];
[_detailViewController release];
[super dealloc];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
NSLog(#"CONNECTION: got auth challange");
NSString* message = [NSString stringWithFormat:#"CONNECTION: cred cout = %i", [[[NSURLCredentialStorage sharedCredentialStorage] allCredentials] count]];
NSLog(message);
NSLog([connection description]);
NSLog([NSString stringWithFormat:#"CONNECTION: host = %#", [[challenge protectionSpace] host]]);
NSLog([NSString stringWithFormat:#"CONNECTION: port = %i", [[challenge protectionSpace] port]]);
NSLog([NSString stringWithFormat:#"CONNECTION: protocol = %#", [[challenge protectionSpace] protocol]]);
NSLog([NSString stringWithFormat:#"CONNECTION: realm = %#", [[challenge protectionSpace] realm]]);
NSLog([NSString stringWithFormat:#"CONNECTION: authenticationMethod = %#", [[challenge protectionSpace] authenticationMethod]]);
}
- (void)connection:(NSURLConnection *)connection didFailWithError:(NSError *)error{
// release the connection, and the data object
[connection release];
// inform the user
NSLog(#"CONNECTION: failed! Error - %# %#",
[error localizedDescription],
[[error userInfo] objectForKey:NSURLErrorFailingURLStringErrorKey]);
}
- (void)connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response;
{
NSLog(#"CONNECTION: received response via nsurlconnection");
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
NSLog(#"CONNECTION: USE!");
return YES;
}
#end
The final solution for WebView authentication was based on custom protocol implementation. All protocols registered as a stack, so if you redefine HTTP protocol it would intercept all requests coming from webView, so you have to check attributes assotiated with incoming request and repack it into new request and send it again via your own connection. Since you are in stack, your request immidiatly comes to you again and you have to ignore it. So it goes down protocol stack to real HTTP protocol implementation, since your request is not athenticated you'll get authenticaiton request. And after authenticaiton you'll get a real response from server, so you repack response and reply to original request received from webView and that's it.
Don;t try to create new requests or responses bodies, you have to just resend them. The final code would be aproximetly 30-40 lines of code and it is quite simple, but requires a lot of debuging and tetsing.
Unfortunatlly I cannot provide code here, since I am assigned to different project already, I just wanted to say that my post is wrong way, it stucks when user changes password.
The secret to HTTP basic authentication using cocoa is knowing NSURL and the related classes.
NSURL
NSURLRequest/NSMutableURLRequest
NSURLConnection
NSURLCredential
NSURLCredentialStorage
NSURLProtectionSpace
UIWebView/WebView/NIWebController etc.
The real magic comes from NSURLConnection. In the words of the devDocs, "An NSURLConnection object provides support to perform the loading of a URL request." If you want to load some a URL in the background without displaying it you would use NSURLConnection. The real power of the NSURLConnection is in the method
+ (NSURLConnection *)connectionWithRequest:(NSURLRequest *)request delegate:(id < NSURLConnectionDelegate >)delegate
The NSURLConnectionDelegate protocol has methods for responding to successful connections, fatal errors, and authentication challenges. If you are trying to access data Protected by HTTP basic authentication this is how Cocoa does it. At this point an example should bring some clarity.
//basic HTTP authentication
NSURL *url = [NSURL URLWithString: urlString];
NSMutableURLRequest *request;
request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:12];
[self.webView openRequest:request];
(void)[NSURLConnection connectionWithRequest:request delegate:self];
This creates a URL. From the URL a URLRequest is created. The URLRequest is then loaded in the web view. The Request is also used to make a URLConnection. We don't really use the connection, but we need to receive notifications about authentication so we set the delegate. There are only two methods we need from the delegate.
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge;
{
NSURLCredential * cred = [NSURLCredential credentialWithUser:#"username"
password:#"password"
persistence:NSURLCredentialPersistenceForSession];
[[NSURLCredentialStorage sharedCredentialStorage]setCredential:cred forProtectionSpace:[challenge protectionSpace]];
}
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
return YES;
}
Whenever there is an authentication challenge a credential is added to the credential storage. You also tell the connection to use the credential storage.
I've just implemented this by setting basic auth credentials using an NSMutableURLRequest for the UIWebView. This also avoids the round trip incurred when implementing sharedCredentialStorage (of course there are tradeoffs involved).
Solution:
NSString *url = #"http://www.my-url-which-requires-basic-auth.io"
NSString *authStr = [NSString stringWithFormat:#"%#:%#", username, password];
NSData *authData = [authStr dataUsingEncoding:NSASCIIStringEncoding];
NSString *authValue = [NSString stringWithFormat:#"Basic %#", [authData base64EncodedString]];
NSMutableURLRequest *mutableRequest = [[NSMutableURLRequest alloc] initWithURL:[NSURL URLWithString:url]];
[mutableRequest setValue:authValue forHTTPHeaderField:#"Authorization"];
NSURLRequest *request = [mutableRequest copy];
NSURLRequest *request = [NSURLRequest basicAuthHTTPURLRequestForUrl:url];
[self.webView loadRequest:request];
You can grab the NSData+Base64 category which implements the base64EncodedString for NSData from Matt Gallagher's page (it was at the bottom of the blog post when I downloaded it)
For TKAURLProtocolPro [http://kadao.dir.bg/cocoa.htm]
For SVWebViewController [https://github.com/samvermette/SVWebViewController]
Make sure to remember that logging out is not so easy with sessions and UIWebView credentials. See answer here: https://stackoverflow.com/a/18143902/2116338.
I'm trying to get the three above working properly, and something is not clicking. Specifically, the authentication request is not being triggered when it appears it should be. According to what I've read here, the relevant pieces are:
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace
{
NSLog(#"protection space");
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"auth challenge");
NSInteger count = [challenge previousFailureCount];
if (count > 0)
{
NSLog(#"count > 0");
NSObject<ServiceDelegate> *delegate = [currentRequest delegate];
[[challenge sender] cancelAuthenticationChallenge:challenge];
if ([delegate respondsToSelector:#selector(wrapperHasBadCredentials:)])
{
[delegate rbService:self wrapperHasBadCredentials:self];
}
return;
}
NSArray *trustedHosts = [[NSArray alloc] initWithObjects:#"myserver", nil];
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust])
{
NSLog(#"server trust");
if ([trustedHosts containsObject:challenge.protectionSpace.host])
{
[challenge.sender useCredential:[NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust] forAuthenticationChallenge:challenge];
}
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
else
{
NSLog(#"credentials");
NSURLCredential* credential = [[[NSURLCredential alloc] initWithUser:#"xxx" password:#"xxx" persistence:NSURLCredentialPersistenceForSession] autorelease];
[[challenge sender] useCredential:credential forAuthenticationChallenge:challenge];
}
}
The target server is set up for two URLS, one HTTPS and one HTTP, both of which prompt for username/password using basic authentication. I've checked the target server using firefox and everything seems to work as advertised. The target server uses an untrusted cert, but I thought I'd taken care of that in the code. Maybe not.
The specific behavior in the application:
When using HTTP the log reads:
log - protection space
(then returns 401 code)
When using HTTPS:
log - protection space
log - auth challenge
log - server trust
log - protection space
(then returns a 401 code)
In the first instance, it gets to the canAuthenticate... section, returns, but then doesn't challenge for the authentication, and returns a 401 response.
In the second instance, it does all that, actually does challenge, then goes to the canAuthenticate... section again, returns, and returns a 401 response.
Keep in mind that the request is a POST request, complete with headers and an HTTPBody. The authentication is not included in the headers (I would rather not do that), but if there is no other solution I will try things that way.
As always, thank you very much for the help. It's priceless.
It looks like the answer here has to be sending the authentication along with the POST request. I was thinking that the challenge/response process would somehow add those headers to the request by itself, but apparently not.