I use Restkit in my iOS app to call an API.
When the app first launched, a client token is retrieved from the API in order to call the service to post a new user.
After this user has successfully being created, a new access token is sent back by the API. This time is it is a user token.
All the other requests to the API made by the app will now have to use this user token.
I am using a singleton class that inherits from RKObjectManager. I then built one class per ressource to access (example : Users, Images, ...) all inheriting from that main class called AKObjectManager.
In AKObjectManager I have the following method :
+ (instancetype)sharedManager
{
NSURL *url = [NSURL URLWithString:LLY_API_BASE_URL];
AKObjectManager *sharedManager = [self managerWithBaseURL:url];
sharedManager.requestSerializationMIMEType = RKMIMETypeJSON;
...
// Access Token
NSUserDefaults* userData = [NSUserDefaults standardUserDefaults];
if ([userData objectForKey:#"accessToken"]) {
// Not too sure if this is being taken into account for the other class that inherits
[ sharedManager.HTTPClient setDefaultHeader:#"Authorization" value:[userData objectForKey:#"accessToken"]];
}
return sharedManager;
}
I thought that by checking for every access the accessToken in NSUserDefaults and setting it in the Authorization field in the header would work but no. I can see through NSLog that the new access token is set when changing it for for some reasons using Charles the header of the request still points to the old one.
I then used
[[AKObjectManager sharedManager].HTTPClient setDefaultHeader:#"Authorization" value:accessToken.accessToken];
As soon as I got the new token but faced the same issue.
Finally I went for that road (UserManager inherits from AKObjectManager)
// Force the newly refresh token to be set in the Authorization header
[[UserManager sharedManager].HTTPClient setDefaultHeader:#"Authorization" value:[[NSUserDefaults standardUserDefaults] objectForKey:#"accessToken"]];
[[UserManager sharedManager] show:nil // userId equals nil meaning it will be replaced by 'self'
success:^(User* user){
self.user = user;
...
And it worked but I am not too happy about the implementation.
Could you point me to where I got it wrong and advise on how to do it ?
Related
I'm trying to read in a Google sheet my Twitter timeline.
I've copied the following code reported in the GAS documentation about twitter authentication (omitting step 2 since I'm not using the code inside a UI):
function getTwitterService() {
// Create a new service with the given name. The name will be used when
// persisting the authorized token, so ensure it is unique within the
// scope of the property store.
return OAuth1.createService('twitter')
// Set the endpoint URLs.
.setAccessTokenUrl('https://api.twitter.com/oauth/access_token')
.setRequestTokenUrl('https://api.twitter.com/oauth/request_token')
.setAuthorizationUrl('https://api.twitter.com/oauth/authorize')
// Set the consumer key and secret.
.setConsumerKey('mykey')
.setConsumerSecret('mysecret')
// Set the name of the callback function in the script referenced
// above that should be invoked to complete the OAuth flow.
.setCallbackFunction('authCallback')
// Set the property store where authorized tokens should be persisted.
.setPropertyStore(PropertiesService.getUserProperties());
}
function authCallback(request) {
var twitterService = getTwitterService();
var isAuthorized = twitterService.handleCallback(request);
if (isAuthorized) {
return Logger.log('Success! You can close this tab.');
} else {
return Logger.log('Denied. You can close this tab');
}
}
function makeRequest() {
var twitterService = getTwitterService();
var response = twitterService.fetch('https://api.twitter.com/1.1/statuses/user_timeline.json');
Logger.log(response);
}
but I obtain the message error: Service not authorized. (row 292, file "Service", project "OAuth1").
What's wrong?
I needed to add the following line the first time I execute makeRequest:
var authorizationUrl = twitterService.authorize();
Logger.log(authorizationUrl);
Then, open the url read from the log and authorize the app.
After that, all works fine.
I am using a "Cognito User Pool authorizer" (no "AWS_IAM" option, no custom coded authorizer) to call Lambda methods via API Gateway and identify the user logged in on the iOS client.
On Lambda, I use the user id I get from the Cognito User Pool authorizer via event.requestContext.authorizer.claims.sub (to store the user id with some DynamoDB items).
I now need to compare this with the id of the logged in user in the iOS client.
I found [AWSIdentityManager defaultIdentityManager].identityId, but this (obviously) returns he IdentityID (which I can look up in the AWS console in Cognito --> Federated Identities --> Identity Browser), which is different from the "sub" id I see in Cognito --> User Pools --> Users and groups
Can I get the "sub" via the AWS iOS SDK?
If I cannot get it, what other id parameter should I use that I can retrieve both on Lambda and the client to identify the current client user/the user making the API request?
It seems that I have to specifically request the attributes via the user details like this:
AWSCognitoIdentityUserPool *pool = [AWSCognitoIdentityUserPool CognitoIdentityUserPoolForKey:AWSCognitoUserPoolsSignInProviderKey];
AWSCognitoIdentityUser *user = [pool currentUser];
NSString *mySub;
[[user getDetails] continueWithBlock:^id _Nullable(AWSTask<AWSCognitoIdentityUserGetDetailsResponse *> * _Nonnull task) {
if(!task.error){
AWSCognitoIdentityUserGetDetailsResponse *response = task.result;
NSArray<AWSCognitoIdentityProviderAttributeType*> *userAttributes = response.userAttributes;
for (AWSCognitoIdentityProviderAttributeType *attr in self.userAttributes) {
if ([attr.name isEqualToString:#"sub"]) {
mySub = attr.value;
}
}
} else {
NSLog(#"Error fetching Cognito User Attributes: %#", task.error.localizedDescription);
}
}];
Another solution (tested with the AWS JavaScript SDK):
When we authenticate with Cognito, we can retrieve a JWT token:
user.authenticateUser(authenticationDetails, {
onSuccess: (result) => resolve(result.getIdToken().getJwtToken()),
onFailure: (err) => reject(err)
})
It happens that this JWT token is an standard object that can be decoded.
Using Auth0 JWT decode (npm install jwt-decode), we can decode this token and retrieve all user attributes (e-mail, username, etc.) and the sub.
var jwtDecode = require('jwt-decode');
var decoded = jwtDecode(token);
console.log(decoded);
// prints sub, email, username, ...
As the token will get expired in one hour and we need to fetch new token, I want to know will be allocating cumstomIdentityProvider class again or thers is another ay of doing that. Need help.
This is i have implemented in My cumstomIdentityProvider.
- (AWSTask *)refresh {
/*
* Get the identityId and token by making a call to your backend
*/
// Call to your backend
// Set the identity id and token
self.identityId = IdentityId;
self.token = Token;
return [AWSTask taskWithResult:self.identityId];
}
I'm trying to use Xamarin native iOS library to authenticate with Facebook and access Graph API.
According to release 4.0.1.1 notes for the component (I didn't find any other documentation anywhere)
FBSDKTokenCachingStrategy. No alternative. LoginManager class caches
tokens to keychain automatically. You can observe token changes to do
manual post processing.
However this doesn't seem to be happening. When my iOS application starts I create LoginManager instance and call Init. However after that AccessToken.CurrentAccessToken is still null. It is only populated with data after I call LogInWithReadPermissionsAsync on the LoginManager.
Am I missing something or is it a bug.
Here's my code.
public bool IsLoggedIn
{
get
{
return AccessToken.CurrentAccessToken != null &&
AccessToken.CurrentAccessToken.ExpirationDate.ToDateTime() > DateTime.Now;
}
}
public Task<AccessToken> FacebookLoginInternal()
{
lock (monitor)
{
if (_loginTask == null)
{
LoginManager manager = new LoginManager();
manager.Init();
if (IsLoggedIn)
{
var ts = new TaskCompletionSource<AccessToken>();
ts.SetResult(AccessToken.CurrentAccessToken);
_loginTask = ts.Task;
}
else
{
var loginResult = manager.LogInWithReadPermissionsAsync(
new string[] { "email", "user_friends" });
_loginTask = loginResult.ContinueWith(r =>
{
return r.Result.Token;
});
}
}
return _loginTask;
}
As per response from Xamarin support (thank you!)
The following code fixes the issue:
public override bool FinishedLaunching(UIApplication app, NSDictionary options)
{
return ApplicationDelegate.SharedInstance.FinishedLaunching(app, options);
}
This would seem to be expected behavior? You won't have a token until you log in, and this seems expected. I believe you may have misunderstood the note you pasted. It does not say that the token is cached as soon as you instantiate LoginManager or call Init on it, just that LoginManager will cache the token. It can't cache a token until a token is generated when you log in. That is why (I believe) Guilherme Torres Castro asked if the token is the same after a second call to LogInWithReadPermissionsAsync. If so, then the token was cached upon login.
Update: Communication with the OP via other channels indicates that I misunderstood. Log in is not maintained after app termination and relaunch, whereas in the native Obj-C Facebook iOS SDK it is. A bug has been filed: https://bugzilla.xamarin.com/show_bug.cgi?id=30287
I'm storing the oauth info from Twitter in a Flash Cookie after the user goes though the oauth process. Twitter says that this token should only expire if Twitter or the user revokes the app's access.
Is there a call I can make to Twitter to verify that my stored token has not been revoked?
All API methods that require authentication will fail if the access token expires. However the specific method to verify who the user is and that the access token is still valid is GET account/verify_credentials
This question may be old, but this one is for the googlers (like myself).
Here is the call to twitter using Hammock:
RestClient rc = new RestClient {Method = WebMethod.Get};
RestRequest rr = new RestRequest();
rr.Path = "https://api.twitter.com/1/account/verify_credentials.json";
rc.Credentials = new OAuthCredentials
{
ConsumerKey = /* put your key here */,
ConsumerSecret = /* put your secret here */,
Token = /* user access token */,
TokenSecret = /* user access secret */,
Type = OAuthType.AccessToken
};
rc.BeginRequest(rr, IsTokenValid);
Here is the response:
public void IsTokenValid(RestRequest request, RestResponse response, object userState)
{
if(response.StatusCode == HttpStatusCode.OK)
{
var user = userState;
Helper.SaveSetting(Constants.TwitterAccess, user);
}
else
{
Dispatcher.BeginInvoke(() => MessageBox.Show("This application is no longer authenticated "))
}
}
I always borrow solutions from SO, this is my first attempt at giving back, albeit quite late to the question.
When debugging manually:
curl \
--insecure https://api.twitter.com/1/account/verify_credentials.json?oauth_access_token=YOUR_TOKEN
I am using TwitterOAuth API and here is the code based on the accepted answer.
$connection = new TwitterOAuth(CONSUMER_KEY, CONSUMER_SECRET, $twitter_oauth_token, $twitter_oauth_secret);
$content = $connection->get("account/verify_credentials");
if($connection->getLastHttpCode() == 200):
// Connection works fine.
else:
// Not working
endif;