Problems with Google API authentication - java

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

Related

Google Cloud Platform - cloud functions API - 401 Unauthorized

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);

Error while requesting an access token. No accessTokenResponse object recieved, maybe a non HTTP 200 received?

I was following this tutorial: https://developers.docusign.com/esign-rest-api/code-examples/config-and-auth
(I used java)
In the third step I dont know how to get the code that is sent back form DocuSign as query param in the redirect uri:
// Java request auth token
**String code = "{ENTER_AUTH_CODE_FROM_PREVIOUS_STEP}";**
// assign it to the token endpoint
apiClient.getTokenEndPoint().setCode(code);
// optionally register to get notified when a new token arrives
apiClient.registerAccessTokenListener(new AccessTokenListener() {
#Override
public void notify(BasicOAuthToken token) {
System.out.println("Got a fresh token: " + token.getAccessToken());
}
});
// following call exchanges the authorization code for an access code and updates
// the `Authorization: bearer <token>` header on the api client
apiClient.updateAccessToken();
I get an error saying the requested access token is null. Below is the error:
Error while requesting an access token. No accessTokenResponse object received, maybe a non HTTP 200 received?
Has anybody ever got the same one or could maybe someone tell me how to fix it.
I ran below code and its working fine for me, after getting code from the previous step in the URL:
public static void main(String[] args) {
String IntegratorKey = "[Your_Integrator_Key]";
String ClientSecret = "[Your_Secret_Key]";
String RedirectURI = "https://www.getpostman.com/oauth2/callback";//This REDIRECT_URI should match whats configured with IntegratorKey in your Sandbox account
String AuthServerUrl = "https://account-d.docusign.com";
String RestApiUrl = "https://demo.docusign.net/restapi";
ApiClient apiClient = new ApiClient(AuthServerUrl, "docusignAccessCode", IntegratorKey, ClientSecret);
apiClient.setBasePath(RestApiUrl);
apiClient.configureAuthorizationFlow(IntegratorKey, ClientSecret, RedirectURI);
Configuration.setDefaultApiClient(apiClient);
String code = "{ENTER_AUTH_CODE_FROM_PREVIOUS_STEP}";
apiClient.getTokenEndPoint().setCode(code);
apiClient.registerAccessTokenListener(new AccessTokenListener() {
#Override
public void notify(BasicOAuthToken token) {
System.out.println("Got a fresh token: " + token.getAccessToken());
}
});
apiClient.updateAccessToken();
}
To get the code returned by DocuSign from browser, you need to have a WEBApp to which DocuSign will redirect the browser, this same callback URL should be configured in your DS Sandbox's REDIRECT_URI, for instance if you WEBApp callback URL is http://locahost:8080/docusignapp/callback, then this should be added in REDIRECT_URI in IntegratorKey and same needs to be added when calling DS URL to authenticate a user. Once DS authenticates the user, it will redirect the browser to your APP url. On hitting your WEBApp then you need to read the URL and strip off the code part using Javascript, then run the step2 to get the access Token. A sample JS code to strip the code part is:
var vars = [], hash;
var hashes = window.location.href.slice(window.location.href.indexOf('?') + 1).split('&');
for(var i = 0; i < hashes.length; i++)
{
hash = hashes[i].split('=');
vars.push(hash[0]);
vars[hash[0]] = hash[1];
}
var authCode = vars["code"];
In the example which you share they did it using Standalone code, where you are manually copying the code part from the URL after authentication and running the step2.

Authenticate and get token for Azure Data Catalog using Java

