NSURLSession, HTTP Basic Authentication and Self-Signed Certificate - basic-authentication

I call a web service with the HTTPS scheme. To access to web service I need a username and password. I implemented the http basic authentication.
I always get the 401 error and I am sure that username and password are correct. Here is the code I used (I saw this code here: How can I pass credentials while using NSURLSession). Suggestions? Thank you!
NSURL *url = [NSURL URLWithString:#"http://myurl...."];
NSString *user = #"userna,e";
NSString *password = #"password";
NSURLCredential *defaultCredential = [NSURLCredential credentialWithUser:user password:password persistence:NSURLCredentialPersistencePermanent];
NSString *host = [url host];
NSInteger port = [[url port] integerValue];
NSString *protocol = [url scheme];
NSURLProtectionSpace *protectionSpace = [[NSURLProtectionSpace alloc] initWithHost:host port:port protocol:protocol realm:nil authenticationMethod:NSURLAuthenticationMethodHTTPBasic];
NSURLCredentialStorage *credentials = [NSURLCredentialStorage sharedCredentialStorage];
[credentials setDefaultCredential:defaultCredential forProtectionSpace:protectionSpace];
NSURLSessionConfiguration *config = [NSURLSessionConfiguration defaultSessionConfiguration];
[config setURLCredentialStorage:credentials];
NSURLSession *session = [NSURLSession sessionWithConfiguration:config delegate:self delegateQueue:nil];
[[session dataTaskWithURL:url completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
if (!error) {
NSHTTPURLResponse *httpResp = (NSHTTPURLResponse*) response;
NSLog(#"%d", httpResp.statusCode);
}
}] resume];
...
// ONLY FOR SELF-SIGNED CERTIFICATE!!! DANGEROUS!!
- (void)URLSession:(NSURLSession *)session didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition, NSURLCredential *))completionHandler
{
NSURLCredential *credential = [NSURLCredential credentialForTrust:challenge.protectionSpace.serverTrust];
completionHandler(NSURLSessionAuthChallengeUseCredential,credential);
}

For basic auth I didn't manage to getting working with the way the docs described but I could just do exactly what the docs say not to do and set the header directly on the session.
This is the Swift version.
let sessionConfig = NSURLSessionConfiguration.defaultSessionConfiguration()
sessionConfig.HTTPAdditionalHeaders = ["Authorization":"Basic fjdslkfsdfk="]
I think you can also set them directly on the request. Obj-C equivalent is left as an exercise for the reader.
There is a good blog post here that explains how the credential delegate should be used.

Related

Google sign-in using AppAuth and cross-client identity

