I am using new FBSDK. Able to get access token generated by FBSDK using
[[FBSDKAccessToken currentAccessToken]tokenString]
How can I use this to share or post without prompting user to login again?
Scenario : User Logs in via Facebook, access token gets generated, we store this access token in defaults. User shares something for this session. For future sessions when user wants to share something we use access token stored inside defaults for posting.
Please correct me if I am not getting function of access tokens...
check Permission like this...don't store access token...following code use you don't need everytime login...
if ([[FBSDKAccessToken currentAccessToken]hasGranted:#"email"])
{
// add your coding here after login call this block
}
else
{
//login code
FBSDKLoginManager *loginManager = [[FBSDKLoginManager alloc] init];
[loginManager logInWithReadPermissions:#[#"public_profile", #"email"] handler:^(FBSDKLoginManagerLoginResult *result, NSError *error)
}];
}
You can use this method:
- (void)continueSystemLogInWithTokenString:(NSString *)oauthToken error:(NSError *)accountStoreError state:(FBSDKLoginManagerSystemAccountState *)state;
Where oauthToken is your accessToken String
Once you have app token, you can do this:
if (oauthToken) {
completer = [[FBSDKLoginSystemAccountCompleter alloc] initWithTokenString:oauthToken appID:[FBSDKSettings appID]];
}
Note: All these methods are defined in FBSDKLoginCompletion class.
Call Heirarchy:
This method:
initWithTokenString in FBSDKLoginCompletion.m is called in continueSystemLogInWithTokenString in FBSDKLoginManager.m
and continueSystemLogInWithTokenString is called in performSystemLogIn method in FBSDKLoginManager.
performSystemLogIn is called from beginSystemLogIn in FBSDKLoginManager.
beginSystemLogIn is called from logInWithBehavior in FBSDKLoginManager.
logInWithBehavior is called from following places:
logInWithPermissions in FBSDKLoginManager
fallbackToNativeBehavior in FBSDKLoginManager
attemptBrowserLogIn in FBSDKLoginCompletion
completeLogIn in FBSDKLoginCompletion
Related
I'm trying to add Firebase Phone Auth to an app that I'm making in XCode. However, I'm having trouble with steps 3 of the firebase documentation and everything after that.
I don't understand where my code is supposed to go. I try some of it already and I attached the image of what I have done so far. Please help.
Thank you.
Ok, the code seems right.
Now you must add another textfield where the user can add the verification code arrived from the SMS.
In a new method triggered by the user after adding the code you must set a FIRAuthCredential like in the code of the example:
FIRAuthCredential *credential = [[FIRPhoneAuthProvider provider]
credentialWithVerificationID:verificationID
verificationCode:newTextField.text!];
And then do the signin with:
[[FIRAuth auth] signInAndRetrieveDataWithCredential:credential
completion:^(FIRAuthDataResult * _Nullable authResult,
NSError * _Nullable error) {
if (error) {
// ...
return;
}
// User successfully signed in. Get user data from the FIRUser object
if (authResult == nil) { return; }
FIRUser *user = authResult.user;
// ...
}];
I'm implementing an Authenticate with Firebase using Password-Based Accounts on iOS. After sign-in a user we can get particular information like user.email, user.uid, user.photoURL, user.displayName. However I can set email and password as follows.
[[FIRAuth auth]
createUserWithEmail:username
password:password
completion:^(FIRUser *_Nullable user,
NSError *_Nullable error) {
}];
In here no problem to get user.email. But I didn't set other information here. How to set other information such as user.photoURL, user.displayName with above createUserWithEmail method.
You need to call FIRUserProfileChangeRequest after the user authentication to update the profile info.
FIRUserProfileChangeRequest *changeRequest =
[[FIRAuth auth].currentUser profileChangeRequest];
changeRequest.displayName = userInput;
[changeRequest commitChangesWithCompletion:^(NSError *_Nullable error) {
// ...
}];
Read more on https://firebase.google.com/docs/auth/ios/manage-users
You can check my answer here.
It can help You. But its on Swift language.
Main tip that You need to store Users in database too. And have a link to image in it.
Hope it helps
Overall Problem:
I have a problem using a developer authenticated identity with my front end (iOS). I know my backend produces the correct token and identityID but my refresh method never gets called. I've also looked at the sample but I get slightly confused with everything going on.
Flow Explanation:
Currently I have a login screen that has a login button. The user presses the login button, then my api class takes the credentials, encrypts the password and stores it in keychain (commented out for now since it doesn't work on simulator). My DeveloperAuthenticatedIdentityProvider is called my app BusytimeAuthenticated. I have completed all the methods (I'm using AWS lambda and DynamoDB to authenticate users so) I start with unauthenticated access which allows me to access only two methods, login and signup. Then I want to assume my authenticated user which allows me to call my other methods.
my API Code:
[AWSLogger defaultLogger].logLevel = AWSLogLevelVerbose;
id<AWSCognitoIdentityProvider> identityProvider = [[BusytimeAuthenticated alloc] initWithRegionType:AWSRegionUSEast1
identityId:nil
identityPoolId:#"SOMEIDENTITYPOOLID"
logins:#{#"SOMEPROVIDERNAME": #"SOMEUSERNAME"}
providerName:#"SOMEPROVIDERNAME"
];
credentialsProvider = [[AWSCognitoCredentialsProvider alloc] initWithRegionType:AWSRegionUSEast1
identityProvider:identityProvider
unauthRoleArn:nil
authRoleArn:nil];
configuration = [[AWSServiceConfiguration alloc] initWithRegion:AWSRegionUSEast1
credentialsProvider:self.credentialsProvider];
AWSServiceManager.defaultServiceManager.defaultServiceConfiguration = configuration;
[[credentialsProvider refresh] continueWithBlock:^id(BFTask *task){
[self testAuth];
return nil;
}];
my DeveloperAuthenticatedIdentityProvider code (BusytimeAuthenticated) :
#import "BusytimeAuthenticated.h"
#interface BusytimeAuthenticated()
#property (strong, atomic) NSString *providerName;
#property (strong, atomic) NSString *token;
#end
#implementation BusytimeAuthenticated
#synthesize providerName=_providerName;
#synthesize token=_token;
- (instancetype)initWithRegionType:(AWSRegionType)regionType
identityId:(NSString *)identityId
identityPoolId:(NSString *)identityPoolId
logins:(NSDictionary *)logins
providerName:(NSString *)providerName{
if (self = [super initWithRegionType:regionType identityId:identityId accountId:nil identityPoolId:identityPoolId logins:logins]) {
self.providerName = providerName;
}
return self;
}
// Return the developer provider name which you choose while setting up the
// identity pool in the Amazon Cognito Console
- (BOOL)authenticatedWithProvider {
return [self.logins objectForKey:self.providerName] != nil;
}
// If the app has a valid identityId return it, otherwise get a valid
// identityId from your backend.
- (BFTask *)getIdentityId {
// already cached the identity id, return it
if (self.identityId) {
return [BFTask taskWithResult:nil];
}
// not authenticated with our developer provider
else if (![self authenticatedWithProvider]) {
return [super getIdentityId];
}
// authenticated with our developer provider, use refresh logic to get id/token pair
else {
return [[BFTask taskWithResult:nil] continueWithBlock:^id(BFTask *task) {
if (!self.identityId) {
return [self refresh];
}
return [BFTask taskWithResult:self.identityId];
}];
}
}
// Use the refresh method to communicate with your backend to get an
// identityId and token.
- (BFTask *)refresh {
if (![self authenticatedWithProvider]) {
return [super getIdentityId];
}else{
// KeychainWrapper *keychain = [[KeychainWrapper alloc]init];
AWSLambdaInvoker *lambdaInvoker = [AWSLambdaInvoker defaultLambdaInvoker];
NSDictionary *parameters = #{#"username" : #"SOMEUSERNAME",
#"password":#"SOMEENCRYPTEDPASS",
#"isError" : #NO};
NSLog(#"Here");
[[lambdaInvoker invokeFunction:#"login" JSONObject:parameters] continueWithBlock:^id(BFTask* task) {
if (task.error) {
NSLog(#"Error: %#", task.error);
}
if (task.exception) {
NSLog(#"Exception: %#", task.exception);
}
if (task.result) {
self.identityId = [task.result objectForKey:#"IdentityId" ];
self.token = [task.result objectForKey:#"Token" ];
// [keychain mySetObject:[task.result objectForKey:#"Token" ] forKey:#"Token"];
// [keychain mySetObject:[task.result objectForKey:#"IdentityId" ] forKey:#"IdentityId"];
NSLog(#"Result: %#", task.result);
}
return [BFTask taskWithResult:self.identityId];
}];
}
return NULL;
}
#end
Summary Problem:
Unfortunately when I test my new priveleges, I see from the error: "Unauth_Role/CognitoIdentityCredentials is not authorized to perform: lambda:InvokeFunction". Clearly I'm not switching properly. I've placed a breakpoint in my refresh method to see if it's getting called. It's not. I'm not quite understanding how I switch properly. Any help with getting this to work is much appreciated.
Note: One big change I did make though is I took out the "DeveloperAuthenticationClient" class because I assumed I could do it without it.
The fundamental problem is that you are trying to call a Lambda function (which requires credentials) to get credentials. Because you are using the "default" client configuration, when your developer authenticated client comes back with a response it is going to override the credentials used to access your Lambda function. Additionally, once that id has been transitioned to authenticated, you won't be able to use it to get credentials in an unauth flow and would need to generate a new unauthenticated id just to authenticate again and then get back to your authenticated id.
I would strongly encourage you to setup API Gateway in front of your Lambda function to remove this circular dependency.
Update based on new information in the question...
A few things here:
1. Avoid code like while(!finished) to wait on an async task to complete. In the best case, this style of busy waiting will consume a CPU/core at 100% while doing nothing useful and adversely affect battery life and will only hurt performance of your app. Instead, use a notification with a block. Since you have already have a AWSTask in this instance, instead of returning nil at the end of the [credentialsProvider refresh] continueWithBlock... just call your [self testAuth] right there and do away with the finished/while code.
2. In your getIdentityId implementation the first if condition checks if there is an identityId and if there is it returns nil. I'm guessing you goal here is to cache the identityId after a successful authentication and return that so that you don't have to call your backend every time getIdentityId is called. If that is the case, pretty sure you want to return identityId instead of nil
3. I don't think this is the cause of your issue but will simplify things: As long as you've configured your identity pool with Auth/UnAuth roles in the console, you don't have to explicitly use them when initializing the AWSCognitoCredentialsProvider.
Once these are resolved if you continue to have problems, please debug the code in more detail and tell us things like the following:
Does the refresh method get called? If so, which parts of your if statement does it enter and what is the result? Does it ever enter the else block and call your backend identity provider? Does it successfully retrieve an identity id and return it?
If you get further but start experiencing a slightly different issue then please mark this question answered and post a separate question instead of continuing to edit this question. This will help keep things clear (this question/answer is getting pretty long and has changed).
Original answer to initial posted question/code... The getIdentity method of the AWSCognitoCredentialsProvider returns an AWSTask (i.e. a BFTask). So you'll need to call something like continueWithBlock in order to actually execute the method. In the first block of code above it looks like you're not doing that.
I have an iPad application which allows users to login to their Gmail account(s) using OAuth2. Thus far, the login process and email fetching is successful. However, when the app is closed and then re-opened after a (long) period of time, an error is produced "invalid credentials,' even though previous logins with the same credentials were successful.
Login Flow:
1) User logs in to gmail using OAuth 2.
2) User email address and oAuthToken provided by the GTMOAuth2Authentication object are saved to core data for future logins.
3) IMAP Session is created using saved email address and OAuthToken.
Here is the relevant code
Google Login
- (void)gmailOAuthLogin
{
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
GTMOAuth2ViewControllerTouch *googleSignInController =
[[GTMOAuth2ViewControllerTouch alloc] initWithScope:GmailScope clientID:GmailAppClientID clientSecret:GmailClientSecret keychainItemName:KeychainItemName completionHandler:^(GTMOAuth2ViewControllerTouch *googleSignInController, GTMOAuth2Authentication *auth, NSError *error){
if (error != nil) {
//handle error
} else {
[[ModelManager sharedInstance] authenticateWithEmailAddress:[auth userEmail]
oAuthToken:[auth accessToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session using above arguments
} failure:^(NSError *error) {
//handle error
}];
}
}];
[self presentGoogleSignInController:googleSignInController];
}
Create IMAP Session Using MailCore2
- (void)authenticateWithEmailAddress:(NSString *)emailAddress password:(NSString *)password oAuthToken:(NSString *)oAuthToken imapHostname:(NSString *)imapHostname imapPort:(NSInteger)imapPort smtpHostname:(NSString *)smtpHostname smtpPort:(NSInteger)smtpPort success:(void (^)())success failure:(void (^)(NSError *))failure
{
self.imapSession = [[MCOIMAPSession alloc] init];
self.imapSession.hostname = imapHostname;
self.imapSession.port = imapPort;
self.imapSession.username = emailAddress;
self.imapSession.connectionType = MCOConnectionTypeTLS;
self.imapSession.password = nil;
self.imapSession.OAuth2Token = oAuthToken;
self.imapSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.imapSession.authType;
[self.imapSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type,
NSData * data){
NSLog(#"MCOIMAPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
self.smtpSession = [[MCOSMTPSession alloc] init];
self.smtpSession.hostname = smtpHostname;
self.smtpSession.port = smtpPort;
self.smtpSession.username = emailAddress;
self.smtpSession.connectionType = MCOConnectionTypeTLS;
self.smtpSession.password = nil;
self.smtpSession.OAuth2Token = oAuthToken;
self.smtpSession.authType = nil != oAuthToken ? MCOAuthTypeXOAuth2 :
self.smtpSession.authType;
[self.smtpSession setConnectionLogger:^(void * connectionID, MCOConnectionLogType type, NSData * data){
NSLog(#"MCOSMTPSession: [%i] %#", type, [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding]);
}];
[[self.imapSession checkAccountOperation] start:^(NSError *error) {
if (nil == error) {
success();
} else {
failure(error); //FAILS WITH INVALID CREDENTIALS ERROR
}
}];
}
Once again, the above code works fine, unless the application has not been used in some time. I was not sure if I needed to refresh the OAuthToken or not, so I tried doing the following on launch of the application:
GTMOAuth2Authentication *auth = [GTMOAuth2ViewControllerTouch authForGoogleFromKeychainForName:KeychainItemName clientID:GmailAppClientID clientSecret:GmailClientSecret];
BOOL canAuthorize = [auth canAuthorize]; //returns YES
NSDictionary *googleSettings = [[EmailServicesInfo emailServicesInfoDict] objectForKey:Gmail];
[[ModelManager sharedDefaultInstance] authenticateWithEmailAddress:[auth userEmail] oAuthToken:[auth refreshToken] imapHostname:[googleSettings objectForKey:IMAPHostName] imapPort:[[googleSettings objectForKey:IMAPPort]integerValue] smtpHostname:[googleSettings objectForKey:SMTPHostName] smtpPort:[[googleSettings objectForKey:SMTPPort]integerValue] type:EmailProtocolTypeImapAndSmtpGMail success:^(Account *account) {
//create IMAP session
} failure:^(NSError *error) {
NSLog(#"failure %#", error);
}];
But I still get the same error. I have no idea why the OAuth token stops working or how to resolve this. Since the user is able to save multiple accounts, I am wondering if I need to save the refresh token for each account in core data and use that if the access token stops working?
(Disclaimer - I don't know iOS or the gtm-oauth2 libraries, but I do know Google's OAuth implementation.)
Conceptually you do need to persist the refresh token for the user. The refresh token is a long-lived credential which is used (along with your client secret) to get a short-lived access token that is used for actual API calls.
If you anticipate making multiple calls in a short period of time then your app will normally actually persist both the refresh token and access token (currently access tokens will last 1 hour).
That all said, it looks like the gtm-oauth2 library should be taking care of persisting these already (looks like authForGoogleFromKeychainForName does this).
What I think you need help with is getting an up-to-date access token that you can use to initiate your IMAP session.
The gtm-oauth2 library does contain an authorizeRequest method. It takes information about an HTTP request you intend to make and adds the appropriate authorization headers. It looks like this code will examine the state of the access token, and refresh it if necessary.
While I know you aren't able to make an HTTP request (you need to speak IMAP), my suggestion is to use this method with a dummy NSMutableURLRequest - and then, once it's finished, don't actually send the HTTP request, instead examine the headers it added and pull the access token from there.
See:
https://code.google.com/p/gtm-oauth2/wiki/Introduction#Using_the_Authentication_Tokens
Hope that helps - I don't have an iOS environment to test it on.
I need to do make some API calls in the background on the server that's why I need the oauth token.
Using ACAccountCredential's oauthToken method, it returns null. My question is, it it possible to somehow get the oauth token?
For example Instagram does this. They authenticate via Accounts framework and send the token to their servers where they do all the graph API calls.
Thanks
(Edited to address #0xSina's comment)
Yes. I understand that you've tried accessing the oauthToken property of the ACAccountCredential, of the Facebook ACAccount object and that it is returning nil. That said, this IS the way to do it and it does not return nil when everything is set up correctly.
I created a test project to prove this and the relevant code is below. A few things to check:
1) on your Facebook app information page, ensure that you have your bundle ID set in the Native iOS App section.
2) (pretty obviously) make sure you have your Facebook account setup in iOS settings.
3) if you can't get your main project to work, try an empty test project, as I did, and see if that works. You'll have to remember to set your test project's bundleID to match what you entered on the Facebook settings page!
Here's the code with some asserts to notify you if something's failing:
- (void) viewDidAppear:(BOOL)animated
{
ACAccountStore* as = [ACAccountStore new];
ACAccountType* fbat = [as accountTypeWithAccountTypeIdentifier: ACAccountTypeIdentifierFacebook];
NSAssert( fbat != nil, #"oops, can't find account-type for facebook!");
[as requestAccessToAccountsWithType: fbat
options: #{ ACFacebookAppIdKey : #"99999999999", // your FB appid here!
ACFacebookPermissionsKey : #[ #"email" ] }
completion: ^(BOOL granted, NSError *error) {
NSAssert( granted && error==nil, #"oops, access not granted!");
if ( granted )
{
NSArray* accounts = [as accountsWithAccountType: fbat];
NSAssert( accounts.count > 0, #"oops, no accounts??" );
ACAccount* fbaccount = [accounts lastObject];
ACAccountCredential* ac = [fbaccount credential];
NSAssert( ac != nil, #"oops, no credential??" );
NSString* token = [ac oauthToken];
NSAssert( token.length > 0, #"oops, no credential??" );
NSLog( #"token=%#", token );
}
}];
}
One common issue, I think, is to mis-format the ACFacebookPermissionsKey as a string instead of an array of strings. Or to forego it altogether because the docs say it is optional (it isn't.)
And, here's the output from my test (slightly altered so I'm not publishing a real token here)
2013-07-26 16:54:23.699 testfb[26870:1303] token=CAAAAEZBYxezsBAK6OK9YPQIfA0GjiG7rn2XjdZBC6lZCpfAAAocr2cyEnmne9eMh59ZA4UabQyZAsdQMu0gMruxEJXJI1tFfBVTmLqNpRK31GPQvr3rXRAr50HUXI37iS46gPwXwZAroUHT7WqZCza6HcV6L35gVThBcOttWNxozj1df70XluEreM14nucSmvimjVGu5i4ZBZAUrSYLsvO
It seems that sometimes, either due to one's development process or timed expiration, the Facebook credentials for an app can become invalid. At this point, calls to account.credential will return invalid or nil. In this case you must renew the credential. To do this, simply call -renewCredentialsForAccount:completion: on the account in question. For Facebook accounts this will renew the oauth token automatically.
For other service types the user may be prompted for their password, check the docs in this case.
I was having the exact same issue. The problem for me was that I was setting my Facebook account in the completion handler of: requestAccessToAccountsWithType and then attempting to access the ACAccountCredential in a different function.
Getting the oauth token in the completion handler and saving it for later solved the problem for me:
[account requestAccessToAccountsWithType:accountType options:options completion:
^(BOOL granted, NSError *error) {
...
dispatch_async(dispatch_get_main_queue(), ^{
facebookAccount = [arrayOfAccounts lastObject];
oAuthToken = facebookAccount.credential.oauthToken;
});
...
}
];