Gmail API, get new access token after revoke - java

I'm developing application that provides access to gmail mailbox. I created new project and logged in with few email accounts. When I revoked access token through this endpoint: https://oauth2.googleapis.com/revoke, the number of accounts attached to my project didn't change.
When i try to select account again, google redirects me to uri I specified, but code within url is corrupted and I can't get new access token using code from this url.
Response i get using code from url:
{
"error": "invalid_grant",
"error_description": "Malformed auth code."
}
How can i completely log out from my app?
Credential authorize=getCredentials(gmailMailBox, emailProperties.getGoogleClientSecrets());
authorize.refreshToken();
Unirest.post(emailProperties.getRevokeTokenUrl(authorize.getAccessToken()));
getCredentials method:
Credential authorize = new GoogleCredential.Builder()
.setTransport(GoogleNetHttpTransport.newTrustedTransport())
.setJsonFactory(JacksonFactory.getDefaultInstance())
.setClientSecrets(clientSecrets)
.build()
.setAccessToken(gmailMailBox.getAccessToken())
.setRefreshToken(gmailMailBox.getRefreshToken());
private Credential getCredentials(GmailMailBox gmailMailBox, GoogleClientSecrets clientSecrets)
throws GeneralSecurityException, IOException {
return new GoogleCredential.Builder().setTransport(GoogleNetHttpTransport.newTrustedTransport())
.setJsonFactory(JacksonFactory.getDefaultInstance())
.setClientSecrets(clientSecrets)
.build()
.setAccessToken(gmailMailBox.getAccessToken())
.setRefreshToken(gmailMailBox.getRefreshToken());
}
public class GmailMailBox {
private String accessToken;
private String refreshToken;
private LocalDateTime expiresIn;
}
public GoogleClientSecrets getGoogleClientSecrets() {
GoogleClientSecrets clientSecrets = new GoogleClientSecrets();
GoogleClientSecrets.Details clientSecretsDetails = new GoogleClientSecrets.Details();
clientSecretsDetails.set("client_id", client_id);
clientSecretsDetails.set("project_id", project_id);
clientSecretsDetails.set("auth_uri", auth_uri);
clientSecretsDetails.set("token_uri", token_uri);
clientSecretsDetails.set("auth_provider_x509_cert_url", auth_provider_x509_cert_url);
clientSecretsDetails.set("client_secret", client_secret);
clientSecrets.setWeb(clientSecretsDetails);
return clientSecrets;
}

Related

Unable to have concurrent log-ins with Google auth