I am using AppAuth to implement google sign-in. The app could successfully authenticate. But I need an id_token for my server so that I can communicate with the my server from my app. For that I believe I need to include audience:server:client_id:WEB_CLIENT_ID as shown in the following link.
https://developers.google.com/identity/sign-in/android/v1/backend-auth
More information is available here:
https://developers.google.com/identity/protocols/CrossClientAuth
How can I use my web client id from the app to get an id_token so that I can reliably communicate with my server using that token?
The scope audience:server:client_id:WEB_CLIENT_ID is specific to Android. For iOS we need to send audience=WEB_CLIENT_ID as parameter to token endpoint.
It works in my case using the following code.
OIDServiceConfiguration *configuration = [[OIDServiceConfiguration alloc] initWithAuthorizationEndpoint:authorizationEndpoint tokenEndpoint:tokenEndpoint];
// builds authentication request
OIDAuthorizationRequest *authorizationRequest =
[[OIDAuthorizationRequest alloc] initWithConfiguration:configuration
clientId:kClientId
scopes:#[OIDScopeOpenID,
OIDScopeEmail]
redirectURL:[NSURL URLWithString:kRedirectUri]
responseType:OIDResponseTypeCode
additionalParameters:nil];
// performs authentication request
OIDAuthorizationUICoordinatorIOS *coordinator = [[OIDAuthorizationUICoordinatorIOS alloc]
initWithPresentingViewController:self];
id<OIDAuthorizationFlowSession> authFlowSession = [OIDAuthorizationService
presentAuthorizationRequest:authorizationRequest
UICoordinator:coordinator
callback:^(OIDAuthorizationResponse *_Nullable authorizationResponse,
NSError *_Nullable authorizationError) {
// inspects response and processes further if needed (e.g. authorization
// code exchange)
if (authorizationResponse) {
if ([authorizationRequest.responseType
isEqualToString:OIDResponseTypeCode]) {
// if the request is for the code flow (NB. not hybrid), assumes the
// code is intended for this client, and performs the authorization
// code exchange
OIDTokenRequest *tokenExchangeRequest =
[[OIDTokenRequest alloc] initWithConfiguration:authorizationRequest.configuration
grantType:OIDGrantTypeAuthorizationCode
authorizationCode:authorizationResponse.authorizationCode
redirectURL:authorizationRequest.redirectURL
clientID:authorizationRequest.clientID
clientSecret:authorizationRequest.clientSecret
scope:authorizationRequest.scope
refreshToken:nil
codeVerifier:authorizationRequest.codeVerifier
additionalParameters:#{#"audience":kWebClientId}];
//tokenExchangeRequest.scope = kAudienceServerClientId;
[OIDAuthorizationService
performTokenRequest:tokenExchangeRequest
callback:^(OIDTokenResponse *_Nullable tokenResponse,
NSError *_Nullable tokenError) {
OIDAuthState *authState;
if (tokenResponse) {
authState = [[OIDAuthState alloc]
initWithAuthorizationResponse:
authorizationResponse
tokenResponse:tokenResponse];
}
[self onSignInResponse:authState error:tokenError];
}];
} else {
// implicit or hybrid flow (hybrid flow assumes code is not for this
// client)
OIDAuthState *authState = [[OIDAuthState alloc]
initWithAuthorizationResponse:authorizationResponse];
[self onSignInResponse:authState error:authorizationError];
}
} else {
[self onSignInResponse:nil error:authorizationError];
}
}];
MyAppDelegate *appDelegate = [MyAppDelegate sharedInstance];
appDelegate.currentAuthorizationFlow = authFlowSession;

Issues with UIWebView and HTTP Basic Auth

I've scoured the web on how to do basic http auth in a uiwebview. I've tried it all and am getting inconsistent results no matter what I do. Sometimes auth works, sometimes it doesn't. It's driving me up the wall and I'd love some help!
Here's the relevant code. I always get an auth challenge, but the webview doesn't always load (I basically time out waiting for webViewDidFinishLoad - no errors). Any ideas?
/*
Load a webview
*/
- (void)loadWebView
{
NSLog(#"loading webview");
if( _webView == NULL ){
// UIWebView initialization
_webView = [[UIWebView alloc] init];
_webView.hidden = TRUE;
}
// set up webview request
NSURL *url = [NSURL URLWithString:BASE_URL];
NSMutableURLRequest* request = [NSMutableURLRequest requestWithURL:url
cachePolicy:NSURLRequestReloadIgnoringCacheData
timeoutInterval:30];
// load the request
[_webView setDelegate:self];
[_webView loadRequest:request];
// add ourselves as delegate to connection events so we can authenticate
(void)[NSURLConnection connectionWithRequest:request delegate:self];
}
/**
authenticates against HTTP basic authorization
*/
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
//receive a authenticate and challenge with the user credential
NSLog(#"got auth challenge. Authenticating...");
if ([challenge.protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodHTTPBasic] &&
[challenge previousFailureCount] == 0)
{
NSURLCredential *credentail = [NSURLCredential
credentialWithUser:AUTH_USERNAME
password:AUTH_PASS
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:credentail forAuthenticationChallenge:challenge];
}
else
{
UIAlertView *alert = [[UIAlertView alloc] initWithTitle:#"Error Message" message:#"Invalid credentails" delegate:self cancelButtonTitle:#"OK" otherButtonTitles:nil];
[alert show];
}
}
/**
store auth credentials
*/
- (BOOL)connectionShouldUseCredentialStorage:(NSURLConnection *)connection;
{
NSLog(#"using credential storage");
return YES;
}
You can pass authentication in url instead of request header
please try instead of BASE_URL:
NSString* urlString = [NSString stringWithFormat:#"%#//%#:%##%#",#"http:",userName,password,myUrl]

iOS: How can I add credentials to my code for my NSURL?

I'm trying to access a page that has xml I'm trying to parse. I'm using the following code which works as long as I do not need credentials. How can I add credentials to this code?
NSURL *url = [[NSURL alloc] initWithString:URLPath];
NSXMLParser *xmlParser = [[NSXMLParser alloc] initWithContentsOfURL:url];
[xmlParser setDelegate: self];
success = [xmlParser parse];
Above my "URLPath" is my url string. I've tried using the following url path format but it doesn't work for me. http://username:password#mypage.com/path/to/file.aspx?Function=Update
Thanks!
Use the NSURLConnectionDelegates
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
if ([challenge previousFailureCount] == 0) {
NSLog(#"received authentication challenge");
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:#"USER"
password:#"PASSWORD"
persistence:NSURLCredentialPersistenceForSession];
NSLog(#"credential created");
[[challenge sender] useCredential:newCredential forAuthenticationChallenge:challenge];
NSLog(#"responded to authentication challenge");
}
else {
NSLog(#"previous authentication failure");
}
}
See NSURLConnection and Basic HTTP Authentication in iOS for more information

UIWebKit Insufficiencies

Like plenty of other people before me, I have some web content that I want to display in my app. My site is an ASP.NET-based site developed in Microsoft Visual Web Developer, and it uses Ajax Toolkit among other nifty addons. I can open up the site in my iPhone's mobile Safari browser, and I've even thrown together some mobile-friendly pages.
I (or more accurately, my boss) want an iPhone app which stores credentials used to access my site and sends them automatically upon opening the app. Here's the problem:
My site doesn't load in a UIWebView. It doesn't run into any of the delegate methods either (finish loading, failed to load, etc). It simply starts up the connection request and sits there. The site does work in Safari on the same device I'm using for testing, so I figure the problem has something to do with UIWebKit not having some tools built in that Safari does.
What (specifically) is causing my site not to load in UIWebKit, when it is loading correctly in Safari?
I will post the methods I have set up to catch any signs of life from my call to
[myWebView loadRequest:myRequest];
None of the following methods are being called when I run my app and reach the loadRequest call above.
- (void)connection:(NSURLConnection *)connection willSendRequestForAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge
{
NSLog(#"WillSendForAuthenticationChallenge");
}
- (void)webView:(UIWebView *)webView didFailLoadWithError:(NSError *)error
{
NSLog(#"Webview failed to load.");
if (error.code == NSURLErrorCancelled) return; // this is Error -999, called when the user forcibly ends the connection by starting a new connection.
UIAlertView *failure = [[UIAlertView alloc] initWithTitle:#"Failed" message:error.localizedDescription delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[failure show];
[spinner stopAnimating];
}
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"Loading request: %#",request.URL);
return YES;
}
- (void)webViewDidFinishLoad:(UIWebView *)webview
{
NSLog(#"Webview finished loading.");
[spinner stopAnimating];
}
- (BOOL)connection:(NSURLConnection *)connection canAuthenticateAgainstProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
NSLog(#"Can Authenticate: %#",[protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust]);
return [protectionSpace.authenticationMethod isEqualToString:NSURLAuthenticationMethodServerTrust];
}
- (void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSLog(#"Received an authentication challenge.");
[challenge.sender continueWithoutCredentialForAuthenticationChallenge:challenge];
}
Well, here's how I solved it. The UIWebView never calls any of the NSURLConnection methods, and it doesn't have any built-in security methods, so when the web view is about to load a page, I first send an NSURLConnection to the same address, which can then handle the authentication through methods like didReceiveAuthenticationChallenge. The variable "_sessionChecked" is a simple BOOL which keep track of whether or not the authenticating NSURLConnection has already been sent for this page. Once it has, I can simply load the page into the web view normally.
- (BOOL)webView:(UIWebView *)webView shouldStartLoadWithRequest:(NSURLRequest *)request navigationType:(UIWebViewNavigationType)navigationType
{
NSLog(#"Loading request: %#",request.URL);
if (_sessionChecked) {
NSLog(#"Session already checked.");
return YES;
}
NSLog(#"Will check session.");
NSMutableURLRequest *newReq = request.mutableCopy;
[newReq setValue:USRAGNT forHTTPHeaderField:#"User-Agent"];
NSURLConnection *conn = [NSURLConnection connectionWithRequest:newReq delegate:self];
if (conn == nil) {
NSLog(#"Cannot create connection.");
}
return NO;
}
The didReceiveResponse method is called when the NSURLConnection gets a response from the server. This response contains two important things: whether or not the connection was successfully authenticated with the credentials supplied by the NSURLRequest, and what url to be directed to if it was. Upon successful authentication, I send the new url to the web view to be loaded.
- (void) connection:(NSURLConnection *)connection didReceiveResponse:(NSURLResponse *)response
{
NSLog(#"connection:didReceiveResponse");
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
_sessionChecked = YES;
NSString *newUrl = #"";
NSHTTPURLResponse *httpResponse = (NSHTTPURLResponse *) response;
int status = [httpResponse statusCode];
if (status == 401 || status == 403) {
NSLog(#"Not authenticated. http status code: %d", status);
newUrl = #"";
[myWebView stopLoading];
[spinner stopAnimating];
UIAlertView *failed = [[UIAlertView alloc] initWithTitle:#"Authentication Failed" message:#"You do not have any credentials stored, or your stored credentials are not valid." delegate:self cancelButtonTitle:nil otherButtonTitles:#"OK", nil];
[failed show];
_sessionChecked = NO;
}
else {
NSLog(#"Authenticated. http status code: %d", status);
newUrl = [NSString stringWithFormat:#"%#", response.URL];
}
// cancel the connection. we got what we want from the response,
// no need to download the response data.
[connection cancel];
// start loading the new request in webView
NSURLRequest *req = [NSURLRequest requestWithURL:[NSURL URLWithString:newUrl]];
[myWebView loadRequest:req];
}
}
Many thanks to Ardal Ahmet for this how-to.

iOS and ADFS protected web site

I have a web application that is protected using ADFS, this was previously NTLM. We used to be able to retrieve data using the ASIHTTPRequest classes, but it does not appear to work with ADFS. The response is a redirect to the login url.
Is this the correct method of connecting from iOS to a ADFS protected URL?
NSURL *url = [NSURL URLWithString:#"https://URL_TO_WCF_SERVICE"];
ASIHTTPRequest *request = [ASIHTTPRequest requestWithURL:url];
[request addRequestHeader:#"Authorization" value:[NSString stringWithFormat:#"Basic %#",[ASIHTTPRequest base64forData:[[NSString stringWithFormat:#"%#:%#",userName, password] dataUsingEncoding:NSUTF8StringEncoding]]]];
[request startSynchronous];
NSError *error = [request error];
if (!error) {
NSString *response = [request responseString];
NSLog(#"%#", response);
}
It might be related to Extended Protection. See NTLM authentication to AD FS for non-IE browser without 'Extended Protection' switched off? for more info.
HTH!

Resources