I'm developing a SpringBoot application which needs to manage multiple Facebook Ad accounts. I've successfully used the Facebook Java Ads SDK:
https://github.com/facebook/facebook-java-ads-sdk
to connect to a single Facebook ads account, via a Facebook app within the same account (account A) and create a campaign and query campaigns, ads sets, etc:
public static final APIContext context = new APIContext(ACCESS_TOKEN, APP_SECRET);
public static void main(String[] args)
{
try
{
AdAccount account = new AdAccount(ACCOUNT_ID, context);
Campaign campaign = account.createCampaign().setName("Java SDK Test Campaign")
.setObjective(Campaign.EnumObjective.VALUE_LINK_CLICKS).setSpendCap(10000L)
.setStatus(Campaign.EnumStatus.VALUE_PAUSED).execute();
System.out.println(campaign.fetch());
} catch (APIException e)
{
e.printStackTrace();
}
}
Thew ACCESS_TOKEN, APP_SECRET and ACCOUNT_ID are all from the same Facebook account (account A), the one in which the Facebook app was created. The Facebook app has the ads_management permissions reviewed and approved.
Now I need to be able to access Facebook Ad Accounts from other Facebook accounts. I've used the spring-social-facebook Accessing Facebook Data tutorial:
https://spring.io/guides/gs/accessing-facebook/
To register a different Facebook account (account B) with the Facebook App, using the scopes "ads_read, ads_management" and the account ID for the new Facebook account (account B) was added to the Facebook App from the original account (account A).
However, when I used the second account's account ID in the code above I get:
{"error":{"message":"Unsupported get request. Object with ID 'act_105428933326149' does not exist, cannot be loaded due to missing permissions, or does not support this operation. Please read the Graph API documentation at https:\/\/developers.facebook.com\/docs\/graph-api","type":"GraphMethodException","code":100,"fbtrace_id":"Ee5exTqJdCp"}
Which suggests I have a permissions issue somewhere, but I'm stumped. I'm not even sure I'm following the correct approach.
I've tried googling for an example of how to use the Facebook Ads API with spring-social-facebook, but other than a few commits to the repository which don't help a great deal, I can't find one.
Facebook Ads API has tiered access. A newly registered app is in Development Tier and can only access the user's own ad accounts. You need to meet certain criteria to get promoted to Basic Tier and manage other people's ad account.
Related
I have a server-side application that needs to access every user's gmail data in a google workspace organization. I want to publish a public listing on the google workspace marketplace that is installable domain-wide by the super admin user and gives the server-side application the permissions to access the gmail data of the user's in that domain.
From my current understanding, we need a service account with impersonation to access each user's data. On top of that the service account needs to be delegated domain-wide authority, so that user's do not need to give individual OAuth consent or their passwords.
When publishing the app using the marketplace SDK I see that there is a field for service account credentials:
I see that the current Marketplace SDK has a field that accepts service account credentials.
But when my app is published and I install it and go to check the app's data access. I can only see fields for the scopes and the Oauth clients.
My questions:
Are the service accounts created in the marketplace SDK usable to the organization that installs my marketplace app? Will the service account's have the same email and unique id for everyone who installs the public listed app?
If (1) is not true, then how is it possible for admins to create a service account for my marketplace app?
If (1) is true, is it automatically granted access unlike the OAuth clients and scopes?
If (1) is true, Are the service accounts automatically delegated domain-wide on install or do we have to provide the person who installed the marketplace app with the service accounts unique ids so they can manually delegate the scopes domain-wide.
I reviewed some Google public documentations related to Service Account & here’s what I have found that may answer your questions:
Question 1
Are the service accounts created in the marketplace SDK usable to the organization that installs my marketplace app?
Answer
No
Note: The CREDENTIALS tab that you see on the Google Workspace Marketplace SDK page is only an overview of credentials you have created for the GCP Project & NOT necessarily only for that service/API.
Service Accounts are created within a specific GCP Project & that project is where you will enable the Google APIs/Services that your application needs. Google Workspace Marketplace SDK is being described as:
“A toolkit that lets you create and control your app listing on the Google Workspace Marketplace, or for Chat apps, in Google Chat.” (Source)
So, this Google Workspace Marketplace SDK doesn’t necessarily use a Service Account to authenticate & be called in your app. However, when you setup a Service Account for your app, you'll need to create a Google Workspace Marketplace OAuth Client & this OAuth Client is associated to that Service Account. This is needed to support Google Workspace Marketplace domain-wide installation.
Setting up the Google Workspace Marketplace OAuth Client from the GCP console:
Follow-up Question
Will the service account's have the same email and unique id for everyone who installs the public listed app?
Answer
Yes. In theory, it should be.
Question 2
If (1) is not true, then how is it possible for admins to create a service account for my marketplace app?
Answer
You have to review the official Google documentation for OAuth & Service account.
Based on the official documentation, this is the overview:
Create a service account for your project
Delegate domain-wide access to the service account
Your application prepares to make authorized API calls using the service account's credentials. (This is regardless of how many users install & use your app)
That API call will request an access token from the OAuth 2.0 auth server.
Your application will then be able to use the access token to call Google APIs (which in your case uses Gmail API).
I need to extract information from videos using YouTube Analytics and Reporting Api.
I have access to multiple YouTube Brand Accounts, when I log into YouTube with my Google Account.
Using the "Try it" for testing the API, I'm only able to retrieve data for a channel once I switch to the Brand Account that this channel belongs, otherwise I get 403 - Forbidden error.
Is there any way to extract data using the Google Account that I'm using to log in? Because once I create the credentials in developers console, they will be associated to the Google Account and not to the Brand Accounts.
My google account has Manager Role on the brand accounts.
I've search for the onBehalfOfContentOwner field to be used in requests, but I don't know how to get this ID, and I'm not sure if this is applicable in my situations, since we're talking about Brand Accounts, correct me if I'm wrong.
I fought with this just two days ago. Turns out it IS possible, it's just undocumented and works a bit differently than you'd expect:
Once I create the credentials in developers console, they will be associated to the Google Account and not to the Brand Accounts.
I had the same exact misconception when I first tried (even went so far as to find out the brand account's client_id). Turns out you don't want to use the brand's oauth info -- you want to use your own client_id/client_secret to create a refresh token on behalf of the brand account then use that to create auth tokens.
Steps:
Using your main account create an oauth client_id and client_secret via https://console.developers.google.com/apis/credentials
Edit the client_id/client_secret entry you just added and add "https://developers.google.com/oauthplayground" to the "Authorized redirect URIs" at the bottom of the page.
We're going to create a refresh token the lazy way. Go to https://developers.google.com/oauthplayground/
Click the gears on the top right corner and set access type to "offline", then click "Use your own OAuth credentials" and enter the client_id and client_secret you created in step 1.
Select the scopes you want to give it access to. Click authorize APIs.
Here's the magic bit: You'll now be asked to "Choose an account". Choose the brand account you want to access here, NOT your main account. Since you have permission to access it this'll work fine even though you're using your own client_id and client_secret
Allow the permission access when it prompts you, then you'll be brought back to the oauth playground.
Click "Exchange authorization code for tokens"
Grab the refresh token and use it like normal to generate auth tokens as needed.
Congratulations, you now have api access to the brand account!
Hope that helps.
The YouTube API is different then other google APIs. With other APIs you authenticate access to the full account. However with the YouTube API its channel based. You are going to need to authenticate your application once for each channel.
onBehalfOfContentOwner
This parameter is intended for YouTube content partners that own and
manage many different YouTube channels. It allows content owners to
authenticate once and get access to all their video and channel data,
without having to provide authentication credentials for each
individual channel. The actual CMS account that the user authenticates
with needs to be linked to the specified YouTube content owner.
You need to be a YouTube partner then you can contact your account manager and get a CMS id. I have yet to figure out what magic one must archive to become a YouTube partner.
I will give an update to #Paolo's incredible answer. In my case, I was trying to get my private videos using the Playlist.list api. I've never seen an api as poorly documented, asinine, and CONVOLUTED as youtube's api.
Context: I have a main google account for which my youtube api credentials are tied to (there is no google developer accounts for youtube brand accounts) but would like to get the private playlists (and videos) for my youtube account (a brand account). mine=true, key, channelId, onBehalfOfContentOwner, and onBehalfOfContentOwnerChannel all did NOTHING for me. I was getting either public playlists or api errors with various combinations and values of those parameters.
In the end, these were the steps I took to run a node script to get private videos from my brand account:
Go to https://console.developers.google.com/ for your main google account.
In the sidebar, go to APIs & Services, then Credentials
At the top, click +Create Credentials, then Service account
Under Service account details, enter any name, then click Create and Continue
Under "Grand this service account access to project", click continue
Under "Grant users access to this service account", click Done
On the main credentials page that loads, click the newly created service account under Service Accounts
In the tabs, click Keys
Click the Add Key button, then Create new key
Keep JSON, then click create
Save the file as client-key.json in the root of your nodejs project
Go to https://developers.google.com/oauthplayground
Scroll to bottom of scopes and select YouTube Data API v3 v3, then https://www.googleapis.com/auth/youtube and https://www.googleapis.com/auth/youtube.readonly.
In the window that pops up, click your youtube (brand) account, then allow
In the next step, click Exchange authorization code for tokens
Copy the access token
Go back to your node script and use like this:
const auth = new google.auth.GoogleAuth({
keyFile: "client-key.json",
scopes: [
"https://www.googleapis.com/auth/youtube",
"https://www.googleapis.com/auth/youtube.force-ssl",
"https://www.googleapis.com/auth/youtube.readonly",
"https://www.googleapis.com/auth/youtubepartner",
"https://www.googleapis.com/auth/youtubepartner-channel-audit",
],
})
const authClient = await auth.getClient()
google.options({ auth: authClient })
const youtube = google.youtube("v3")
const token = "your token here"
const results = await youtube.playlists.list({
part: [
"snippet",
"id",
"contentDetails",
"status",
"localizations",
"status",
],
mine: true,
auth: token,
oauth_token: token,
maxResults: 50,
})
Note mine: true and that the token must be passed to BOTH auth and oauth_token, but not key. If either parameter is missing, the call will fail. (Why? No clue. Please tell me.) Also, you must continuously renew your access token in the playground after it expires.
Now, with all of this said, I encourage you to find me an api worse than the youtube api. My guess is you'll be hard-pressed to find one even half as ridiculous as this.
P.S.
I believe there were additional things required before this such as enabling the youtube api and doing something on the OAUTH Consent Screen but I'm too exhausted with this thing to continue. Hopefully the Google console UX will be enough to guide you through those steps, though quite frankly, I doubt it.
Hope this helps and good luck, because you may actually need it.
If you follow the solution for getting a permanent refresh token and use Java, this works for me
GoogleCredential credential = new GoogleCredential.Builder().setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setClientSecrets(oauth2ClientId, oauth2ClientSecret)
.build()
.setRefreshToken(oauth2RefreshToken);
this.youTubeClient = new YouTube.Builder(httpTransport, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
Required dependencies
<dependency>
<groupId>com.google.apis</groupId>
<artifactId>google-api-services-youtube</artifactId>
<version>v3-rev212-1.25.0</version>
</dependency>
<dependency>
<groupId>com.google.auth</groupId>
<artifactId>google-auth-library-oauth2-http</artifactId>
<version>0.18.0</version>
</dependency>
These resources might also help once you have the refresh token:
Authenticate programmatically to Google with OAuth2
https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35
Current scenario:
Web App and Web API are authenticated using AAD B2C and working fine. Each customer has a unique tenant. OpenIdConnectAuthenticationOptions (web app) and OAuthBearerAuthenticationOptions (api) are set at the application Startup.
As described here: Token based authentication for both Web App and Web API using Azure AD B2C
Unfortunately both Web app and API have to be deployed for each and every customer to keep them separated.
Requirement:
Use same Web app and API for multiple customers rather than deploying them for every customer. Each customer will have a different URL for the web application.
Question 1 (Web App): How to redirect (for authentication) users to the correct tenant based on the request URL?
i.e. Set OpenIdConnectAuthenticationOptions (tenant, applicationId, signInPolicy etc.) from the database (or memory) on the fly based on the Request.Url rather than at application Startup.
Question 2 (API): How to validate the received token using the correct tenant?
i.e. Set OAuthBearerAuthenticationOptions configurations on the fly based on the received token clientId rather than at the application Startup
There are a couple of things within this that will not work based on what I think are assumptions.
Firstly, B2C has no concept of multiple "tenants". B2C is essentially
a Directory within your tenant and cannot be separated further. You
can have multiple B2C Directories within your tenant, say for each
customer, but you cannot have multiple segregated customers within a
single B2C Directory.
Secondly, if you did have your application connected to multiple B2C
directories, you would need to manage then connectors, App ID's, keys
etc for each one. It is then up to your application to work out which
one to use based on some data (URL accessed etc.).
There are also a couple of questions I would ask around how are you on-boarding users ? Can they register themselves ? Do they have their own user stores ?
If you really want to keep user accounts separate for each organisation (and they don't have a SAML / OIDC identity provider already) then I would do the following:
Create a B2C instance for each customer
Create a B2C instance for your application / web API
Use Custom Policies to connect your application B2C instance with your customer B2C instances (making sure to use the <Domain> element for the claims provider in the XML)
Use domain hints within your application to force the application to chose the correct B2C instance (see this: http://www.cloudidentity.com/blog/2014/11/17/skipping-the-home-realm-discovery-page-in-azure-ad/)
In this way your application only needs to trust and maintain the single B2C directory, and if you do have customers who have their own OIDC or SAML endpoints they become a claims provider within the directory, rather than a separate B2C instance.
Per #ManishJoisar's request this is how I resolved my issue long ago. This is an old .net framework 4.8 code sample.
Please note that B2C v2 policies provide better implementation with federation through custom policies these days.
public void ConfigureAuth(IAppBuilder app)
{
int index = 2000;
foreach (var record in SystemConfig.ApiToWebUrl)
{
app.MapWhen(
context => System.Web.HttpContext.Current.Request.Url.BaseURL() == record.Key,
config =>
{
var customer = SystemConfig.GetWebSettings(true)[record.Value];
config.UseOAuthBearerAuthentication(CreateBearerOptionsFromPolicy(index, customer.B2CSignInPolicyId,
customer.B2CAadInstance, customer.B2CTenant, customer.B2CApplicationId));
}
);
index++;
}
}
public OAuthBearerAuthenticationOptions CreateBearerOptionsFromPolicy(int index, string b2cPlicyName,
string b2cInstance, string b2cTenant, string b2cClientId)
{
TokenValidationParameters tvps = new TokenValidationParameters
{
// This is where you specify that your API only accepts tokens from its own clients
ValidAudience = b2cClientId,
AuthenticationType = string.Format("{0}_{1}", b2cPlicyName, index)
};
var aadInstance = string.Format("https://{0}{1}", b2cTenant.Split('.')[0], ".b2clogin.com/{0}/{1}/v2.0/.well-known/openid-configuration");
var config = String.Format(aadInstance, b2cTenant, b2cPlicyName);
return new OAuthBearerAuthenticationOptions
{
// This SecurityTokenProvider fetches the Azure AD B2C metadata & signing keys from the OpenIDConnect metadata endpoint
AccessTokenFormat = new JwtFormat(tvps, new OpenIdConnectCachingSecurityTokenProvider(config)),
Provider = new CustomOAuthBearerProvider()
};
}
I had implemented spring controller for fetching all the information about my friend list. The implementation are as follows:
#RequestMapping(value = "/getFbContactList/{accessToken}", method = RequestMethod.GET,produces = MediaType.APPLICATION_JSON_VALUE)
public String fbContactList(#PathVariable String accessToken) {
//accessToken recieved from facebook after OAuth authorization
Facebook facebook = new FacebookTemplate(accessToken);
System.out.println("User's FaceBook Profile Id::"+facebook.userOperations().getUserProfile().getId());
System.out.println("User's FaceBook UserName::"+facebook.userOperations().getUserProfile().getName());
System.out.println("User's FaceBook UserEmail::"+facebook.userOperations().getUserProfile().getEmail());
List<User> friends = facebook.friendOperations().getFriendProfiles();
for(User user : friends) {
System.out.println("User Email::" + user.getEmail());
}
return friends.toString();
}
The problem is that i am able to fetch my own information like Profile Id, User Name, Email,DOB but when i am printing the information about my friend's list it is not showing anything. It is having NULL values.
Can anyone give me some pointers where i am missing something.
A lot of this will depend on your friends' privacy settings and whether or not those friends have also used/authorized your app.
Your friends will not even be in the results from getFriendProfiles() if they have not themselves authorized your app to access their profiles. Before Facebook Graph API v2.0, you could fetch all of a user's friends, but that changed and now you only get friends who are also using the same app.
Once you get those friends, there are some fields that the app simply won't have access to no matter what privacy settings are in place. I don't recall off hand, but I believe email may be one of those. Further, if one of your friends has locked down their privacy settings, you won't be able to see many other fields as well. For cases where your app doesn't have authority to see a field, it will return null.
On the other hand...if you were to use a valid access token and request https://graph.facebook.com/me/friends?access_token={{YOUR TOKEN}} in your browser or using curl or some other client and you actually see your friends' data, then that very well could be a bug in Spring Social Facebook. But if you don't see that data, then there's no way that SSFB can get that data for you.
When Facebook gives a user a newer updated token and we already have a userconnection record for that user, does a new userconnection record get created or does the userconnection record for that user just get updated with the new token?
Basically, what is happening here is that each device they use creates a new different Facebook token, than what is currently stored in Spring Social's userconnect db table. And it is therefore calling the ConnectionSignUp implementation we have written, which right now creates a new Our App User for our application, instead of seeing that it is the same Facebook user and use the same App internal User in our application.
AccountSecurity accountSecurity = accountService.newUserGenerator(newUserName, username, userProfile.getFirstName(),
userProfile.getLastName(), userProfile.getEmail())
Update 12-5-14 4:09 PM PST: It seems that yes, the two different "devices" will get different access tokens. Spring Social will just use the providerid and userproviderid and "see" that they are the same account and not create a new user by calling a ConnectionSignUp execute() method in that case.
However, it seems that Facebook is not sending back the same userproviderid when it should considering it is the exact same app (Flash versus iOS app, but defined as one app in Facebook) and the exact same user account (mine). Facebook should send the same userproviderid.