I have a web app where users have to authenticate using Google sign-in. I do this because I need to grab their email address. When they fill out the fields on the page, all that data is stored in a google sheet alongside their email address (for auditing purposes incase something is askew with the data). Unfortunately what's happening is that if user A signs in, and does some work and at the same time user B logs in, when user A submits data, they will be submitting user B's email address (as does user B). In short, the latest person to log in, that email address is used. There is no database and I'm not storing any cookies. When they refresh the page, they have to re-authenticate. I am using Angular 7 and Java. Here is the code that I used:
ngOnInit() {
gapi.load('auth2', () => {
this.auth2 = gapi.auth2.init({
client_id: 'CLIENT_ID_HERE',
// Scopes to request in addition to 'profile' and 'email'
scope: 'https://www.googleapis.com/auth/spreadsheets'
});
});
}
signInWithGoogle(): void {
this.auth2.grantOfflineAccess().then((authResult) => {
this.authCode = authResult['code'];
this.fetchData();
});
}
authCode is bound to the child component so it can be passed as query param to the java code for google auth.
this.seriesService.submitSeriesData(matchList, this.authToken).subscribe(res => {.....);
The google auth java code is so:
private static final String APPLICATION_NAME = "Google Sheets API Java";
private static final JsonFactory JSON_FACTORY = JacksonFactory.getDefaultInstance();
private static final List<String> SCOPES = Collections.singletonList(SheetsScopes.SPREADSHEETS);
private static final String CLIENT_SECRET_DIR = "/client_secret.json";
private static GoogleTokenResponse tokenResponse = null;
public static String getEmailAddress() throws IOException {
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
String email = payload.getEmail();
return email;
}
public static Sheets getSheetsService1(String token, String redirectUri) throws IOException, GeneralSecurityException {
// Exchange auth code for access token
InputStream in = GoogleAuthUtil.class.getResourceAsStream(CLIENT_SECRET_DIR);
GoogleClientSecrets clientSecrets = GoogleClientSecrets.load(JSON_FACTORY, new InputStreamReader(in));
tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
token,
redirectUri)
.execute();
String accessToken = tokenResponse.getAccessToken();
GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
Sheets service = new Sheets.Builder(new NetHttpTransport(), JacksonFactory.getDefaultInstance(), credential)
.setApplicationName("MY APP HERE")
.build();
return service;
}
And the endpoint:
#RequestMapping(value="series/data", method = RequestMethod.POST, consumes="application/json")
public boolean submitSeriesMatchData(#RequestBody(required=true) SubmitStatsDto request) throws IOException, GeneralSecurityException, Exception {
if (service == null) {
service = GoogleAuthUtil.getSheetsService1(request.getToken(), this.redirectUri);
}
......
}
1) User clicks on the google sign in button
2) They select email and auth with google
3) I receive an auth code back and store it in ng.
4) Every REST call is passed said token to auth with google, and every endpoint calls getSheetsService1 which authenticates w/ token. (multiple endpoints, I only showed one above)
5) I get email from that tokenResponse.
Any ideas? This site will not have a database/users/local logins. Thank you.

Google Reseller API getting GoogleJsonResponseException: 403 Forbidden

