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.
Related
I want to create a lifecycle rule or lifecycle management policy for a specific azure storage account through java code (not through terraform or azure portal). Any appropriate code snippet or reference will be helpful. Thanks in advance.
If you would like to manage the Azure Blob storage lifecycle, you could create it with these following methods.
Azure portal, Azure PowerShell, Azure CLI, REST APIs
So you could call this REST API to create lifecycle with Java code. You need to get access token, then call the API. See the sample code, note to change the HTTP request:
public class PublicClient {
/*tenant_id can be found from your azure portal. Login into azure portal and browse to active directory and choose the directory you want to use. Then click on Applications tab and at the bottom you should see "View EndPoints". In the endpoints, the tenant_id will show up like this in the endpoint url's: https://login.microsoftonline.com/{tenant_id} */
private final static String AUTHORITY = "https://login.microsoftonline.com/{tenant_id}";
public static void main(String args[]) throws Exception {
AuthenticationResult result = getAccessTokenFromUserCredentials();
System.out.println("Access Token - " + result.getAccessToken());
HttpClient client = new DefaultHttpClient();
/* replace {subscription_id} with your subscription id and {resourcegroupname} with the resource group name for which you want to list the VM's. */
HttpGet request = new HttpGet("https://management.azure.com/subscriptions/{subscription_id}/resourceGroups/{resourcegroupname}/providers/Microsoft.ClassicCompute/virtualMachines?api-version=2014-06-01");
request.addHeader("Authorization","Bearer " + result.getAccessToken());
HttpResponse response = client.execute(request);
BufferedReader rd = new BufferedReader (new InputStreamReader(response.getEntity().getContent()));
String line = "";
while ((line = rd.readLine()) != null)
{
System.out.println(line);
}
}
private static AuthenticationResult getAccessTokenFromUserCredentials() throws Exception {
AuthenticationContext context = null;
AuthenticationResult result = null;
ExecutorService service = null;
try {
service = Executors.newFixedThreadPool(1);
context = new AuthenticationContext(AUTHORITY, false, service);
/* Replace {client_id} with ApplicationID and {password} with password that were used to create Service Principal above. */
ClientCredential credential = new ClientCredential("{client_id}","{password}");
Future<AuthenticationResult> future = context.acquireToken("https://management.azure.com/", credential, null);
result = future.get();
} finally {
service.shutdown();
}
if (result == null) {
throw new ServiceUnavailableException("authentication result was null");
}
return result;
}
}
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;
}
I'm struggling with invoking GCP cloud functions via REST API using Java.
The steps that I've performed to do it were:
create a service account with role "Cloud Functions Invoker"
download JSON key file for the newly created service account
in my code, obtain an access token using the following method:
private String getAuthToken() {
File credentialsPath = new File(PATH_TO_JSON_KEY_FILE);
GoogleCredentials credentials;
try (FileInputStream serviceAccountStream = new FileInputStream(credentialsPath)) {
credentials = ServiceAccountCredentials.fromStream(serviceAccountStream);
return credentials
.createScoped(Lists.newArrayList("https://www.googleapis.com/auth/cloud-platform"))
.refreshAccessToken()
.getTokenValue();
} catch (IOException e) {
throw new RuntimeException("Action could not be performed");
}
}
perform a REST call, using the created token:
public <Payload, Response> ResponseEntity<Response> callCloudFunction(
String endpoint,
Payload payload,
Class<Response> klazz
) {
RestTemplate restTemplate = new RestTemplate();
HttpHeaders headers = new HttpHeaders();
headers.setAccept(Collections.singletonList(MediaType.APPLICATION_JSON));
headers.setContentType(MediaType.APPLICATION_JSON);
String url = gCloudUrl + endpoint;
String token = getAuthToken();
String payloadString = null;
if (payload != null) {
try {
ObjectMapper objectMapper = new ObjectMapper();
payloadString = objectMapper.writeValueAsString(payload);
} catch (JsonProcessingException e) {
System.out.println(e.getMessage());
throw new RuntimeException("Could not perform action");
}
}
headers.add("Authorization", String.format("Bearer %s", token));
HttpEntity<String> entity = new HttpEntity<>(payloadString, headers);
return restTemplate.exchange(url, HttpMethod.POST, entity, klazz);
}
The implementation looks fine, but in response I'm getting 401 Unauthorized.
Unfortunately, GCP documentation is not really helpful. I think I've searched through all the possible places.
First of all, agree, it's not clear...
Then, you have to know (and it's not clear again) that you need an access token to call Google Cloud API, but and identity token to call IAP (on App Engine for example) or private Cloud Function and Cloud Run. And this identity token need to be signed by Google.
And, as mentioned in the code, you need to have a service account on your computer, but I recommend you to avoid this on GCP, it's not required if you use default authentication (see my code, on your computer set the GOOGLE_APPLICATION_CREDENTIALS env var that points to the service account key file). The best way is to not use service account key file on your computer also, but it's not yet possible (that is a security issue IMO, and I'm discussing with Google on this...)
Anyway, here a code snippet which works in Java (nowhere in the documentation...)
String myUri = "https://path/to/url";
// You can use here your service account key file. But, on GCP you don't require a service account key file.
// However, on your computer, you require one because you need and identity token and you can generate it with your user account (long story... I'm still in discussion with Google about this point...)
Credentials credentials = GoogleCredentials.getApplicationDefault().createScoped("https://www.googleapis.com/auth/cloud-platform");
IdTokenCredentials idTokenCredentials = IdTokenCredentials.newBuilder()
.setIdTokenProvider((IdTokenProvider) credentials)
.setTargetAudience(myUri).build();
HttpRequestFactory factory = new NetHttpTransport().createRequestFactory(new HttpCredentialsAdapter(idTokenCredentials));
HttpRequest request = factory.buildGetRequest(new GenericUrl(myUri));
HttpResponse httpResponse = request.execute();
System.out.println(CharStreams.toString(new InputStreamReader(httpResponse.getContent(), Charsets.UTF_8)));
NOTE If you want to continue to use RestTemplate object and set manually your token, you can generate it like this
String token = ((IdTokenProvider) credentials).idTokenWithAudience(myUri, Collections.EMPTY_LIST).getTokenValue();
System.out.println(token);
I'm trying to run the following example
https://developers.google.com/identity/sign-in/web/server-side-flow#step_1_create_a_client_id_and_client_secret
Everything runs correctly until step 7. I get the following exception
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad Request
{
"error" : "redirect_uri_mismatch",
"error_description" : "Bad Request"
}
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.googleapis.auth.oauth2.GoogleAuthorizationCodeTokenRequest.execute(GoogleAuthorizationCodeTokenRequest.java:158)
at {package}.service.SecurityService.getProfile(SecurityService.java:55)
.....
My code looks as followed:
public Result getProfile(User auth){
Result result = new Result();
try {
// Set path to the Web application client_secret_*.json file you downloaded from the
// Google API Console: https://console.developers.google.com/apis/credentials
// You can also find your Web application client ID and client secret from the
// console and specify them directly when you create the GoogleAuthorizationCodeTokenRequest
// object.
String CLIENT_SECRET_FILE = "client_secret.json";
GoogleClientSecrets clientSecrets = loadSecret(CLIENT_SECRET_FILE);
GoogleTokenResponse tokenResponse =
new GoogleAuthorizationCodeTokenRequest(
new NetHttpTransport(),
JacksonFactory.getDefaultInstance(),
"https://www.googleapis.com/oauth2/v4/token",
clientSecrets.getDetails().getClientId(),
clientSecrets.getDetails().getClientSecret(),
auth.getCode(),"http://localhost:8080/api/security/googleAPICallback")
.execute();
String accessToken = tokenResponse.getAccessToken();
// Use access token to call API
//GoogleCredential credential = new GoogleCredential().setAccessToken(accessToken);
// Get profile info from ID token
GoogleIdToken idToken = tokenResponse.parseIdToken();
GoogleIdToken.Payload payload = idToken.getPayload();
auth.setAccessToken(accessToken);
auth.setUuid(payload.getSubject()); // Use this value as a key to identify a user.
auth.setEmail(payload.getEmail());
auth.setVerifiedEmail(payload.getEmailVerified());
auth.setName(String.valueOf(payload.get("name")));
auth.setPictureURL(String.valueOf(payload.get("picture")));
auth.setLocale(String.valueOf(payload.get("locale")));
auth.setFamilyName(String.valueOf(payload.get("family_name")));
auth.setGivenName(String.valueOf(payload.get("given_name")));
HashMap<String,Object> map = new HashMap<>();
Field[] fields = auth.getClass().getDeclaredFields();
for(Field field : fields){
field.setAccessible(true);
map.put(field.getName(), PropertyUtils.getSimpleProperty(field.getName(), field.getName()));
}
logger.info(auth.toString());
result.setCode(Result.OK);
result.setMessage("¡Exito!");
result.setVarious(false);
result.setData(map);
}catch (Exception e){
e.printStackTrace();
result.setCode(Result.BAD_REQUEST);
result.setMessage("¡No hay access_token!");
result.setVarious(false);
}
return result;
}
I already tried adding different endpoints from both a local an production server. Both links accept GET and POST methods and returned a "OK" json response. Also both links are already added in Google Console in the Authorized URI redirect form.
If I leave an empty string it throws and error saying It needs a redirect_uri, and I omit that space I throws and error saying it missing a scheme for my token.
Extra:
Every time I change something in Google Console, I re download my client-secret.json
I want to obtain a new "access token" based on the "refresh token" saved in database.
Here is the code I wrote:
GoogleCredential.Builder credentialBuilder = new GoogleCredential.Builder()
.setTransport(HTTP_TRANSPORT).setJsonFactory(JSON_FACTORY)
.setClientSecrets(CLIENT_ID, CLIENT_SECRET);
credentialBuilder.addRefreshListener(new MyCredentialRefreshListener());
credential = credentialBuilder.build();
credential.setRefreshToken("saved_refresh_token_from_database");
try {
credential.refreshToken();
} catch (IOException e) {
e.printStackTrace();
}
class MyCredentialRefreshListener implements CredentialRefreshListener {
public void onTokenResponse(Credential cr, TokenResponse tr) {
System.out.println("Credential was refreshed successfully.");
}
public void onTokenErrorResponse(Credential cr, TokenErrorResponse tr) {
System.out.println(tr);
}
}
I get this message:
com.google.api.client.auth.oauth2.TokenResponseException: 400 Bad
Request { "error" : "invalid_grant" }
I use the same CLIENT_ID, CLIENT_SECRET and "refresh token" in a php script and there I managed to get the new "access token" using "refresh token" when "access token" expired.
I wrote the code based on javadoc.google-oauth-java-client.
Any person here knows how to modify the code to obtain the new access token ?
Thanks in advance.
UPDATE: the problem was that I was saving in the database the refresh_token without doing a json_decode on it and it contained a "\" which is considered escaped character in JSON.
It looks like you may have found some out of date documentation. The JavaDoc you link is for version 1.8 of the client library. The current version is 1.12.
The client library authors recommend that you use GoogleAuthorizationCodeFlow to manage your OAuth credentials. It takes care of refreshes automatically. If you follow this path, the code will look something like this:
// Create the flow
AuthorizationCodeFlow authorizationCodeFlow = new GoogleAuthorizationCodeFlow.Builder(
new UrlFetchTransport(), new JacksonFactory(), CLIENT_ID, CLIENT_SECRET,
Collections.singleton(OAUTH_SCOPES))
.setAccessType("offline")
.setCredentialStore(new AppEngineCredentialStore())
.build();
// User Id: e.g. from session
Credential credential = authorizationCodeFlow.loadCredential(USER_ID);
// Make your API call. This example uses the Google+ API
// If the access token has expired, it will automatically refresh
Plus plus = new Plus(new UrlFetchTransport(), new JacksonFactory(), credential)
.activities().list("me", "public").execute();
if you get http status 400, and message "invalid_grant".
I think you should check your instance of HTTP_TRANSPORT, JSON_FACTORY, CLIENT_ID, CLIENT_SECRET.