Here I am trying to register a Data Asset from Data Lake Store in the Azure Data Catalog.
I am trying to get the authentication token for Azure Data Catalog which I will then set in header as below
request.setRequestProperty("Authorization","Bearer "+accesstoken);
Code I am using to get token
//This method sends request and gets the reponse
public static String SetRequestAndGetResponse(HttpsURLConnection request, String payload)
{
String accesstoken=null;
ExecutorService service = null;
Future<AuthenticationResult> FutureResult;
AuthenticationResult result;
AuthenticationCallback callback = null;
//Creating the credential object for DataCatalog with Client ID and Client secret picked up from the vault
ClientCredential credential = new ClientCredential("client_ID", "client_secret");
try
{
service = Executors.newFixedThreadPool(1);
AuthenticationContext context = new AuthenticationContext("https://login.windows.net/tenant_ID/oauth2/token",true,service);
/*
* getting the authentication result object using the App ID URI from Azure AD as suggested in
*
* https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-protocols-oauth-code
*/
FutureResult = context.acquireToken("App ID URI from Azure AD/login/aad", credential,null);
while(!(FutureResult.isDone()))
{}
accesstoken=FutureResult.get().getAccessToken();
//System.out.println("result "+accesstoken);
}
catch(Exception e)
{System.out.println("ex "+e.getMessage());
e.printStackTrace();}
But , I am getting exception as shown below
ex com.microsoft.aad.adal4j.AuthenticationException:
{"error_description":"AADSTS50001: The application named https://abc.onmicrosoft.com/somecode/login/aad was not found in the tenant named tenant_id.
This can happen if the application has not been installed by the administrator of the tenant or consented to by any user in the tenant.
You might have sent your authentication request to the wrong tenant.
Trace ID: some_id
Correlation ID: some_id
Timestamp: 2017-04-06 09:57:01Z","error":"invalid_resource"}
at com.microsoft.aad.adal4j.AdalTokenRequest.executeOAuthRequestAndProcessResponse(AdalTokenRequest.java:107)
at com.microsoft.aad.adal4j.AuthenticationContext.acquireTokenCommon(AuthenticationContext.java:816)
at com.microsoft.aad.adal4j.AuthenticationContext.access$100(AuthenticationContext.java:64)
at com.microsoft.aad.adal4j.AuthenticationContext$1.call(AuthenticationContext.java:172)
at com.microsoft.aad.adal4j.AuthenticationContext$1.call(AuthenticationContext.java:161)
at java.util.concurrent.FutureTask.run(FutureTask.java:266)
at java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1142)
at java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:617)
at java.lang.Thread.run(Thread.java:745)
***************** EDIT *******************
Thanks! But, I tried with "https://graph.windows.net" as resource URI to get token using authentication context but still server responds as unauthorized request.
By the way, I am using below snippet to create a request object.
String fullUri = String.format("https://api.azuredatacatalog.com/catalogs/"+catalogName+"/views/tables?api-version=2016-03-30");
URL url = null;
try {
//sETTING UP url connection to azure data catalog api
url = new URL(fullUri);
} catch (MalformedURLException e) {
// TODO Auto-generated catch block
System.out.println("Malformed URL Exception");
}
HttpsURLConnection request;
try {
request = (HttpsURLConnection) url.openConnection();
}
catch(Exception e){ e.printStackTrace();}
Please help me on how do I get this fixed.
Thanks.
The first parameter of AuthenticationContext.acquireToken method should be the graph resource instead of your App ID URI from Azure AD.
Please change the line of code as below:
FutureResult = context.acquireToken("https://graph.windows.net", credential, null);
For more info, see: https://learn.microsoft.com/en-us/azure/active-directory/develop/active-directory-devquickstarts-webapp-java
Here is what you should use:
string authorityUri = "https://login.windows.net/common/oauth2/authorize";
AuthenticationContext authContext = new AuthenticationContext(authorityUri);
Also, the resource string being passed in to the AcquireToken method should be "https://api.azuredatacatalog.com".

Outlook OAuth2 access mails

