Fitbit Authorization with OAuth.io - fitbit

I'm trying to use OAuth.io to allow users to connect their Fitbit to our application. Right now, I'm trying to see what information I can get from using the out-of-the-box Fitbit configuration using this code:
$scope.connectFitbit = function() {
OAuth.initialize(OAUTHIO_KEY);
OAuth.popup('fitbit').done(function(result) {
console.log(result);
});
};
I receive a token and token secret in the response, which is great, but I don't know how to get the Fitbit user ID. I will need to store all three of those to make API requests. Documentation is not clear on the next step, so any help would be appreciated!
Including the console output for reference.
Object {oauth_token: "TOKEN", oauth_token_secret: "TOKEN_SECRET", get: function, post: function, put: function…}
del: function (opts, opts2) {
get: function (opts, opts2) {
me: function (filter) {
oauth_token: "TOKEN"
oauth_token_secret: "TOKEN_SECRET"
patch: function (opts, opts2) {
post: function (opts, opts2) {
put: function (opts, opts2) {
__proto__: Object

So, despite radio silence here, I slogged on and found out the answer. You need to then make a call to the user profile API, but using the authenticated user method.
Put this inside your done block:
result.get("/1/user/-/profile.json").done(function(data)
{
uID = data.user.encodedId;
// Store uID along with token and secret for future use
}

Related

Get ACCESS_TOKEN_SCOPE_INSUFFICIENT error migrating to People API

I have a desktop Java app that I am migrating from Google Contacts API to People API. I have some of it working. For example, I can retrieve contact information. But when I tried to create a new contact, I get the following error:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
POST https://people.googleapis.com/v1/people:createContact
{
"code" : 403,
"details" : [ {
"#type" : "type.googleapis.com/google.rpc.ErrorInfo",
"reason" : "ACCESS_TOKEN_SCOPE_INSUFFICIENT"
} ],
"errors" : [ {
"domain" : "global",
"message" : "Insufficient Permission",
"reason" : "insufficientPermissions"
} ],
"message" : "Request had insufficient authentication scopes.",
"status" : "PERMISSION_DENIED"
}
Here's the relevant code:
protected void createContact() throws Exception {
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
PeopleService service = new PeopleService.Builder(
httpTransport, JSON_FACTORY, credential).setApplicationName(APPLICATION_NAME).build();
Person contactToCreate = new Person();
List<Name> names = new ArrayList<Name>();
names.add(new Name().setGivenName("John").setFamilyName("Doe"));
contactToCreate.setNames(names);
Person createdContact = service.people().createContact(contactToCreate).execute();
System.out.println("CREATED Contact: " + createdContact.getNames().get(0).getDisplayName());
}
protected Credential authorize(String scope, String subDir) throws Exception {
File dataStoreDir = new File(System.getProperty("user.home"), ".store/myapp/" + cfg.dataStore + "/" + subDir);
// initialize the transport
httpTransport = GoogleNetHttpTransport.newTrustedTransport();
// initialize the data store factory
dataStoreFactory = new FileDataStoreFactory(dataStoreDir);
// load client secrets
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY,
new InputStreamReader(SyncMgr.class.getResourceAsStream("/client_secrets.json")));
if (clientSecrets.getDetails().getClientId().startsWith("Enter")
|| clientSecrets.getDetails().getClientSecret().startsWith("Enter ")) {
System.out.println(
"Enter Client ID and Secret from https://code.google.com/apis/console/?api=calendar "
+ "into /client_secrets.json");
System.exit(1);
}
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
}
When I first ran it, I had the scope set to CONTACTS_READONLY. And I got the consent screen. But then I changed the scope to CONTACTS when I added the code to create a new contact. And that's when I got the ACCESS_TOKEN_SCOPE_INSUFFICIENT error.
I saw in another post that I need to force your app to reauthorize the user when you change the scope, so that you get the consent screen again. But I'm not sure how to do that. Any suggestions?
Thanks.
UPDATE 1/4/22
I tried Gabriel's suggestion of removing access to the application. After removing access, I ran the application again. This time I got this error on the execute() call:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
POST https://oauth2.googleapis.com/token
{
"error" : "invalid_grant",
"error_description" : "Token has been expired or revoked."
}
And even the execute() statement that worked before to retrieve contacts is giving the same error now.
My application also used the Calendar API. I didn't touch that code. But when I try to use it, I get the same "invalid_grant" error. What do I do now?
You appear to be using the People.createContact method. If we take a look at the documentation we will see that this method requires a consent to the following scope of permissions from the user
Now if we check your code you apear to be using
Credential credential = authorize(PeopleServiceScopes.CONTACTS, "people");
Which is the exact scope needed. But you oringally had readonly there. So when your code ran the first time the user authorized to the read only scope and not the full contacts scope and your stuck.
The key here is this section of code.
// set up authorization code flow
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
httpTransport, JSON_FACTORY, clientSecrets,
Collections.singleton(scope)).setDataStoreFactory(dataStoreFactory).build();
// authorize
return new AuthorizationCodeInstalledApp(flow, new LocalServerReceiver()).authorize(cfg.gUser);
Kindly note I am not a Java developer I am a .net developer. The libraries are very close and i have been helping with questions this in both languages for years.
dataStoreFactory is where the consent from the user is stored. There should be a json file some where in your directory structure with the users name associated with it this is how your system reloads it. When your code runs it will look for a file in that directory with cfg.gUser name.
There should be a way in the Java client library to force it to rerequest authorization of the user. prompt type force. But i will have to look around to see how to do it in java.
The easiest solution now would be to find that directory and delete the file for the user or just change the users name cfg.gUser to cfg.gUser +"test" or something this will cause the name to change and the file name as well. Forcing it to prompt the user for authorization again.
This time when it requests consent take note which scope of permissions it asks for.
Token has been expired or revoked.
This is probably due to the fact that your refresh tokens are expiring. When your application is in the testing phase the refresh tokens are expired or revoked automatically by google after seven days.
This is something new and something that Google added in the last year or so. Unfortunately the client libraries were not designed to request access again if the refresh token was expired in this manner.
If you are looking to retrieve the consent screen again you can remove access to your application from your account settings by following the steps in this documentation and then try to authorize the app again. As you mentioned, the error received is due to the scope that was granted with authorization was CONTACTS_READONLY instead of CONTACTS when checking the authorization scope for this specific create contacts method.

AWS Cognito integration swift3 Refresh provides ResourceNotFoundException

Following the answer here:
https://github.com/aws/aws-sdk-ios/issues/357
At the very bottom there is a mini guide on getting swift and cognito working.
I've made a AWSCustomIdentityProvider as such:
import Foundation
import AWSCognitoIdentityProvider
import AWSCognito
class AWSCustomIdentityProvider: NSObject, AWSIdentityProviderManager
{
private var dict = NSDictionary()
func addToken(value:NSString)
{
dict = ["graph.facebook.com":value]
}
public func logins() -> AWSTask<NSDictionary>
{
return AWSTask(result: dict)
}
}
And I have a login method from facebook:
public func loginButtonDidCompleteLogin(_ loginButton: FacebookLogin.LoginButton, result: FacebookLogin.LoginResult){
switch result {
case .failed(let error):
print("FACEBOOK LOGIN FAILED: \(error)")
case .cancelled:
print("User cancelled login.")
case .success(_, _, let accessToken):
let customIdentity = AWSCustomIdentityProvider()
let token = accessToken.authenticationToken
customIdentity.addToken(value: token as NSString)
let credentialsProvider = AWSCognitoCredentialsProvider(regionType: REGIONTYPE, identityPoolId: "XXXXXXXXXXXXXXXXXXXXXXX", identityProviderManager:customIdentity)
credentialsProvider.clearKeychain()
credentialsProvider.clearCredentials()
let serviceConfiguration = AWSServiceConfiguration(region: REDIONTYPE, credentialsProvider: credentialsProvider)
AWSServiceManager.default().defaultServiceConfiguration = serviceConfiguration;
credentialsProvider.getIdentityId().continue( { (task: AWSTask!) -> AnyObject! in
if (task.error != nil) {
print("Error: " + (task.error?.localizedDescription)!)// gets called
}
else {
print(task.result)//identityid
}
return nil
})
}
}
However I get the error:
Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=8 "(null)" UserInfo={__type=NotAuthorizedException, message=Logins don't match. Please include at least one valid login for this identity or identity pool.}
Please let me know if you have any idea on how to resolve my issue. I've tried also following the docs and setting the logins directly "credentialsProvider.logins = {"graph.facebook.com": mytoken}
and that produces a different exception upon invoking a lambda method but DOES retrieve the identityID properly. However doing it according to the docs makes a warning that the method I'm using is deprecated.
The error I get:
UserInfo={NSLocalizedDescription=serialized object is neither a valid json Object nor NSData object: }
However that only happens sometimes. If I retry then i can potentially get the identity id but upon invoking a lambda method, I get the same error. I'm assuming it is a cognito issue.
UPDATE
If I use AWSCognitoLoginProviderKey.facebook.rawValue instead of graph.facebook.com in the first part, then it gives me the cognito ID and then I invoke the lambda method. I'll include the lambda method just incase that's the part I'm getting wrong but I'm decently sure that it's cognito that is preventing me from calling the lambda method:
import AWSLambda
import Foundation
struct AWSHelper{
let lambda = AWSLambda.default()
let APPLICATION_NAME = "MYAPPLICATION"
init(){
}
func getFunctionName(funcName: String) -> String{
return "\(funcName)_\(APPLICATION_NAME)"
}
func login(facebookID: String, cognitoID:String, callback:#escaping (Bool) -> Void){
let req = AWSLambdaInvocationRequest();
req?.invocationType = AWSLambdaInvocationType.requestResponse
req?.payload = ["cognitoID" : cognitoID, "facebookID" : facebookID]
req?.functionName = getFunctionName(funcName: "MYFUNCNAME")
lambda.invoke(req!) { (response: AWSLambdaInvocationResponse?,error: Error?) in
print(error)
let payload = response?.payload
print(payload)
callback(true)
}
}
}
Update 2
I have found out that calling a refresh method like this:
credentialsProvider.credentials().continue({ (task: AWSTask!) -> Any? in
print(task.result)
})
Causes an error like this:
AWSiOSSDK v2.4.10 [Error] AWSCredentialsProvider.m line:577 | __44-[AWSCognitoCredentialsProvider credentials]_block_invoke.352 | Unable to refresh. Error is [Error Domain=com.amazonaws.AWSCognitoIdentityErrorDomain Code=10 "(null)" UserInfo={__type=ResourceNotFoundException, message=Identity 'us-east-1:0db18266-1baa-4c59-9110-f9041dc92ead' not found.}]
I believe the big string that looks like an identitypoolID is actually the identityID for the given user that I have, so cognito has distributed an ID but is not able to query it?
the error:
Logins don't match. Please include at least one valid login for this identity or identity pool
Can also occur because you attempt to log in as another user without logging out, so the token in the logins dictionary is compared to the identityId for a different identity (and doesn't match). In this case the SDK usually recovers by retrying, clearing and reestablishing the identityId, and then it works.
But in your case since you are constructing your own logins dictionary, the issue is more likely that you have constructed a token that does not match. You can inspect tokens using https://jwt.io. (though I admit it works for google and cognito user pools, but not on facebook tokens (I don't know why this is)),
I think doesn't match means that the identityId records a different unique user than is specified in the token.
Are you sure the token is constructed correctly?
As you mentioned... the documentation.. well .. I find the documentation is not worth looking at, So I set up my projects so I can review working code and set breakpoints.
Here is a snippet of the code from Mobile Hub Helper's Facebook AWSSignInProvider, which shows what they use to get the token (token.tokenstring).
- (AWSTask<NSString *> *)token {
FBSDKAccessToken *token = [FBSDKAccessToken currentAccessToken];
NSString *tokenString = token.tokenString;
NSDate *idTokenExpirationDate = token.expirationDate;
if (tokenString
// If the cached token expires within 10 min, tries refreshing a token.
&& [idTokenExpirationDate compare:[NSDate dateWithTimeIntervalSinceNow:AWSFacebookSignInProviderTokenRefreshBuffer]] == NSOrderedDescending) {
return [AWSTask taskWithResult:tokenString];
}
AWSTaskCompletionSource *taskCompletionSource = [AWSTaskCompletionSource taskCompletionSource];
[FBSDKLoginManager renewSystemCredentials:^(ACAccountCredentialRenewResult result, NSError *error) {
if (result == ACAccountCredentialRenewResultRenewed) {
FBSDKAccessToken *token = [FBSDKAccessToken currentAccessToken];
NSString *tokenString = token.tokenString;
taskCompletionSource.result = tokenString;
} else {
taskCompletionSource.error = error;
}
}];
return taskCompletionSource.task;
}
Also... It bears mentioning. The AWSIdentityManager, and it's associated AWSSignInProviders is a nice architecture for getting signed in with Facebook and Google. Even if you don't use the rest of Mobile Hub Helper. Why re-invent the wheel, they did a very good job on the Identity portion of aws-mobilehub-helper-ios
I have a version of that library posted on github that adds an AWSSignInProvider for Cognito User Pools as well. SignIn-awsmhh it requires some fixes in the aws-mobilehub-helper-ios to use cognito user pools they are here aws-mobilehub-helper-ios (so if you clone do a clone --recursive and you will be set up for debugging using breakpoints in the library).
Few things that made it work I think.
I made the correct move by making my own identityprovidermanager and I think the main thing that was blocking me from executing a lambda method was actually the fact that I was using AWSLambda instead of AWSLambdaInvoker. After I switched it started making errors that made sense.

Get user access token facebook access token in node

I'm trying to make a call to the Facebook Graph API, I'm using node & express for this, it's my first time using them, when I make a call to the API I get the error below, how can I get the access token or set it so the call goes through ?
message: 'Invalid OAuth access token.',
type: 'OAuthException',
code: 190,
fbtrace_id: 'hgjhguoiu' }
// This is the call to API I'm making :
FB.api('4', function (res) {
if(!res || res.error) {
console.log(!res ? 'error occurred' : res.error);
return;
}
console.log(res.id);
console.log(res.name);
});
// This is the authentication call:
app.get('/auth/facebook/callback',
passport.authenticate('facebook', {
successRedirect : '/',
failureRedirect: '/login'
}));
Thanks
I know this is 12 months old, but anyways:
I am using 'fb' (https://www.npmjs.com/package/fb) for node and I had the same errors as described.
To authenticate your request you just have to write FB.setAccessToken(<your_token>);
You need a different token for different purposes. You can read about them here: https://developers.facebook.com/docs/facebook-login/access-tokens/?locale=de_DE
Hope it helps anyone.

Identity Server 3 Facebook Login Get Email

Identity server is implemented and working well. Google login is working and is returning several claims including email.
Facebook login is working, and my app is live and requests email permissions when a new user logs in.
The problem is that I can't get the email back from the oauth endpoint and I can't seem to find the access_token to manually request user information. All I have is a "code" returned from the facebook login endpoint.
Here's the IdentityServer setup.
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"]
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then of course I've customized the AuthenticateLocalAsync method, but the claims I'm receiving only include name. No email claim.
Digging through the source code for identity server, I realized that there are some claims things happening to transform facebook claims, so I extended that class to debug into it and see if it was stripping out any claims, which it's not.
I also watched the http calls with fiddler, and I only see the following (apologies as code formatting doesn't work very good on urls. I tried to format the querystring params one their own lines but it didn't take)
(facebook.com)
/dialog/oauth
?response_type=code
&client_id=xxx
&redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&scope=email
&state=xxx
(facebook.com)
/login.php
?skip_api_login=1
&api_key=xxx
&signed_next=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx&cancel_url=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook%3Ferror%3Daccess_denied%26error_code%3D200%26error_description%3DPermissions%2Berror%26error_reason%3Duser_denied%26state%3Dxxx%23_%3D_
&display=page
&locale=en_US
&logger_id=xxx
(facebook.com)
POST /cookie/consent/?pv=1&dpr=1 HTTP/1.1
(facebook.com)
/login.php
?login_attempt=1
&next=https%3A%2F%2Fwww.facebook.com%2Fv2.7%2Fdialog%2Foauth%3Fredirect_uri%3Dhttps%253A%252F%252Fidentity.[site].com%252Fid%252Fsignin-facebook%26state%3Dxxx%26scope%3Demail%26response_type%3Dcode%26client_id%3Dxxx%26ret%3Dlogin%26logger_id%3Dxxx
&lwv=100
(facebook.com)
/v2.7/dialog/oauth
?redirect_uri=https%3A%2F%2Fidentity.[site].com%2Fid%2Fsignin-facebook
&state=xxx
&scope=email
&response_type=code
&client_id=xxx
&ret=login
&logger_id=xxx
&hash=xxx
(identity server)
/id/signin-facebook
?code=xxx
&state=xxx
I saw the code parameter on that last call and thought that maybe I could use the code there to get the access_token from the facebook API https://developers.facebook.com/docs/facebook-login/manually-build-a-login-flow
However when I tried that I get a message from the API telling me the code has already been used.
I also tried to change the UserInformationEndpoint to the FacebookAuthenticationOptions to force it to ask for the email by appending ?fields=email to the end of the default endpoint location, but that causes identity server to spit out the error "There was an error logging into the external provider. The error message is: access_denied".
I might be able to fix this all if I can change the middleware to send the request with response_type=id_token but I can't figure out how to do that or how to extract that access token when it gets returned in the first place to be able to use the Facebook C# sdk.
So I guess any help or direction at all would be awesome. I've spent countless hours researching and trying to solve the problem. All I need to do is get the email address of the logged-in user via IdentityServer3. Doesn't sound so hard and yet I'm stuck.
I finally figured this out. The answer has something to do with Mitra's comments although neither of those answers quite seemed to fit the bill, so I'm putting another one here. First, you need to request the access_token, not code (authorization code) from Facebook's Authentication endpoint. To do that, set it up like this
var fb = new FacebookAuthenticationOptions
{
AuthenticationType = "Facebook",
SignInAsAuthenticationType = signInAsType,
AppId = ConfigurationManager.AppSettings["Facebook:AppId"],
AppSecret = ConfigurationManager.AppSettings["Facebook:AppSecret"],
Provider = new FacebookAuthenticationProvider()
{
OnAuthenticated = (context) =>
{
context.Identity.AddClaim(new System.Security.Claims.Claim("urn:facebook:access_token", context.AccessToken, ClaimValueTypes.String, "Facebook"));
return Task.FromResult(0);
}
}
};
fb.Scope.Add("email");
app.UseFacebookAuthentication(fb);
Then, you need to catch the response once it's logged in. I'm using the following file from the IdentityServer3 Samples Repository, which overrides (read, provides functionality) for the methods necessary to log a user in from external sites. From this response, I'm using the C# Facebook SDK with the newly returned access_token claim in the ExternalAuthenticationContext to request the fields I need and add them to the list of claims. Then I can use that information to create/log in the user.
public override async Task AuthenticateExternalAsync(ExternalAuthenticationContext ctx)
{
var externalUser = ctx.ExternalIdentity;
var claimsList = ctx.ExternalIdentity.Claims.ToList();
if (externalUser.Provider == "Facebook")
{
var extraClaims = GetAdditionalFacebookClaims(externalUser.Claims.First(claim => claim.Type == "urn:facebook:access_token"));
claimsList.Add(new Claim("email", extraClaims.First(k => k.Key == "email").Value.ToString()));
claimsList.Add(new Claim("given_name", extraClaims.First(k => k.Key == "first_name").Value.ToString()));
claimsList.Add(new Claim("family_name", extraClaims.First(k => k.Key == "last_name").Value.ToString()));
}
if (externalUser == null)
{
throw new ArgumentNullException("externalUser");
}
var user = await userManager.FindAsync(new Microsoft.AspNet.Identity.UserLoginInfo(externalUser.Provider, externalUser.ProviderId));
if (user == null)
{
ctx.AuthenticateResult = await ProcessNewExternalAccountAsync(externalUser.Provider, externalUser.ProviderId, claimsList);
}
else
{
ctx.AuthenticateResult = await ProcessExistingExternalAccountAsync(user.Id, externalUser.Provider, externalUser.ProviderId, claimsList);
}
}
And that's it! If you have any suggestions for simplifying this process, please let me know. I was going to modify this code to do perform the call to the API from FacebookAuthenticationOptions, but the Events property no longer exists apparently.
Edit: the GetAdditionalFacebookClaims method is simply a method that creates a new FacebookClient given the access token that was pulled out and queries the Facebook API for the other user claims you need. For example, my method looks like this:
protected static JsonObject GetAdditionalFacebookClaims(Claim accessToken)
{
var fb = new FacebookClient(accessToken.Value);
return fb.Get("me", new {fields = new[] {"email", "first_name", "last_name"}}) as JsonObject;
}

Meteor share sessions data between client and server

I'm building a restricted signup. I want user with a specific code passed in a url to be able to signup and not others. I'm using the accounts package.
I can prevent account creation in the Accounts.onCreateUser method. I'm looking for a way to tell the server if the client had an authorised signup code. With a classic form (email+password) I can just add an extra hidden field. How can I achieve the same result if the user signs up with let's say Facebook?
Since Meteor doesn't use cookies, I can't store this info in a cookie that the server would access. Session variable are not accessible server side. And since I'm not controlling what got send with the account-facebook creation, I can't use a Session variable on the client side that I'd pass along when the user presses sign up.
Any idea"?
Just add the special token to the user object being passed to Accounts.createUser():
var user = {
email: email,
password: password,
profile: {
token: token
}
};
Accounts.createUser(user, function (error, result) {
if (error) {
console.log(error)
}
});
On the server side you can access this in the Accounts.onCreateUser():
Accounts.onCreateUser(function(options, user) {
console.log(options);
console.log(user);
});
I think it's in the options variable that you will find your token, so it would be options.profile.token.
for me, the best option here was passing in custom parameters to loginbuttons.
see the package docs:
https://github.com/ianmartorell/meteor-accounts-ui-bootstrap-3
Where it outlines the below:
accountsUIBootstrap3.setCustomSignupOptions = function() {
return {
mxpDistinctId: Session.get('mxpdid'),
leadSource: Session.get('leadSource')
}
};