HI, i am about to create an iphone application that will have a account system . ( login/logout ) .
that will have a server side also. so how to do session management. while your client is iphone
how i can do that ??
I use the ASIHTTPRequest library to communicate with my webservice.
It has built-in capability to handle cookies, so I simply login with a POST request and the cookie is set like a normal browser.
When your network connection is down, you can still check for a valid cookie:
- (BOOL) hasSignInCookie
{
NSArray *cookieJar = [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies];
for( NSHTTPCookie *cookie in cookieJar)
{
if( [[cookie name] compare: #"JourneyTagID"] == NSOrderedSame)
{
return YES;
}
}
return NO;
}
If it's a webapp, or your server-side is going to be a webservice, you probably want to be using HTTP cookies.
Otherwise, you could come up with some custom scheme where you assign a session id to the client and associate it with their state on the server side. The client provides this session id in future requests.
Things to think about here would include persistence/expiry, both server-side and client-side. Also, security (is your scheme susceptible to brute force or prediction attacks? Should you be encrypting the communication where this is assigned/provided?)
Regarding the iPhone, you may want to make your session id specific to a particular UDID (unique hardware address).
Related
I'm trying get RestKit (version 0.10.2) to authenticate with OAuth2. I'm using GTMOAuth2 to handle the OAuth interactions.
I've successfully gotten GTMOAuth2 to sign me in and make a request to the api I'm using.
I've also managed to get RestKit to make a request with my access token with this:
- (void)setRKAuthorizationToken:(NSString *)authorizationToken {
RKObjectManager* objectManager = [RKObjectManager sharedManager];
NSString* authHeader = [NSString stringWithFormat:#"Bearer %#", authorizationToken];
[objectManager.client setValue:authHeader forHTTPHeaderField:#"Authorization"];
}
In this code sample I am manually setting the HTTP header because RestKit's support for OAuth2 sets the header as Authorization: OAuth2 <accessToken> instead of Authorization: Bearer <accessToken>.
Anyway, this works great until the access token needs to be refreshed with the refresh token.
What I'd really like to do is tell RestKit to use GTMOAuth2Authentication's - (BOOL)authorizeRequest:(NSMutableURLRequest *)request; as it automatically fetches a new access token with the refresh token when the access token expires.
BTW, RestKit is phasing it's support for OAuth; authorizing requests with a third-party library is the suggested approach. I asked for an example and the response pointed me in the direction of classes to subclass, which are in the development branch.
So, the question is: Have you successfully integrated RestKit 0.10.x with GTMOAuth2 or know how to accomplish this?
I'm going to answer my own question here because I have something that works, but it's not ideal.
Basically I am continuing to sign requests manually, and to deal with the possibility of an expired token, I am using GTMOAuth to authorize a dummy request. The serious downside is that it means an extra request to the api. But, now I know I have a valid access token. Part of the trick is going to be knowing when to call loginWithBaseController:.
Here is the basic concept:
- (void)makeRequestToEnsureAccessTokenSuccess:(void (^)())block error:(void (^)(NSError*))errorBlock {
NSURL *url = [NSURL URLWithString:#"https://uri/to/a/basic/api/call.json"];
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:url];
// TODO: Only do this if we need to. It's kind of a waste to refresh the token
// all the time.
[self.gtmoauth authorizeRequest:request completionHandler:^(NSError *error) {
if (error) {
errorBlock(error);
} else {
[GTMOAuth2ViewControllerTouch saveParamsToKeychainForName:kKeychainItemName accessibility:NULL authentication:self.gtmoauth];
block();
}
}];
}
- (void)loginWithBaseController:(UIViewController *)baseViewController {
[self loadAuthFromKeyChain];
if (self.gtmoauth.canAuthorize) {
[self makeRequestToEnsureAccessTokenSuccess:^{
NSLog(#"setting RKAuthorizationToken with %#", self.gtmoauth.accessToken);
[self setRKAuthorizationToken: self.gtmoauth.accessToken];
[self.delegate authorizationFinished];
} error:^(NSError* error) {
[self logout];
[self showAuthorizationControllerFor:baseViewController];
}];
} else {
[self showAuthorizationControllerFor:baseViewController];
}
}
Anyway, I am still looking for a better solution. I thought I would post this in case it helps someone else at least through development.
This post on the RestKit Wiki, OAuth Support on Restkit references a complete solution using AFOAuth2Client.
The referenced sample iOS application at telegraphy-interactive/OAuthClientSetup has a pluggable architecture for authorized operations. It includes a class, OACSAuthOpRK that provides a RestKit RKObjectManager operation for making an authorized request.
The strategy used there is to wrap the request inside of the authorization. The authorization checks the currency of the OAuth2 authorization token. Finding the token current, it tries the request and checks the return status. Finding the token expired, or finding a 401 status return from the request, it will refresh the authorization token and try the request one more time.
I have made a simple SharePoint client App for iPhone, which require access to some SharePoint web services (mainly /_vti_bin/Lists.asmx). I am having a trouble figuring out how to do this on newer SharePoint environment such as Office365.
With old BPOS environment having forms-based authentication, I was able to authenticate to those services by simply implementing didReceiveAuthenticationChallenge method;
-(void)connection:(NSURLConnection *)connection didReceiveAuthenticationChallenge:(NSURLAuthenticationChallenge *)challenge {
NSURLCredential *newCredential = [NSURLCredential credentialWithUser:username
password:password
persistence:NSURLCredentialPersistenceForSession];
[[challenge sender] useCredential:newCredential
forAuthenticationChallenge:challenge];
}
This obviously didn't work any more with SharePoint sites having claims authentication, so I did some research and found out that I need FedAuth cookies to be attached to the request.
http://msdn.microsoft.com/en-us/library/hh147177.aspx
According to this article, with .NET Apps, it seems possible to retrieve those HTTPOnly FedAuth cookies using WININET.dll, but I guess that's not available on iPhone?
Then, I saw SharePlus App presenting UIWebView and getting user to login to their Office365 account first on the browser screen (which is the same concept as explained in "Enabling User Login for Remote Authentication" section of the article above).
So, I tried to see if I can somehow get access to those FedAuth cookies by logging into Office365 account via UIWebView, but [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] did not let me access HTTPOnly cookies.
Is there a way to achieve claims authentication on iPhone apps without needing designated intermediate .NET service for handling authentications, or requiring user to turn off HTTPOnly property on those cookies?
Sorry, I am very new to SharePoint so I may not even be looking at the right direction, but I would appreciate any advise on getting claims authentication to work on iPhone apps. Thanks in advance!
I've figured this out myself. Had to laugh at my own stupidity and impatience.
First of all, [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies] DO let you access HTTPOnly cookies. Though, when user logs into Office 365 on the UIWebView, (void)webViewDidFinishLoad:(UIWebView *)webView delegate method get called several times so you just need to wait until FedAuth appears in the cookies jar.
Here is my (void)webViewDidFinishLoad:(UIWebView *)webView implementation;
- (void)webViewDidFinishLoad:(UIWebView *)webView {
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookiesArray = [storage cookies];
for (NSHTTPCookie *cookie in cookiesArray) {
if ([[cookie name] isEqualToString:#"FedAuth"]) {
/*** DO WHATEVER YOU WANT WITH THE COOKIE ***/
break;
}
}
}
Once you have obtained the cookie, you just need to append that to the NSURLRequest using (void)setAllHTTPHeaderFields:(NSDictionary *)headerFields method when you call SharePoint web services.
Hope this helps someone.
Using GData, is there a built in way to store a session or credentials for interacting with the gdata api, or do I need to store credentials manually in the keychain? I'm using the YouTube upload API, and want to ensure the user doesn't have to enter username and pw each time. If there's a way to automatically get the user's Google login session, that's even better.
If you're using the GTM Oauth library (http://code.google.com/p/gtm-oauth/), they provide a method to store access token information in keychain by service name (e.g. "YouTubeAPI" or something).
Additionally, if you're using raw username / password, I would definitely store the details in keychain. Sci-Fi Hi-Fi has a nice, easy to use library that I've used in the past - http://github.com/ldandersen/scifihifi-iphone.
The GTM OAuth is newer, but the GData API's also support this via the setAuthorizer method. I didn't notice that until I dove into the source code.
//save to keychain
- (void)viewController:(GDataOAuthViewControllerTouch *)viewController
finishedWithAuth:(GDataOAuthAuthentication *)auth
error:(NSError *)error {
if (error != nil) {
// Authentication failed
} else {
[[self youTubeService] setAuthorizer:auth];
}
}
//check if authorized:
- (BOOL)isAuthorized
{
GDataOAuthAuthentication * auth = [GDataOAuthViewControllerTouch authForGoogleFromKeychainForName:kAppServiceName];
BOOL isSignedIn = [auth canAuthorize]; // returns NO if auth cannot authorize requests
if(isSignedIn) [[self youTubeService] setAuthorizer:auth];
return isSignedIn;
}
This issue is giving me serious headaches, I don't have a clue what's going on here. If you don't have any experience with the Windows Live network, I ask you to read this anyway, maybe it has nothing to do with it and am I overlooking something totally unrelated.
In short: I wrote an Objective-C class that allows me to connect to the Windows Live Messenger network, called WLNotificationSession. I works really straightforward, I set the username and password variables and do [notificationSession start];. It then logs in successfully.
Let's say I have two Windows Live accounts. The first one, A, is now logged in.
The problem arises when I try to fire up a second WLNotificationSession, with the other Windows Live account, B. It always fails. The usernames and passwords are 100% correct. When I try to log in B first, it succeeds. When I try A while B is logged in, it fails. The second login session always fails.
It can't be something like "too much log in attempts in a short period of time". When I log in A, quit the app, restart the app and log in A again, both attempts succeed. I can do this within 20 seconds. But, when I fire up the app, log A in, disconnect A, wait 2 hours, log in B (all without closing the app), it fails. (??)
For those of you with experience with the WL network: the failure occurs during the Tweener authentication. The part where you get the "Authentication-Info" or "WWW-Authenticate" HTTP header from the login server. When it fails, I get this value:
"Www-Authenticate" = "Passport1.4 da-status=failed-noretry,srealm=Passport.NET,ts=-2,prompt,cburl=http://messenger.msn.com/images/logo102x88.gif,cbtxt=.NET%20Messenger%20Service";
I really hope someone can help with this. Thank you.
UPDATE
This is some example code. I create a new project, add this code in the applicationDidFinishLaunching method and click Build & Run:
WLNotificationSession *notificationSession1 = [[WLNotificationSession alloc] init];
notificationSession1.username = #"testaccount1#hotmail.com";
notificationSession1.password = #"testpwd";
[notificationSession1 start];
WLNotificationSession *notificationSession2 = [[WLNotificationSession alloc] init];
notificationSession2.username = #"testaccount2#hotmail.com";
notificationSession2.password = #"testpwd";
[notificationSession2 start];
notificationSession1 always succeeds, notificationSession2 always fails. No global variables, or shared variables whatsoever.
UPDATE 2
Following David's suggestion the problem could be cookie-related, I added this code to my project:
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyNever];
I also use his method in the comments below to delete any cookies before and after each URL request. This is probably unnecessary, but I do it anyway.
I think at this point it is safe to assume it's not the cookies, or there has to be some other place where cookies are stored.
No global variables, or shared variables whatsoever
Then, as the authentication is performed using http request, this could be cookie issue. There might be some session cookie reminding the server about the former session.
I know that FBConnect (Facebook API for iPhone) uses the following method when logging out to remove any cookie :
- (void)deleteFacebookCookies {
NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray* facebookCookies = [cookies cookiesForURL:[NSURL URLWithString:#"http://login.facebook.com"]];
for (NSHTTPCookie* cookie in facebookCookies) {
[cookies deleteCookie:cookie];
}
}
You could try this (replace facebook url with yours). You could even add some NSLogs to watch for these cookies.
Can an iPhone application read cookies previously stored by Safari Mobile?
To actually answer your question:
No.
Mobile Safari's cookies are not accessible from SDK apps. And each SDK app is given its own WebKit cache and cookie stores, so while cookies will persist within the same app, they aren't accessible betweeen apps.
As of iOS 9 this is possible!
Use a sfSafariViewController.
You will need to setup:
A custom URL scheme in your app to receive cookie data.
The website you are getting cookies from will need to implement an API specific your app's custom URL scheme, to redirect back to your app.
You can clone this repo which has a fully working demo of this.
Hope this helps,
Liam
There is actually an interesting way if you have access to a server url.
In your app launch the server url with mobile safari.
The target server url reads the cookie and redirects back to an app specific url (myapp://cookie=123)
The app is then switched back and you can read that value from the url handler
It's a little hacky as the app would switch mobile safari and then immediately switch back to the app. But, it is possible.
Note that on iOS 8, you're probably better using Safari Password Sharing to solve some of the use cases that give rise to this problem.
This is not directly possible, but with the cooperation of the web site it is possible.
To clarify, the user case is that an Objective C application wants to read the value of a cookie that has been set by a website in mobile safari. (ie. in particular, a UIWebView was not involved in setting the cookie)
Your app should do this:
Launch mobile safari, using [[UIApplication sharedApplication] openURL:url];
The URL should be a special one, eg. http://yourwebsite.com/give-ios-app-the-cookie
On your website, when that url is launched, issue a redirect to your-app-url-scheme:cookievalue= (eg. angrybirds:cookievalue=hh4523523sapdfa )
when your app delegate receives - (BOOL)application:(UIApplication *)application openURL:(NSURL *)url sourceApplication:(NSString *)sourceApplication annotation:(id)annotation process the url to get the cookie value
Note that you should not do this automatically when the application starts - the user will see the transfer to Mobile Safari and back, which is not a good user experience and Apple will reject your app (Apple also consider this to be "uploading user's personal data to server without their prior consent"). It would be better to do it in response to the user, paying attention to the user experience - eg. wait for the user to hit a "login" button, then do it, and if the user is not logged into your website, http://yourwebsite.com/give-ios-app-the-cookie should show the user the login screen within safari. If the user is logged in you could briefly show a "Automatically logging you in..." screen for a second or two in Safari before redirecting the user back.
There's no way to get this to work with hotmail/gmail/etc of course - it needs to be your own website.
Credit goes to Unique Identifier for both mobile safari and in app in iOS for suggesting this kind of approach.
Because of sandboxing on the iPhone you don't have access to Safari's cookies. You can only access cookies created within your application - by an UIWebView for example.
Although you have asked the same question twice before, here's one approach not yet mentioned...
This may be a little convoluted, but you can do Greasemonkey-esque things with a UIWebView. Something like this:
Load your target page
craft some javascript which will read the document.cookie and return the data you need
In the webViewDidFinishLoad delegate, inject this javascript into the UIWebView with the stringByEvaluatingJavaScriptFromString message
I've used this technique to enhance 3rd party pages in an iPhone app, but I'm not sure if it will read cookies from the same place as Safari mobile.
Worth a shot though?
Here's my utils get/set cookie methods.
+(void)setCookie:(NSString *)key withValue:(NSString *)value {
NSArray *keys = [NSArray arrayWithObjects:
NSHTTPCookieDomain,
NSHTTPCookieExpires,
NSHTTPCookieName,
NSHTTPCookiePath,
NSHTTPCookieValue, nil];
NSArray *objects = [NSArray arrayWithObjects:
#"YOURDOMAIN",
[NSDate distantFuture],
key,
#"/",
value, nil];
NSDictionary *dict = [NSDictionary dictionaryWithObjects:objects forKeys:keys];
NSHTTPCookie *cookie = [NSHTTPCookie cookieWithProperties:dict];
NSHTTPCookieStorage *sharedHTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
[sharedHTTPCookieStorage setCookie:cookie];
}
+(NSString *)getCookie:(NSString *)key {
NSHTTPCookieStorage *sharedHTTPCookieStorage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray *cookies = [sharedHTTPCookieStorage cookiesForURL:[NSURL URLWithString:#"YOURDOMAIN"]];
NSEnumerator *enumerator = [cookies objectEnumerator];
NSHTTPCookie *cookie;
while (cookie = [enumerator nextObject])
{
if ([[cookie name] isEqualToString:key])
{
return [cookie value];
}
}
return nil;
}
You might want to check
if ([[NSHTTPCookieStorage sharedHTTPCookieStorage] cookieAcceptPolicy] != NSHTTPCookieAcceptPolicyAlways) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] setCookieAcceptPolicy:NSHTTPCookieAcceptPolicyAlways];
}
But apparently NSHTTPCookieStorage does not even hold cookies from the last request in the current application on iOS (rdar://8190706)