I'm working on a project that uses the Google Apps Reseller API (Found here).
I'm running into a 403 Forbidden Exception.
Code (most of it origins from the Google Codelab Example Here:
try {
try {
Reseller service = GoogleResellerApiUtil.getResellerService();
Customer customerRecord = service.customers().get("acme.com").execute(); //crashes here
// "acme.com" is also used in the example from Google
System.out.println(customerRecord.toString());
} catch (GoogleJsonResponseException e) {
e.printStackTrace();
}
And this is the class I use to connect to the API.
I've provided a p12 file and it uses the service account, when calling the API it is impersonating one of the super admins, so it should be allowed to make all the calls.
At the moment I'm only using the read-only scope.
public class GoogleResellerApiUtil {
/** HTTP_TRANSPORT */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
/** JSON Factory*/
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Service Account Email */
public static final String SERVICE_ACCOUNT_EMAIL = "****#appspot.gserviceaccount.com";
/** P12 File Location */
public static final String PRIVATE_KEY_FILE = "WEB-INF/key.p12";
/** Reseller Admin Account to impersonate */
public static final String RESELLER_ADMIN = "**.**#**.com";
/** Scopes */
public static final List<String> SCOPES = Arrays.asList(ResellerScopes.APPS_ORDER_READONLY);
/** Application name. */
private static final String APPLICATION_NAME = "**-subscription-portal";
/** Logger */
private final static Logger LOGGER =
Logger.getLogger(GoogleResellerApiUtil.class.getName());
public static GoogleCredential getCredentials() throws IOException {
GoogleCredential credentials = null;
try {
credentials = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountId(SERVICE_ACCOUNT_EMAIL)
.setServiceAccountScopes(SCOPES)
.setServiceAccountUser(RESELLER_ADMIN)
.setServiceAccountPrivateKeyFromP12File(new File(PRIVATE_KEY_FILE))
.build();
} catch (GeneralSecurityException e) {
e.printStackTrace();
}
System.out.println("credential has been build, returning credential "); //this gets printed, so I think the credentials are valid?
return credentials;
}
/**
* Build and return an authorized Reseller client service.
* #return an authorized Reseller client service
* #throws IOException
*/
public static Reseller getResellerService() throws Exception {
Credential credential = getCredentials();
return new Reseller.Builder(
HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName(APPLICATION_NAME)
.build();
}
}
But I get the following error message when making the call:
com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 OK
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
at com.google.api.client.googleapis.json.GoogleJsonResponseException.from(GoogleJsonResponseException.java:146)
etc. etc. etc.
It is noted in Reseller API: Manage Subscriptions that
Note: If the customerAuthToken is not valid or has expired, the API response returns a 403 "Forbidden" error.
To solve the issue, please make sure that requests must be authorized by an authenticated user who has access to the data. As also noted in Reseller API: Authorizing
Note: The user granting permission for the Reseller API must be a domain super administrator.
In addition to that, it was suggested in Token expiration that you write your code to anticipate the possibility that a granted token might no longer work. A token might stop working for one of these reasons:
The user has revoked access.
The token has not been used for six months.
The user changed passwords and the token contains Gmail scopes.
The user account has exceeded a certain number of token requests.
Hope that helps!

Invalid channel Id return by youtube service api

I am trying to get a list of my youtube channels from a java app using the com.google.api.services.youtube.YouTube class. .
First of all I have enabled the Service Account credentials (https://console.developers.google.com > Credentials ) and I have enabled the following apis :
-YouTube Analytics API
-YouTube Data API
-Analytics API
To make a call to the Youtube service I create a Credential object using the following code.
/** Global instance of the HTTP transport. */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
private static Credential authorize() throws Exception
{
List<String> scopes = new ArrayList<String>();
scopes.add("https://www.googleapis.com/auth/youtube");
scopes.add("https://www.googleapis.com/auth/yt-analytics.readonly");
scopes.add("https://www.googleapis.com/auth/youtube.readonly");
scopes.add("https://www.googleapis.com/auth/youtubepartner-channel-audit");
GoogleCredential credential = new GoogleCredential.Builder().setTransport(HTTP_TRANSPORT)
.setJsonFactory(JSON_FACTORY)
.setServiceAccountPrivateKeyFromP12File(new File("C://file.privatekey.p12"))
.setServiceAccountId("xxxx-yyyyyyyyyyyyyy#developer.gserviceaccount.com")
.setServiceAccountScopes(scopes)
.build();
return credential;
}
After that I call the Youtube service to get my channels
/** Global instance of the HTTP transport. */
private static final HttpTransport HTTP_TRANSPORT = new NetHttpTransport();
/** Global instance of the JSON factory. */
private static final JsonFactory JSON_FACTORY = new JacksonFactory();
/** Global instance of Youtube object to make general YouTube API requests. */
private static YouTube youtube;
/** Global instance of YoutubeAnalytics object to make analytic API requests. */
private static YouTubeAnalytics analytics;
public String getDefaultChannelId(){
try{
Credential credential = authorize();
// YouTube object used to make all non-analytic API requests.
youtube = new YouTube.Builder(HTTP_TRANSPORT, JSON_FACTORY, credential)
.setApplicationName("API Project")
.build();
YouTube.Channels.List channelRequest = youtube.channels().list("id,snippet");
channelRequest.setMine(true);
channelRequest.setMaxResults(50L);
channelRequest.setFields("items(id,snippet/title,contentDetails,status,kind,etag,auditDetails)");
ChannelListResponse channels = channelRequest.execute();
System.out.println(channels.getItems());
// List of channels associated with user.
List<Channel> listOfChannels = channels.getItems();
// Grab default channel which is always the first item in the list.
Channel defaultChannel = listOfChannels.get(0);
String channelId = defaultChannel.getId();
return channelId;
}catch(Exception ex){
ex.printStacktrace();
}
}
The authorization code seems to work without any problem. The problem is with the getDefaultChannelId() method which returns a channel with id UC9i22sTxrX0IQk4AkT_Og3w .
I tried to navigate using the browser to my youtube channel using tha url : http://www.youtube.com/channel/UC9i22sTxrX0IQk4AkT_Og3w but the channel does not exist..
The line I used to print the channels results "System.out.println(channels.getItems());" displays the following json string.
[{"etag":"\"BDC7VThyM9nfoSQm1_kOyhtJTEw/yJvLzly7DMctrvFV5drOtgksadM\"","id":"UC9i22sTxrX0IQk4AkT_Og3w","kind":"youtube#channel","snippet":{"title":""}}
For some reason the youtube service does not return the right list of channels for the specific credential object.
But why????
You can not use Service Account with Youtube API : https://developers.google.com/youtube/v3/guides/moving_to_oauth#service_accounts. Youtube API is supposed to raise an error with service account but apparently it doesn't.
You should instead create a client ID of type Installed Application then retrieve a refresh token for your user (https://developers.google.com/accounts/docs/OAuth2InstalledApp) and use this token to create credentials.
Here is an example (store clientId, clientSecret, refreshToken and accessToken wherever you like, as soon as you keep it secret, token response should be cached) :
private String clientId;
private String clientSecret;
private String refreshToken;
private String accessToken;
private TokenResponse tokenResponse;
private Credential createCredential() {
final GoogleCredential.Builder builder = new GoogleCredential.Builder();
builder.setTransport(HTTP_TRANSPORT);
builder.setJsonFactory(JSON_FACTORY);
builder.setClientSecrets(clientId, clientSecret);
builder.addRefreshListener(new CredentialRefreshListener() {
#Override
public void onTokenResponse(final Credential credential,
final TokenResponse tokenResponse) throws IOException {
this.tokenResponse = tokenResponse;
}
#Override
public void onTokenErrorResponse(final Credential credential,
final TokenErrorResponse tokenErrorResponse)
throws IOException {
this.tokenResponse = null;
// trace error
}
});
return builder.build();
}
private YouTube getYoutube() {
final Credential credential = createCredential();
if (this.tokenResponse == null) {
credential.setAccessToken(this.accessToken);
credential.setRefreshToken(this.refreshToken);
} else {
credential.setFromTokenResponse(this.tokenResponse);
}
final YouTube youtube = new YouTube.Builder(HTTP_TRANSPORT,
JSON_FACTORY, credential).setApplicationName("*****").build();
return youtube;
}

Google endpoint: Access auth api from native client

I try to access a google endpoint service from a native application with OAuth 2.0. I managed to authenticate with GoogleAuthorizationCodeFlow and the JavaFX webview (as browser).
After a successfull authentication I try to access the api method but the User object is always null and the question is why?
Code for api method call:
GoogleAuthorizationCodeFlow flow = getGoogleAuthorizationCodeFlow();
Credential credential = flow.loadCredential(USER_ID);
Helloworld.Builder builder = new Helloworld.Builder(new NetHttpTransport(),
new JacksonFactory(), credential);
Helloworld service = builder.build();
Helloworld.Greetings.Authed protectedApiMethod = service.
greetings().authed();
HelloGreeting execute = protectedApiMethod.execute();
System.out.println("Response " + execute.getMessage());
Code for creating the flow object:
private static GoogleAuthorizationCodeFlow getGoogleAuthorizationCodeFlow() {
return new GoogleAuthorizationCodeFlow(new NetHttpTransport(),
new JacksonFactory(), INSTALLED_ID, CLIENT_SECRET, Arrays.asList(SCOPE_EMAIL));
}
Code where I try to authenticate:
GoogleAuthorizationCodeFlow flow = getGoogleAuthorizationCodeFlow();
GoogleAuthorizationCodeTokenRequest tokenRequest = flow.newTokenRequest(code);
tokenRequest.setRedirectUri(REDIRECT_URL);
try {
GoogleTokenResponse execute = tokenRequest.execute();
flow.createAndStoreCredential(execute, USER_ID);
Platform.exit();
} catch (IOException e) {
throw new RuntimeException(e);
}
}
Declaration of the Api method:
#ApiMethod(name = "greetings.authed",
path = "greeting/authed",
clientIds = {Constants.WEB_CLIENT_ID, Constants.INSTALLED_ID,
Constants.API_EXPLORER_CLIENT_ID})
public HelloGreeting authedGreeting(User user) {
if (user != null) {
HelloGreeting response = new HelloGreeting("hello " + user.getEmail());
return response;
} else {
HelloGreeting response = new HelloGreeting("no user object was specified");
return response;
}
}
The only response I get is "no user object was specified". Since I can call the method without any error I guess I'm authenticated correctly.
From the docs: https://developers.google.com/appengine/docs/java/endpoints/getstarted/backend/auth
If the request coming in from the client has a valid auth token or is
in the list of authorized clientIDs, the backend framework supplies a
valid User to the parameter. If the incoming request does not have a
valid auth token or if the client is not on the clientIDs whitelist,
the framework sets User to null
So, you have to manully catch the case, where a null user is supplied by the infrastructure. So to answer the above question: The request is invalid. And the mistake in the code is, that the CodeFlow object is recreated for the actual request but since no CredentialStore is set, the token is lost and cannot be resend.

How to get userinfo with google-api after succesfully been authenticated?

I try to get the userinfo after successfully authenticate with a gmail account (tok is a valid token):
GoogleCredential credential2 = new GoogleCredential.Builder()
.setTransport(TRANSPORT).setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.setRequestInitializer((new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request)
throws IOException {
request.getHeaders().setAuthorization("Bearer ".concat(tok));
}
}))
.build();
Oauth2 userInfoService = new Oauth2.Builder(TRANSPORT,
JSON_FACTORY, credential2.getRequestInitializer())
.setApplicationName(APPLICATION_NAME).build();
Userinfo userInfo = userInfoService.userinfo().get().execute();
logger.warn("User email: {}", userInfo.getEmail());
logger.warn("User gender: {}", userInfo.getGender());
logger.warn("User complet name: {} - {}", userInfo.getFamilyName(), userInfo.getName());
But logs display 'null' for all fields, the json data returned contains only the id:
{
"id": "113695880661351193041"
}
What i'm supposed to do ? Add a special scope to do this? I tried it several times without success, just by adding scope=https://www.googleapis.com/auth/userinfo.profile as url parameter, maybe that's wrong ?
Hope someone can help or know how to add scopes to my request and get the correct response from this service.
This works for me :
Credential credential = OAuth2Utils.newFlow().loadCredential(userId);
Oauth2 service = new Oauth2.Builder(OAuth2Utils.HTTP_TRANSPORT, OAuth2Utils.JSON_FACTORY, credential).setApplicationName("appname").build();
UserInfo userInfo = service.userinfo().get().execute();
These are some of the properties that are returned :
userInfo.getBirthday()
userInfo.getFamilyName()
userInfo.getGender()
userInfo.getGivenName()
userInfo.getHd()
userInfo.getLink()
The utility class OAuth2Utils referred in the code :
/** Global instance of the HTTP transport. */
public final static HttpTransport HTTP_TRANSPORT = new UrlFetchTransport();
/** Global instance of the JSON factory. */
public final static com.google.api.client.json.JsonFactory JSON_FACTORY = new com.google.api.client.json.jackson2.JacksonFactory();
public static GoogleAuthorizationCodeFlow newFlow() throws IOException {
return new GoogleAuthorizationCodeFlow.Builder(HTTP_TRANSPORT, JSON_FACTORY, getClientCredential(),
Arrays.asList(SCOPES)).setCredentialStore(new OAuth2CredentialStore()).setAccessType("offline")
.setApprovalPrompt("force").build();
}

Categories