I am designing an app that it uses the web service like posting links and pictures. The first view controller checks the default username and password of the user and if it is correct, it allows the user to login to the home view controller but if its not correct it directs the user to the login view controller (if its the first time that the application is running it also directs the user to the login view controller).
I have different view controllers that is connecting to the home view controller. For example one of them is for posting pictures to the website, the other one is for posting links to the web and the other view controller is for changing the preferences of the users profile.
I am using RestKit API to POST on web and here is the code that I use for posting:
- (IBAction)addLinkPressed:(UIButton *)sender {
[RKClient clientWithBaseURLString:#"http://MyWebsite.com"];
NSDictionary* params = [NSDictionary dictionaryWithObjectsAndKeys:
self.linkField.text, #"url",
self.linkTitleField.text, #"title",
self.linkSummaryField.text, #"summary",
nil];
RKRequest *request = [[RKClient sharedClient] post:#"/send_link.php" params:params delegate:self];
[request setUserData:#"sendLink"];
}
For each view controller I put the following method in viewDidLoad in order to get authentication for posting :
- (void)autoLogin {
[RKClient clientWithBaseURLString:#"http://MyWebsite.com"];
[RKObjectManager sharedManager].client=[RKClient sharedClient];
RKParams *parameters = [RKParams params];
[parameters setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"defaultUsername"] forParam:#"username"];
[parameters setValue:[[NSUserDefaults standardUserDefaults] objectForKey:#"defaultPassword"] forParam:#"password"];
[[RKClient sharedClient] setAuthenticationType:RKRequestAuthenticationTypeHTTP];
RKRequest *request = [[RKClient sharedClient] post:#"/login.php" params:parameters delegate:self];
[request setUserData:#"login"];
}
Question: Is there any method to get authentication from the website for posting other than logging in for each view controller? the problem for logging in for each view controller is that some times logging process gives an error and does not allow the user to POST. I need a method that as soon as the user inputs the correct username and password, It remains logged in until the user logout by himself or delete the app.
I found this relative question but It was not that helpful or I did not understand the answers well. Any thoughts or ideas would be appreciated.
RestKit is designed to consume APIs. Usually APIs are stateless, meaning that there is no such thing as state between requests. On traditional websites state is achieved by transmitting cookies back and forth between client and server. Apparently RestKit does not handle cookies (which is good in my opinion). From the top of my head I can think of two solutions:
Apparently RestKit does not handle cookies automatically. Thus, you could get the cookie after the login and attach it to every subsequent request.
Better: use OAuth2, which is basically a token that the user receives when logging in. This token is then appended as a parameter to each request.
Both methods are basically the same. You have to send a token that identifies the user as logged in.
If I understand well your situation, you have many viewcontrollers that act separately and independently. Doing so you need as many logins as the number of viewcontrollers your app manages.
If this is your scenario, you can try to create a single class that does the login work and use your other viewcontrollers just to handle GUI.
This will be your architecture:
App other classes and viewcontrollers -> LOGINMANAGER -> viewcontroller 1 (pictures), viewcontroller 2 (links), viewcontroller 3 (preferences), viewcontroller 4 (whatever)...
Related
I have seen a lot of questions here regarding the Facebook Graph API but I still haven't find a solution for simple 'login'/'logout' operations using it. Looks like the Single Sign-On style is causing more confusion than benefits.
I'd like to know if it is possible have the following situation:
Enter in the app (no accessToken/expirationDate created).
Perform a login using SSO by calling authorize:delegate: method (application goes background and the login is made in the 'global' scope (Facebook App/Mobile Safari), asking for the user credentials.
Enter back in the app (now logged in, both accessToken and expirationDate are saved to NSUserDefaults).
Perform a logout by calling the logout: method (now logged out, both accessToken and expirationDate are removed from NSUserDefaults)
Attempt to perform a login again, with exactly the same steps done in 2.
I realize that when I call logout:, I do really log out from Facebook (accessToken is invalidated) from my App scope, not from the global scope (Facebook App/Mobile Safari). In 5.) when I try to log in again, the application goes to background and the login attempt is made again in Facebook App/Mobile Safari as usual, however I'm getting a screen saying that I'm already logged in:
You have already authorized .... Press "Okay" to continue.
Logged in as ... (Not You?).
It's a strange behavior for the user that has just logged out in my App.
My question is:
"Can I really log out from facebook (I mean 'global' scope) from inside my App? This would affect other apps using the facebook credentials too. However, if I can't to do this, how can I avoid the 'strange behavior' describe above?
Thanks
Eduardo,
I feel your pain! I spent the better part of a day working on this issue. I have discovered that when you use SSO and the call:
Called from your code:
[facebook logout:self];
Facebook API method:
- (void)logout:(id<FBSessionDelegate>)delegate {
self.sessionDelegate = delegate;
[_accessToken release];
_accessToken = nil;
[_expirationDate release];
_expirationDate = nil;
NSHTTPCookieStorage* cookies = [NSHTTPCookieStorage sharedHTTPCookieStorage];
NSArray* facebookCookies = [cookies cookiesForURL:[NSURL URLWithString:#"http://login.facebook.com"]];
for (NSHTTPCookie* cookie in facebookCookies) {
[cookies deleteCookie:cookie];
}
if ([self.sessionDelegate respondsToSelector:#selector(fbDidLogout)]) {
[_sessionDelegate fbDidLogout];
}
}
The facebook API does invalidate the access token and expirationdate variables and attempts to delete the mobile Safari cookies, but for some reason, probably Apple's fault the cookies are not really deleted. So when you attempt to login in the next time your mobile Safari will see the cookie and it says:
"You have already authorized .... Press "Okay" to continue. Logged in as ... (Not You?)."
Until either Facebook finds a fix or Apple fixes their broken API we must bypass SSO through Safari. Below are the changes I made to Facebook.m in order to force the old login dialog. If you used these changes they may not work forever but it is my guess that they will work for a very long time. Also to be sure this worked with the most recent facebook API I updated to the latest as of this post (Nov 2011 build).
Called from your code:
[facebook authorize:permissions];
Facebook API method:
- (void)authorize:(NSArray *)permissions {
self.permissions = permissions;
// [self authorizeWithFBAppAuth:YES safariAuth:YES];
[self authorizeWithFBAppAuth:NO safariAuth:NO];
}
If this helps you please up rate this thread and my post to help others find it.
gadildafissh
I'm afraid the answer is no, you can't do this.
Your application is in a sandbox, and can't write outside, where global cookies are (for mobile safari) and Facebook app settings (in Facebook app preferences/cookies I think)
You can only warn your user to logout outside of your app...
...Or you can just not use facebook api SSO, but in app login webform, like I do for other reasons.
If you choose that option this pull request might save you some time ;)
Hii ,
its not possible , the reason is for Single Sign On (SSO) is not to make user login everytime, he logouts , instead if the user logs in anyone of FB enabled apps - it will use that to login again - This is because the device is mostly used by single person in this case only one user can login in Facebook.
you can't control any app outside of your app - for Example - if u login with Gmail & when you open google.com you can see your username there is currently logged In which has SSO,
In new SDK of Facebook, you can set login button loginBehaviour property
Below code in swift ...
let fbButton = FBSDKLoginButton()
fbButton.loginBehavior = .Web
Answer already done, but I just want to clarify it. May be it saved somebody's time.
Go to Facebook.m and change line
[self authorizeWithFBAppAuth:YES safariAuth:YES];
to
[self authorizeWithFBAppAuth:YES safariAuth:NO];
It will cause login window appear inside the app. Logout will work perfect. In other words, it will work as it used to in older versions of OS.
in addition to kishnan94 answer. the objective c version is ;
if you want a modal to open up and ask for facebook credentials seperately from Safari or Facebook app, just use the latest facebook sdk and set the login behaviour
FBSDKLoginManager *login = [[FBSDKLoginManager alloc] init];
[login setLoginBehavior:FBSDKLoginBehaviorWeb];
this will make the logout process more convenient and less confusing for users without using safari or facebook app accounts.
It seems that this is a bug of Facebook SDK. In a case of the Facebook app is installed on device, access_token is renewed. In other hand, access_token and expirationDate could not be changed. :((
My question is how to check if my FaceBook app is already authorized for posts by the user, can't find any info on that.
I'm using:
Facebook* facebook = [[Facebook alloc] initWithAppId:#"1234567"];
[facebook authorize:[NSArray arrayWithObjects:#"read_stream", #"offline_access",nil] delegate:self];
A dialog pops up asking me to authorize the app, when done i'm all fine, can do a:
[facebook dialog:#"feed" andDelegate:self];
to post notes on that app.
But, if the user blocks or removes the app i want to do the authorize again before showing the dialog for posting, can't find a way of getting that kind of info before calling authorize.
Any help is appreciated.
Thanks.
I had to deal with this issue too.
When calling the dialog method, you send a delegate that should conform to FBDialogDelegate, which has a method that is called when the dialog fails to load due an error. But in the case the app has been unauthorized, the dialog shows a login screen to the user, but after setting the user and password, a second form appears, letting the user know that an error has occurred. The delegate is also called, but the error received just states that he method has failed with no exact reason why, or even an error number. This method should be called with the correct error, before anything, so the application could act accordingly.
So I found a work around, maybe this is not the best way, but it certainly works. Any call that you do to the Facebook graph api via a request, will fail if the app has been unauthorized by the user. So what I did was to check that before calling the feed dialog method.
Add the following line where you need to test if the app is still authorized:
if ([facebook isSessionValid])
//isSessionValid only checks if the access token is set, and the expiration date is still valid. Lets make a call and see if we really are authorized to post to this user from this app.
[facebook requestWithGraphPath:#"me" andDelegate:self];
else
//authorize Facebook connect
This will just call the method that returns the basic information from the user. If everything is fine, the following method will be called from the delegate:
- (void)request:(FBRequest *)request didLoad:(id)result
{
//Everything is ok. You can call the dialog method. It should work.
}
If the app has been unauthorized by the user, the following method from the delegate will be called:
- (void)request:(FBRequest *)request didFailWithError:(NSError *)error;
{
NSString *type = [[[error userInfo] objectForKey:#"error"] objectForKey:#"type"];
if (type)
{
if ([type isEqualToString:#"OAuthException"]) //aha!
{
//user has unauthorized the app, lets logout from Facebook connect. Also clear the access and expiration date tokens
[facebook logout:self];
//Call the authorize method again. Or let the user know they need to authorize the app again.
}
}
}
So, as I said before, not the best way, but gets the job done. Hopefully, Facebook will add a method to check for this specific scenario, or add a new method to the delegate that deals with the unauthorized app issue.
I'm not sure how exactly to do it with Facebook's SDK, but you can use FQL to query the permissions. The query URL would look something like
https://api.facebook.com/method/fql.query?query=SELECT+uid,+read_stream,+offline_access+FROM+permissions+WHERE+uid=me()&access_token=...
It looks like requestWithMethodName:andParams:andHttpMethod:andDelegate: passing fql.query as the method is the way to go, as long as you can arrange for isSessionValid to be true (or somehow supply access_token in the params yourself).
I want to authorize a user for my iphone app using Twitter. I would like to use sharekit to do this for simplicity but I am missing something fundamental. My understanding is that the way you do this is as follows:
service=[[SHKTwitter alloc] init];
[service authorize];
What is not clear to me is how I get notified once the authorization process is complete. When I execute this code, a modal view controller pops up and the user grants access via twitter. Authorization is successfully made and the modal view controller is dismissed. What I don't see is any way for me to be notified once all this is complete? What am I missing?
You could check two methods in SHKOAuthSharer (if you use OAuth to authorize):
1. - tokenAuthorizeView: didFinishWithSuccess:
2. - tokenAuthorizeCancelledView:
They where called when authorizing view is closing. So you could put your own delegate callbacks here.
Or check this https://github.com/ideashower/ShareKit/issues/187
Is it possible to use the Facebook iOS SDK to authenticate within an app (not go to Safari), and also keep those authentication credentials for the next launch of the app?
When I try to use the demo app in the simulator, it always goes to safari to authenticate, which seems a bit crappy. And then, when I authenticate... if I completely kill the app it will ask to authenticate again (and then tell me I am already logged in)
Is there a way, to just present the user with just an email field and a password field and then keep that info.. within the app?
Take a look at this question and answer: Iphone facebook connect example calls safari. I don't want to use safari. Also, you'll want to store the authentication stuff in NSUserDefaults and check for them to make to prevent re-logins.
EDIT Some sample code:
To save login stuff:
[[NSUserDefaults standardUserDefaults] setObject:_facebook.accessToken forKey:#"AccessToken"];
[[NSUserDefaults standardUserDefaults] setObject:_facebook.expirationDate forKey:#"ExpirationDate"];
[[NSUserDefaults standardUserDefaults] synchronize];
To check for login stuff:
_facebook = [[[Facebook alloc] initWithAppId:#"[app_id]"] retain];
_facebook.accessToken = [[NSUserDefaults standardUserDefaults] stringForKey:#"AccessToken"];
_facebook.expirationDate = (NSDate *) [[NSUserDefaults standardUserDefaults] objectForKey:#"ExpirationDate"];
if (![_facebook isSessionValid]) {
[_facebook authorize:_permissions delegate:self];
}
else {
[_facebook requestWithGraphPath:#"me" andDelegate:self];
}
You can hack round it to stop it if this is what you really want, bearing in mind most other apps that migrate from the older Facebook connect api to graph will behave in the new way
In facebook.m find the following method
- (void)authorizeWithFBAppAuth:(BOOL)tryFBAppAuth
safariAuth:(BOOL)trySafariAuth
find the bottom of the didOpenOtherApp logic and comment out all above it so that it always opens inline and tuns this section of code thats contained in the !didOpenOtherApp braces
// If single sign-on failed, open an inline login dialog. This will require the user to
// enter his or her credentials.
if (!didOpenOtherApp) {
[_loginDialog release];
_loginDialog = [[FBLoginDialog alloc] initWithURL:loginDialogURL
loginParams:params
delegate:self];
[_loginDialog show];
}
However by doing this you are making it more likely that the user will have to input their credentials, which is surely worse than putting up with the fast app switching approach?
When you first auth, make sure you're asking for "offline_access" permission. That will make the token that OAuth returns to you NOT be invalidated at the end of the session, but instead stay valid literally until they come along and use the API to log your app OUT of Facebook.
Then, obviously, you need to save the token (I feel like NSUserDefaults is a fine place for it) and reuse it on later FB interactions.
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)