Handling multiple Facebook user logins in an iOS app - ios

I have an iOS application that can have multiple users and I want to integrate Facebook into it. I would like to cache each user's login credentials. The users will have an application login so each user will only be able to use there own Facebook creds. Facebook's documentation states that you can manage multiple user's session tokens by overriding the FBSessionTokenCachingStrategy object here:
https://developers.facebook.com/docs/ios/login-tutorial/
I am using the local caching method and I have it working where a user logs into my application and then if they have a related Facebook token I log them into there session. Otherwise I prompt them to enter there Facebook credentials using Facebook's API that redirects the user to the Facebook web login page.
[FBSession openActiveSessionWithReadPermissions:#[#"basic_info"]
allowLoginUI:YES
completionHandler:
^(FBSession *session, FBSessionState state, NSError *error)
{
[self loginToFacebookEnd:session state:state error:error];
}];
The above method works great for the FIRST USER. The second user that goes to login gets redirected to the Facebook web login page as expected, but the first user is now logged in through safari and the second user is now logged in with the first users credentials.
I want to be able to either not have the user stay logged in on safari, OR be able to use something like the embedded web dialog. According to the Facebook API docs however, the embedded dialog however doesn't seem to let me load the users token after the app restarts:
https://developers.facebook.com/docs/ios/login-tutorial/#control
"Embedded WebView Login Dialog Disadvantage: People have to fill in
their login credentials every time they go through the login flow."
Also, calling the following method clears out my token cache when the token was saved from an Embedded WebView:
FBSession *session = [[FBSession alloc] initWithAppID:nil
permissions:#[#"basic_info"]
urlSchemeSuffix:nil
tokenCacheStrategy:tokenCaching];
Does anyone know how to support more than one user logging into Facebook from an iOS app without the first user's credentials getting cached in Safari?
UPDATE 1
Ok, I know it is possible to handle multiple users using the Embedded WebView now, because one of the sample programs Facebook includes with the SDK (SwitchUserSample) does it. Something seems to be wrong with loading my FBSession, as a lot of the values are null, though I am calling exactly the same code as the example. Does anyone know what would cause a session to restore like this:
<FBSession: 0x16d9f3a0, state: FBSessionStateCreated, loginHandler: 0x0, appID: 658013034244859, urlSchemeSuffix: , tokenCachingStrategy:<FBSessionTokenCachingStrategy: 0x16d56190>, expirationDate: (null), refreshDate: (null), attemptedRefreshDate: 0001-12-30 00:00:00 +0000, permissions:(null)>
If I use any behavior other than FBSessionLoginBehaviorForcingWebView, then I get this as my deserialized token:
<FBSession: 0x155823d0, state: FBSessionStateOpen, loginHandler: 0x73364, appID: 658013034244859, urlSchemeSuffix: , tokenCachingStrategy:<FBSessionTokenCachingStrategy: 0x15577c90>, expirationDate: 2014-02-15 20:01:50 +0000, refreshDate: 2013-12-17 20:01:51 +0000, attemptedRefreshDate: 0001-12-30 00:00:00 +0000, permissions:(
"user_birthday",
"user_friends"
)>
UPDATE 2
I have identified that it is definitely something to do with how my token is getting cached. I have copied over the methods the Facebook example is using but I am still getting null values for my dates in the FBSession. Here are the caching methods I am calling:
+ (FBSessionTokenCachingStrategy*)createCachingStrategyForSlot:(int)slot {
FBSessionTokenCachingStrategy *tokenCachingStrategy = [[FBSessionTokenCachingStrategy alloc]
initWithUserDefaultTokenInformationKeyName:[NSString stringWithFormat:#"SUUserTokenInfo%d", slot]];
return tokenCachingStrategy;
}
+ (FBSession*)createSessionForSlot:(int)slot {
FBSessionTokenCachingStrategy *tokenCachingStrategy = [self createCachingStrategyForSlot:slot];
FBSession *session = [[FBSession alloc] initWithAppID:nil
permissions:#[#"basic_info",#"user_birthday"]
urlSchemeSuffix:nil
tokenCacheStrategy:tokenCachingStrategy];
return session;
}
And here is how I am calling it:
+ (BOOL)openSessionWithAllowLoginUI:(BOOL)allowLoginUI
{
BOOL openSessionResult = NO;
FBSession *session = [self createSessionForSlot:1];
// If showing the login UI, or if a cached token is available, then open the session.
if (allowLoginUI || session.state == FBSessionStateCreatedTokenLoaded)
{
// For debugging purposes log if cached token was found
if (session.state == FBSessionStateCreatedTokenLoaded)
{
NSLog(#"Cached token found.");
}
// Set the active session
[FBSession setActiveSession:session];
// If the session state is not any of the two "open" states when the button is clicked
if (FBSession.activeSession.state != FBSessionStateOpen && FBSession.activeSession.state != FBSessionStateOpenTokenExtended)
{
// Open the session.
[session openWithBehavior:FBSessionLoginBehaviorForcingWebView
completionHandler:^(FBSession *session,
FBSessionState state,
NSError *error)
{
[SocialInteraction loginToFacebookEnd:session state:state error:error];
}];
}
// Return the result - will be set to open immediately from the session
// open call if a cached token was previously found.
openSessionResult = session.isOpen;
}
return openSessionResult;
}
The frustrating part is that if I change the login behavior from FBSessionLoginBehaviorForcingWebView to FBSessionLoginBehaviorWithFallbackToWebView, then the token loads correctly! But, this behavior won't work for my application. There must be some other setting I am missing that would let the embedded WebView cache its token.

I'm moving this from comment to answer so I can type more:
If the state of your FBSession instance is FBSessionStateCreated then you wouldn't have the expirationDate properties etc. From my experience it has to be effectively open (FBSessionStateCreatedTokenLoaded, FBSessionStateOpen, FBSessionStateOpenTokenExtended). Does your FBSession object have an accessToken associated with it? If it does, then go ahead and try to open the session and see if the status changes and you get values where it used to be NULL.
To open the FBSession when I have a valid accessToken I usually use:
[FBSession openActiveSessionWithAllowLoginUI:NO];
UPDATE 1:
Ok so this is kind of a workaround to the web view - if you use Safari, you can clear the user's cookies using the code below. I would probably search the cookie's domain for Facebook.com or fb.com (probably requires some testing as to what the FB cookie domain is):
NSHTTPCookie *cookie;
NSHTTPCookieStorage *storage = [NSHTTPCookieStorage sharedHTTPCookieStorage];
for (cookie in [storage cookies]) {
[storage deleteCookie:cookie];
}
[[NSUserDefaults standardUserDefaults] synchronize];

Related

Upload Youtube video with "fixed" token in Google APIs Client Library for Objective-C for REST

I'm trying to create an app that upload video on a specified channel, without prompt a login page. I'll try to explain better what i need.
I'm using Google APIs Client Library for Objective-C for REST, with this library i can use a "standard" upload flow :
user record a video -> he press an upload button -> Safari open the login google page -> user login in his own account and give permission to the app -> Safari redirect back to the ios app -> the upload process begin -> the video will be uploaded on the personal user channel.
Instead this is the desired workflow of my ios app:
user record a video -> he press an upload button -> the video will be uploaded in the app's youtube channel.
The only help i had find is this article ,it explain a way to obtain a refreshable app token to upload video in an app channel. It is exactly what i need. Anyway this is a web example, it uploads videos that are in a server. My videos are in the phone, so i think that i have to modify the flow of this article in this way:
obtain token the first time by login as channel owner -> create a token.txt and save it in my server -> create a page called get_token.php that print the content of token.txt and refresh it if the token expire.
With this flow in my app i need this other flow:
user record a video -> press an upload button -> i made a call to get_token.php and retrive the actual token -> i made a call by library with the token retrived to upload the video on the app's youtube channel.
Here i have found some problems, this is my authentication methods :
#pragma mark - Sign In
- (void)authNoCodeExchange {
[self verifyConfig];
NSURL *issuer = [NSURL URLWithString:kIssuer];
[self logMessage:#"Fetching configuration for issuer: %#", issuer];
// discovers endpoints
[OIDAuthorizationService discoverServiceConfigurationForIssuer:issuer
completion:^(OIDServiceConfiguration *_Nullable configuration, NSError *_Nullable error) {
if (!configuration) {
[self logMessage:#"Error retrieving discovery document: %#", [error localizedDescription]];
return;
}
[self logMessage:#"Got configuration: %#", configuration];
if (!kClientID) {
[self doClientRegistration:configuration
callback:^(OIDServiceConfiguration *configuration,
OIDRegistrationResponse *registrationResponse) {
[self doAuthWithoutCodeExchange:configuration
clientID:registrationResponse.clientID
clientSecret:registrationResponse.clientSecret];
}];
} else {
[self doAuthWithoutCodeExchangeCri:configuration clientID:kClientID clientSecret:nil];
}
}];
}
/////////////////
- (void)doAuthWithoutCodeExchangeCri:(OIDServiceConfiguration *)configuration
clientID:(NSString *)clientID
clientSecret:(NSString *)clientSecret {
NSURL *redirectURI = [NSURL URLWithString:kRedirectURI];
OIDTokenRequest *tokenExchangeRequest =
[_authState.lastAuthorizationResponse tokenExchangeRequest];
[OIDAuthorizationService performTokenRequest:tokenExchangeRequest
callback:^(OIDTokenResponse *_Nullable tokenResponse,
NSError *_Nullable error) {
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
}
[_authState updateWithTokenResponse:tokenResponse error:error];
GTMAppAuthFetcherAuthorization *gtmAuthorization =
[[GTMAppAuthFetcherAuthorization alloc] initWithAuthState:authState];
// Sets the authorizer on the GTLRYouTubeService object so API calls will be authenticated.
self.youTubeService.authorizer = gtmAuthorization;
// Serializes authorization to keychain in GTMAppAuth format.
[GTMAppAuthFetcherAuthorization saveAuthorization:gtmAuthorization
toKeychainForName:kGTMAppAuthKeychainItemName];
[self uploadVideoFile];
}];
}
i have also something like:
NSString * RetrivedToken = #"ya29xxxxxxx3nJxxxxxxx6qqQ-FxxxxxxxdGH";
How i can modify those methods to accept my retrivedtoken instead the one they retrive from the standard auth workflow?
This isn't really an answer but its going to be to big for a comment.
So you want to create an app that will allow others to upload to your youtube channel. Normally i would say use a service account which would allow you to do this much easier. However the YouTube api does not support service accounts.
You are going to need to authenticate the app once save your refresh token then embed this refresh token in your app.
Why this is not a good idea with a mobile app.
Refresh tokens can expire so if it does expire or break then your going to have to authenticate the app again and embed the new refresh token into your app and release a new version to your users.
An alternative would be to set up a web service with the refresh token on that and have your app access the web service for upload. Then if the refresh token breaks you will only have to fix it on your web service.
I have done this before its messy but there is really no other way of doing it.
You can see from your code
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
}
your are using OIDTokenResponse object tokenResponse in your further code.
First make an object of Class OIDTokenResponse like *oidTokenResponse and assign it your access token from your server like
oidTokenResponse.accessToken=YOUR_TOKEN_FROM_SERVER;
and then use
[_authState updateWithTokenResponse:oidTokenResponse error:error];
In other way you can also do that and you won't need to change your code much you can do it with one line of code but i'm not sure you are allow to change to access token here but you can try it.
Leave the other code and just add one more line in your else
if (!tokenResponse) {
[self logMessage:#"Token exchange error: %#", [error localizedDescription]];
} else {
[self logMessage:#"Received token response with accessToken: %#", tokenResponse.accessToken];
tokenResponse.accessToken=YOUR_TOKEN_FROM_SERVER;
}

Firebase automatically signs out from the user session on iOS app

I am struggling with Firebase authentication in my app. I have enabled the database persistence using:
[FIRDatabase database].persistenceEnabled = YES;
After the user logs in (email/password authentication), I am expecting the session to remain active until I explicitly signout:. To handle the changes in authorization, I set up the following (with some debugging code to check out the behavior based on the application state changes):
[[FIRAuth auth] addAuthStateDidChangeListener:^(FIRAuth * _Nonnull auth, FIRUser * _Nullable user) {
if (user != nil) {
ZLog(YES, #"Signed in %#\n\n\n>>>>\n\n\n", user.uid);
[user getTokenWithCompletion:^(NSString * _Nullable token, NSError * _Nullable error) {
ZLog(NO, #"Token %#\n\n\n####\n\n\n", token);
}];
} else {
ZLog(YES, #"Not Signed In\n\n\n>>>>\n\n\n");
FIRUser *user = [[FIRAuth auth] currentUser];
ZLog(YES, #"The user is %#", (user != nil) ? user : #"nil");
}
}];
everything seems to be ok even after reinstalling the app from Xcode while debugging. However as soon as the app goes in "Inactive state" (e.g. by pressing the Home button). I get the "Sign out" in my listener block. This does not happen if I just switch to another app when my app goes to "background" as observed by applicationDidEnterBackground.
What could be wrong? The documentation (https://firebase.google.com/docs/database/ios/offline-capabilities) suggests that the session should be saved across app restarts (which implies the cases like applicationWillResignActive and applicationDidBecomeActive, I assume).
I would not like to re-authenticate (with the safely saved email/password credentials) on every such change (and it would also defeat the 'offline' capabilities claim). Or would the re-authentication also work in 'offline' mode?
Going crazy trying to solve this - any clues would be helpful! Thx.

How do I store & access a Twitter Fabric login session (iOS/Swift)?

I'm able to log in to Twitter through my app using this Twitter Fabric code:
let logInButton = TWTRLogInButton(logInCompletion: {
(session: TWTRSession!, error: NSError!) in
// play with Twitter session
if (session != nil) {
println("signed in as \(session.userName)");
self.TWUsernameLabel.text = "Logged in as #" + session.userName
} else {
println("error: \(error.localizedDescription)");
}
})
When I click the login button, it prompts me to approve the login and then logs me in, or it knows I already approved the login and it logs me in. This works like a charm and took all of ten minutes to set up. Amazing.
I already have an email-based login to access the app. I'd like to store a user's logged in Twitter account in that same database, so when a user logs in with their email, I already know their Twitter (if they have logged in before) and they don't need to log in again. The reason I do the email login is because Twitter is an important feature in my app, but not a total requirement.
The issue is that I have no idea how to access session outside of when the button is clicked and logInCompletion fires, and I don't know what variables to store upon initial login/check upon using the app.
I've read through the Twitter Fabric documentation numerous times, but it isn't written in swift, so it's pretty confusing. Any ideas? Thanks
If there is currently an active session you should be able to access it just like the docs say to
Twitter.sharedInstance().session()
If the user isn't logged in that method will return nil. If you want to know if someone is already authenticated just check to see if that method returns a value or not.
Twitter kit automatically present a login screen if user has not logged in (no session exist..).
But, before calling any future API, just check whether session is valid or not. In some APIs, you may required to pass this session too.
if ([[Twitter sharedInstance] session]) {
TWTRShareEmailViewController *shareEmailViewController =
[[TWTRShareEmailViewController alloc]
initWithCompletion:^(NSString *email, NSError *error) {
NSLog(#"Email %# | Error: %#", email, error);
}];
[self presentViewController:shareEmailViewController
animated:YES
completion:nil];
} else {
// Handle user not signed in (e.g. attempt to log in or show an alert)
}
}
You even don't need to worry about storing and managing access token and access token secret.
To access the authToken, userName or the userID for the logged in user session in Swift 4 is:
Twitter.sharedInstance().logIn(completion: { (session, error) in
if (session != nil) {
print(session!.authToken)
print(session!.userName)
print(session!.userID)
} else {
print(error?.localizedDescription)
}
})

How to detect if a Facebook access token has been refreshed?

in my app I need to detect if an access token has been refreshed so that i can update my API with the new token.
I am having trouble trying to figure out the best way to approach this since the Facebook SDK automatically caches the access token - currently I am comparing the old token (which i save to NSUserDefaults) with the accessToken of the activeSession's accessTokenData.
If the tokens are different I send update my api with the newer token.
here is my code within applicationWillEnterForeground
NSString *newToken = [FBSession activeSession].accessTokenData.accessToken;
NSString *oldToken = [[NSUserDefaults standardUserDefaults]valueForKey:#"accessToken"];
if (oldToken) {
if ([newToken isEqualToString:oldToken]) {
NSLog(#"token was not refreshed");
} else {
NSLog(#"token was refreshed");
[[NSUserDefaults standardUserDefaults]setValue:newToken forKey:#"accessToken"];
/* update API with new token*/
}
} else+
{
//if no token exists, set one with the current token
[[NSUserDefaults standardUserDefaults]setValue:newToken forKey:#"accessToken"];
}
I have to admit it's really hacky, but I cant seem to find a way around it. Is there any better way of approaching it?

After signing into Instagram in my iOS app how can I sign in as a different user?

So I am building an iOS with instagram integration. I registered my app with instagram and have everything working so far. When the user clicks the ADD ACCOUNT button, I load the instagram auth url (https://instagram.com/oauth/authorize/?client_id=CLIENT-ID&redirect_uri=REDIRECT-URI&response_type=token) into a webview and the instagram login page appears.
I can then successfully login, my redirect then redirects correctly, and I can then exchange the code for an access token, close the webview, and display an alert saying that login was successful. But when the user clicks the ADD ACCOUNT button again and the webview loads the auth url, instead of seeing the instagram login page, it automatically authorizes and the successful login alert appears.
Is the webview caching something? How would I prevent this?
try clear caches and cookies
clear caches:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
to remove cookies check those two questions:
How to delete all cookies of UIWebView?
Delete cookies UIWebView
In Swift :
class func clearInstagramCookies() {
let cookieStore = NSHTTPCookieStorage.sharedHTTPCookieStorage()
guard let cookies = cookieStore.cookies where cookies.count > 0 else { return }
for cookie in cookies {
if cookie.domain == "www.instagram.com" || cookie.domain == "api.instagram.com" {
cookieStore.deleteCookie(cookie)
}
}
}
You must clear cookies of Instagram if you want to log the user out or if you need the sign-in page to appear again in UIWebView.
EDIT : Swift3
let cookieStore = HTTPCookieStorage.shared
guard let cookies = cookieStore.cookies, cookies.count > 0 else { return }
for cookie in cookies {
if cookie.domain == "www.instagram.com" || cookie.domain == "api.instagram.com" {
cookieStore.deleteCookie(cookie)
}
}
You can clear session and cookies by:
[[NSURLCache sharedURLCache] removeAllCachedResponses];
for(NSHTTPCookie *cookie in [[NSHTTPCookieStorage sharedHTTPCookieStorage] cookies]) {
[[NSHTTPCookieStorage sharedHTTPCookieStorage] deleteCookie:cookie];
}
it took me sometime to figure this out too.
I am working on an web app, so i got no option of clearing the cache. It seems however got very little to do with caching or cookies. Once your Instagram app is authorised by the particular user, pointing to that Auth URL will just automatically bring you back to your redirect URL.
Your user will have to manual revoke your apps rights at https://instagram.com/accounts/manage_access to be able to view the login screen again.

Resources