I am trying to get the user email, that I used to log in with when performing Oauth2 through Youtube in my application. The code is similar to this:
YouTube client = new YouTube.Builder(GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory
.getDefaultInstance(), credential)
.setApplicationName("app_name").build();
YouTube.Channels.List channelListByIdRequest = client.channels().list("snippet,contentDetails,statistics");
channelListByIdRequest.setMine(true);
ChannelListResponse channelListResponse = channelListByIdRequest.execute();
Here I pull the channel api, that, according to the doc is some kind of similar to the user api in v3. However, neither in Channel nor in any other API I cannot find how to get the email I logged in with. How can that information be accessed?
I have done some research and, unfortunately, there is indeed no possibility to fetch the email. As far as I understand, it is against the concept of Youtube and its data API - it deals with channels and multiple of those can be associated with a single email.
Used this for reference:
https://developers.google.com/youtube/v3/getting-started
However, you can use Google plus API scopes along with Youtube scopes to fetch the user info (but we need to take into account that it will be discarded by March 7, 2019)
GoogleNetHttpTransport.newTrustedTransport(), JacksonFactory.getDefaultInstance(), clientSecrets,
Arrays.asList(YouTubeScopes.YOUTUBE_FORCE_SSL, PlusScopes.PLUS_ME, PlusScopes.USERINFO_EMAIL))
.setAccessType("offline").setApprovalPrompt("force").build();
We will be prompted to select email and channel in Oauth window and will be able to fetch the info.
Related
I am working on Google Sheets <-> Salesforce integration and developing it in Salesforce programming language - Apex on Force.com platform.
Currently I am attempting to connect to Google Sheets API. I am using Service Account Key, so Salesforce can pull the data from Google Sheets without the requirement for manual authorisation every time it sends out a query.
I am at the point where I set up the Service Account Key and I am successfully sending a request to it to obtain the access_code.
Then I am attempting to query the API, using the following class:
/****** API CALLOUT *******/
public static HttpResponse googleSheetsCallout (){
//the below line provides a string containing access token to google
string accessCode = getAccessToken();
//I found this endpoint structure online, this may be why my script
//isn't working. However, I am struggling to find the alternative.
string endpoint = 'https://sheets.googleapis.com/v4/spreadsheets/params=[SPREADSHEET ID GOES HERE]/values/[RANGE GOES HERE]?access_token=';
httpRequest req = new httpRequest();
req.setEndpoint(endpoint+accessCode);
req.setMethod('GET');
req.setTimeout(120000);
httpResponse res = new http().send(req);
System.debug ('res is ' +res);
return res;
}
When I run the function this is what the log returns:
|CALLOUT_RESPONSE|[71]|System.HttpResponse[Status=Forbidden, StatusCode=403]
|USER_DEBUG|[72]|DEBUG|res is System.HttpResponse[Status=Forbidden, StatusCode=403]
I enabled Google Sheets access in the google developer console menu, and what's interesting is when loking at the console it appears that Google notices API requests being sent out (they are appearing on the activity chart).
I solved it, and the issue was not the code itself.
The problem was sharing my sheet. To allow read/edit access to your sheet from the service account it must be shared with the Service Account ID email address, the same way it's shared with any other user. If this isn't done the script will produce 403 error.
I'm currently working with Google's Games API. The client sends through a user's authorization code, alongside their GPlay ID.
I'm sending this off to validate with Google, with;
var tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
client_id,
client_secret,
idTokenString,
"")
.execute()
Where the client_id and client_secret are retrieved from our client_secret as retrieved from Google, and the idTokenString is the authorization code as provided by the user logging in to the client (format: 4/xxxxx..).
After retrieving the tokenResponse, the following will return the access token without issue;
var accessToken = tokenResponse.getAccessToken()
However, the idToken as retrieved from:
var idToken = tokenResponse.getIdToken()
returns with null. As such, attempting to get the user's data to validate they're the legitimate owner of the account with;
var idToken = tokenResponse.parseIdToken()
will return a nullpointer exception.
From googling on the topic, some users seem to think that the parseIdToken method is no longer in use, and that only the accessToken can be used to retrieve such information.
However, any solutions I've found have all required use of the getIdToken, which is also returning with null.
Does anyone have any ideas on what I may be doing wrong here, or if there's another expected method for retrieving the user's details after login?
It might be because getIdToken() is in BETA.
As stated in the documentation - Google API Client Library for Java:
Features marked with #Beta at the class or method level are subject to change. They might be modified or removed in any major release. Do not use beta features if your code is a library itself (that is, if your code is used on the CLASSPATH of users outside your control).
You might want to use getIdToken from GoogleSignInAccount as a workaround.
You can check the release notes for any update.
Hope this helps.
My Guidelines
If followed this Google documentation about verifying Google-Account-Tokens on the server side, but I am kinda confused.
My Problem
GoogleIdTokenVerifier googleIdTokenVerifier = new GoogleIdTokenVerifier.Builder(new NetHttpTransport(), new JacksonFactory())
.setAudience(Collections.singletonList(CLIENT_ID))
.build();
In this piece of code I figured out that the transport and jsonFactory arguments can be filled as new NetHttpTransport() and new JacksonFactory() here. It also describes how to get AudienceString, but I couldn't figure out what it is for. I couldn't test it, but my question is if I can use it without .setAudience() or if I need it and what it is for.
In .setAudience() you have to pass all client ID's. You can get the ID for your client from the Credentials Page. It's explained here.
Thanks to #StevenSoneff.
If you didn't get the basic concept
For every client you want your server to accept, you need to create a project in the `Developer Console`. Clients are differentiated by their `SHA-1` fingerprint. You can for example have a debug project (will take your debug fingerprint) and a release one. To make both work, you have to add both `ID`'s to your server's `GoogleIdTokenVerifier`'s `.setAudience()`.
In my case, If you're using Firebase to get the id token on Android or iOS. You should follow these instructions to verify it on your backend server.
Verify ID tokens using a third-party JWT library
For me, I'm using Google OAuth Client as the third-party library so it's easy to use.
But it's a little bit different from this document.
Verify the Google ID token on your server side
The CLIENT_ID is your firebase project ID.
The Issuer has to be set as https://securetoken.google.com/<projectId>.
You need to use GooglePublicKeysManager and call setPublicCertsEncodedUrl to set it as https://www.googleapis.com/robot/v1/metadata/x509/securetoken#system.gserviceaccount.com
GooglePublicKeysManager manager = new GooglePublicKeysManager.Builder(HTTP_TRANSPORT, JSON_FACTORY)
.setPublicCertsEncodedUrl(PUBLIC_KEY_URL)
.build();
GoogleIdTokenVerifier verifier = new GoogleIdTokenVerifier.Builder(manager)
.setAudience(Collections.singletonList(FIREBASE_PROJECT_ID))
.setIssuer(ISSUER)
.build();
If you have multiple issuers, then you have to create GoogleIdTokenVerifier for each one.
I wanted to access some google api services:
GDrive API
Contact API
People API
And I'm struggeling with the oauth2 impersonate service account flow (you know that one: Google Oauth v2 - service account description. For impersonification you need to apply the "Delegating domain-wide authority" in the google apps console, download the correspoding pk12 file and activate the api in a google console project.
At the moment I always get:
com.google.api.client.auth.oauth2.TokenResponseException: 401 Unauthorized
at com.google.api.client.auth.oauth2.TokenResponseException.from(TokenResponseException.java:105)
at com.google.api.client.auth.oauth2.TokenRequest.executeUnparsed(TokenRequest.java:287)
at com.google.api.client.auth.oauth2.TokenRequest.execute(TokenRequest.java:307)
at com.google.api.client.googleapis.auth.oauth2.GoogleCredential.executeRefreshToken(GoogleCredential.java:384)
at com.google.api.client.auth.oauth2.Credential.refreshToken(Credential.java:489)
at oauthsample.GDriveAPI.<init>(GDriveAPI.java:50)
at oauthsample.GDriveAPI.main(GDriveAPI.java:85)
Here is my code:
HttpTransport httpTransport = new NetHttpTransport();
JacksonFactory jsonFactory = new JacksonFactory();
Set<String> scopes = new HashSet<String>();
scopes.add("https://www.google.com/m8/feeds");
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId("myserviceuser#xxxxxx.iam.account.com")
.setServiceAccountPrivateKeyFromP12File(new File("somep12key.p12"))
.setServiceAccountScopes(scopes)
.setServiceAccountUser("my_user_name#gmail.com")
.build();
credential.refreshToken();
ContactsService service = new ContactsService("MYAPP");
service.getRequestFactory().setHeader("User-Agent", "MYAPP");
service.setHeader("GData-Version", "3.0");
service.setOAuth2Credentials(credential);
URL feedUrl = new URL("https://www.google.com/m8/feeds/contacts/default/full");
ContactFeed resultFeed = service.getFeed(feedUrl, ContactFeed.class);
I also searched heavily through stackoverflow (can't list all references and checked the responses and solutions). But one question was never clearly answered - nor in googles documentaiont nor on all the stackoverflow posts:
Is it realy possible to impersonate a serviceaccount with a normal user#gmail.com user (I mean a normal gmail account with no access to the mentioned admin console in the chapter "Delegating domain-wide authority to the service account" and withouth having a own domain )?
Some say yes, some say no. So what's the absolute truth?
As far as I understand when reading the google docs: The service account can only impersonate on users when you in charge of a own domain and you need to have a google work account with your own domain registered. Then you're able to access the admin console and can grant access to the service account.
Thanks for your patience and for your time to answer.
Best regards
Matt
The short answer is no, it's not possible to perform service-account impersonate of a #gmail.com account. The key reason is that although the service account OAuth flow doesn't involve an authorization screen, at the end of the day someone must still say "I authorize this application to impersonate this user."
In the case of a Google Apps domain that person is the domain administrator, who has the authority to approve apps for all users in the domain. For an #gmail.com account, there is no other authority that can approve this on your behalf. And if you have to ask the user for authorization anyway, they it just makes sense to use the regular 3-legged OAuth flow to prompt the user for authorization, get a refresh token, etc.
Now for a while there was a trick where you could take an #gmail.com user through the regular 3-legged flow, and once they approved it use the service account flow from then on. This lead to some strange problems however, so we've disabled that option. This may be why there was disagreement in the past about if this is possible.
My goal is to upload videos that are uploaded to my web server to Youtube on my own channel, not the users' Youtube account (my web server is acting as a proxy).
I found the sample code for uploading video to Youtube here with the credential acquired this way. The problem that I have with this sample is that it writes to disk the credential, and it opens an http server. Since my web server can potentially have a lot of users uploading their videos concurrently, the credential file location has to be dynamic, and multiple binding to the same http port is not possible. Further more, after searching through other writing about uploading to Youtube, I think this approach is for users uploading to their Youtube account.
Could you share your experiences/code sample/solutions for my scenario? In short I am just trying to automate the process of me opening up Youtube dashboard, and uploading videos to a channel in my Youtube.
In general, starting at API V3, Google prefers OAuth2 over other mechanism, and uploading a video (or any other action that modifies user data) requires OAuth2.
Fortunately, there is a special kind of token called refresh token to the rescue. Refresh token does not expire like normal access token, and is used to generate normal access token when needed. So, I divided my application into 2 parts:
The 1st part is for generating refresh token, which is a Java desktop app, meant to be run by a user on a computer. See here for sample code from Google.
The 2nd part is is part of my web application, which uses a given refresh token to create a credential object.
Here is my implementation in Scala, which you can adapt to Java version easily:
For generating a refresh token, you should set the accessType to offline for the authorization flow. Note: if a token already exists on your system, it won't try to get new token, even if it does not have refresh token, so you also have to set approval prompt to force:
def authorize(dataStoreName: String, clientId: String, clientSecret: String): Credential = {
val builder = new GoogleAuthorizationCodeFlow.Builder(
HTTP_TRANSPORT,
JSON_FACTORY,
clientId,
clientSecret,
Seq(YouTubeScopes.YOUTUBE_UPLOAD)
)
val CREDENTIAL_DIRECTORY = s"${System.getProperty("user.home")}/.oauth-credentials"
val fileDataStoreFactory = new FileDataStoreFactory(new java.io.File(CREDENTIAL_DIRECTORY))
val dataStore: DataStore[StoredCredential] = fileDataStoreFactory.getDataStore(dataStoreName)
builder.setCredentialDataStore(dataStore).setAccessType("offline").setApprovalPrompt("force")
val flow = builder.build()
val localReceiver = new LocalServerReceiver.Builder().setPort(8000).build()
new AuthorizationCodeInstalledApp(flow, localReceiver).authorize("user")
}
val credential = authorize(dataStore, clientId, clientSecret)
val refreshToken = credential.getRefreshToken
For using the refresh token on the server, you can build a credential from a refresh token:
def getCredential = new GoogleCredential.Builder()
.setJsonFactory(JSON_FACTORY)
.setTransport(HTTP_TRANSPORT)
.setClientSecrets(clientId, clientSecret)
.build()
.setRefreshToken(refreshToken)
I have have bypassed the whole AuthorizationCodeInstalledApp authorize() method and created a new subclass which bypasses the jetty server implementation process.
The methods are as follows
getAuthorizationFromStorage : Get access token from stored credentials.
getAuthorizationFromGoogle : Get the authentication with the credentials from Google creates the url that will lead the user to the authentication page and creating a custom defined name-value pair in the state parameter. The value should be encoded with base64 encoder so we can receive the same code redirected from google after authentication.
saveAuthorizationFromGoogle : Save the credentials that we get from google.
Create the GoogleAuthorizationCodeFlow object from the credentialDatastorfrom the response received from the google after authentication.
Hit google to get the permanent refresh-token that can be used to get the accesstoken of the user any time .
Store the tokens like accesstoken and refreshtoken in the filename as userid
Checkout the code Implementation here