I am following this post: Outlook RestGettingStarted. From my Java app I am trying to get AccessToken and RefreshToken. When I made Authorization code request, it ended into following error:
Sorry, but we’re having trouble signing you in. We received a bad
request.
Additional technical information: Correlation ID:
ed838d66-5f2e-4cfb-9223-a29082ecb26f Timestamp: 2015-08-20 10:20:09Z
AADSTS90011: The 'resource' request parameter is not supported.
NOTE: URL formation is correct as per documentation.
So, I removed "resource" query parameter from my code. And redirected authorize url in browser. On user consent I got authorization code. Using this code I got AccessToken. But when I try to connect with Outlook IMAP server it failed. Java ref Link for details: Java OAuth2
But it gives me error:
[AUTHENTICATIONFAILED] OAuth authentication failed.
NOTE: I added correct scope, and user email.
Then using obtained Access Token I made Mail Rest API call to get Messages from User Inbox. It ended into following error:
HTTP response:
{"error":{"code":"MailboxNotEnabledForRESTAPI","message":"REST API is
not yet supported for this mailbox."}}
Can anyone help me for following:
What is the exact cause for: "AADSTS90011: The 'resource' request parameter is not supported" after following Outlook dev docs.
How to resolve "MailboxNotEnabledForRESTAPI" error.
Is it possible to connect using java mail APIs to Outlook IMAP server with correct AccessToken ?
I ran into this recently, but don't remember which solved it. One main issue is in the documentation in that it is varying. It will tell you to attach "resource", but that is for something else like Azure.
Here is the code I used:
First request to send:
private static final String USER_OAUTH2_AUTHORIZE_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/authorize";
public String getOAuthDialog(Http.Request request) {
return USER_OAUTH2_AUTHORIZE_URL
+ "?client_id=" + config.getClientId()
+ "&redirect_uri=" + getOutlookLoginRedirect(request)
+ "&response_type=code"
+ "&scope=https%3A%2F%2Foutlook.office.com%2Fmail.send%20" +
"https%3A%2F%2Foutlook.office.com%2Fmail.readwrite%20" +
"offline_access%20openid%20email%20profile"
+ "&state=" + crypto.generateSignedToken();
}
Scope was the hardest thing to figure out. I found a lot of ones that did not work. And it wasn't clear that I needed to separate them with spaces.
Then they will send you a request to your redirect url that was supplied. It will contain a code which you need to exchange for the data you requested in the scope. The redirect url that is supplied needs to be the exact same. Also you need to register the redirect url on your application portal under the Platform->Add Platform->Redirect URI->Add Url
private static final String USER_ACCESS_TOKEN_URL = "https://login.microsoftonline.com/common/oauth2/v2.0/token";
private Map<String, String> sendOutlookUserOAuthRequest(Http.Request request, String code) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("code", code, true);
params.put("redirect_uri", getOutlookLoginRedirect(request), true);
params.put("grant_type", "authorization_code");
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
Map<String, String> result = new HashMap<>();
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
return result;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
result.put("access_token", node.get("access_token").asText());
}
if (node.hasNonNull("refresh_token")) {
result.put("refresh_token", node.get("refresh_token").asText());
}
if (node.hasNonNull("id_token")) {
String[] tokenSplit = node.get("id_token").asText().split("\\.");
if (tokenSplit.length >= 2) {
try {
JSONObject jsonObject = new JSONObject(new String(Base64.getDecoder().decode(tokenSplit[1])));
if (jsonObject.has("name")) {
result.put("name", jsonObject.get("name").toString());
}
if (jsonObject.has("email")) {
result.put("outlookUid", jsonObject.get("email").toString());
} else if (jsonObject.has("preferred_username")) {
result.put("outlookUid", jsonObject.get("preferred_username").toString());
}
} catch (JSONException e) {
log.error("Error extracting outlookUid from id_token: ", e);
}
}
}
return result;
}
Another request that you might need is to update the refresh token:
private String getAccessTokenFromRefreshToken(User user) {
WSClient ws = WS.client();
HttpParameters params = new HttpParameters();
params.put("client_id", config.getClientId(), true);
params.put("client_secret", config.getClientSecret(), true);
params.put("grant_type", "refresh_token");
params.put("refresh_token", user.getOutlookRefreshToken());
String postParams = OAuthUtil.parametersToString(params);
WSRequest wsRequest = ws.url(USER_ACCESS_TOKEN_URL)
.setMethod("POST")
.setContentType("application/x-www-form-urlencoded")
.setBody(postParams);
WSResponse wsResponse = wsRequest.execute().get(10, TimeUnit.SECONDS);
if (wsResponse.getStatus() != HttpStatus.SC_OK) {
log.error("Failure to refresh outlook access token for user: " + user +
". Received status: " + wsResponse.getStatus() + " : " + wsResponse.getStatusText());
return null;
}
JsonNode node = wsResponse.asJson();
if (node.hasNonNull("access_token")) {
String accessToken = node.get("access_token").asText();
return accessToken;
} else {
log.error("Outlook refresh token failure, 'access_token' not present in response body: " + wsResponse.getBody());
return null;
}
}
One issue I ran into that took far longer than I would have hoped was in getting the clientId and clientSecret. This was because the language microsoft uses wasn't the most explicit. Client Id and application id are used interchangeably. The client secret is also the password that you create on the Application Portal, not to be confused with the Private Key that you can generate.
So you actually want the application_id and the password, although they refer to them as client_id and client_secret with no direct indication as to the lines drawn.
This is all assuming you have set up an application on the Outlook Application Portal. https://apps.dev.microsoft.com/
I hope this helps, although I assume you probably already solved this.
I faced the same problem with Java mail. You need to add service principals for your application on the Azure AD.
Find complete steps explained in Medium article Complete guide: Java Mail IMAP OAuth2.0 Connect Outlook | by Ritik Sharma | Dec, 2022.

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.

Categories