I've recently faced a problem.
My frontend use Oauth2 to authenticate my user on Azure (Organization). This giives me multiple information containing idToken and accessToken.
My Backend uses AADResourceServerWebSecurityConfigurerAdapter to authenticate the user thanks to the idToken put in the Authorization Bearer header from the frontend.
Unitil here everything works well. I can get the connected user with this:
public static String getConnectedUserEmail() {
return (String) ((AADOAuth2AuthenticatedPrincipal) SecurityContextHolder.getContext().getAuthentication().getPrincipal()).getAttributes().get("preferred_username");
}
I use my backend app credentials to contact the graph api on behalf of the API itself.
Though, following Azure Ad documentation, I cannot query group/calendar on behalf of the API, I have to do it on behalf of the user.
To respect SOLID principles, I want to make the request from the backend, but on behalf of the user.
I cannot find any information about that.
So here is my final question: How can I make a graph API request in my backend on the behalf of the user?
Knowing that trying to use the tokenValue (idToken) of the user or the accessToken value returns invalid credentials from Microsoft.
You requested GET /groups/{id}/calendar to get group calendar as you said.
You can call Graph API with the access token using on-behalf-of flow, see here.
There is a sample using the On-Behalf-Of flow: https://github.com/Azure-Samples/ms-identity-java-webapi
Note: Make sure the following delegated permission is added.
I tried to get graph api token from postman UI and was able to get planner data.
How to achieve same in java spring
I am not able to get access token for Microsoft graph api using java spring. I am able to get access token using postman.
I need to access planner API from one of the web application. As per Microsoft documentation I configured a app in azure active directory and got client key, secret key etc.
I also configured required permission to get groups and users.
Very first time I used below from POSTMAN
https://login.microsoftonline.com//oauth2/token with below data
client_id : <client_id from configured app>
client_secret : <client secret from configured app>
grant_type : client_credentials
resource : https://graph.microsoft.com
I got token, and I was able to get groups from https://graph.microsoft.com/v1.0/groups/
But same token was not valid for getting plans of group.
With lot of digging, I came to know that token accessed with client_credentials is not applicable to get data from planner API. So, next I used below details to get access token from UI of postman.
Grant Type : authorization_code
Callback URL : https://www.getpostman.com/oauth2/callback
Auth URL : https://login.microsoftonline.com/<tenant_id>/oauth2/authorize?resource=https://graph.microsoft.com
Access Token URL : https://login.microsoftonline.com/<tenant_id>/oauth2/v2.0/token
client_id : <client_id from configured app>
client_secret : <client secret from configured app>
I got the Microsoft login screen, and after successful login, I got token.
I could call planner API using this access token.
Now my question is how can I get this same token using java spring.
Also, my web app will be having background service running in scheduler calling graph API daily.
I do not want manual intervention here, but as told earlier, graph API will ask to login.
How to achieve above requirement.
private String getAuth() {
ConfidentialClientApplication app = null;
IClientCredential credential = ClientCredentialFactory.create(Appsecret);
try {
app = ConfidentialClientApplication.builder(MicrsoftAppId, credential).authority("https://login.microsoftonline.com/"+tenantId+"/").build();
}catch(MalformedURLException e) {
System.out.println(e.getMessage());
}
ClientCredentialParameters parameters = ClientCredentialParameters.builder(Collections.singleton("https://graph.microsoft.com/.default")).build();
CompletableFuture<IAuthenticationResult> future = app.acquireToken(parameters);
try {
IAuthenticationResult result = future.get();
return result.accessToken();
}catch(ExecutionException e){
System.out.println(e.getMessage());
}catch(InterruptedException e) {
System.out.println(e.getMessage());
}
return null;
}
Here you go! This code is made for application permission (so not delegated). It only requires your client Id and secret to operate. You will need the microsoft graph jar for it to work (and the many jars supporting it).
I'm trying to run the below code to access the REST API, this REST API is secured with hawk authentication.
What i want to achieve here is with this POST request the server will return the AppTicket, this AppTicket will be used in subsequent API calls for testing.
But the problem is i'm getting the bad request error message.
I'm using jersey client with java.
String payload= "{\"username\":\"test#domain.com\",\"password\":\"Password\",\"applnName\":\"my APP AngularJS\",\"applnType\":\"Web Application\",\"fingerprint\":\"1234314y31387ddhsks\",\"fpData\":\"fdata11\"}";
Client restClient = Client.create();
WebResource request = restClient.resource("https://test.server.com/api/login");
ClientResponse response2 = request.type("application/json; charset=UTF-8").post(ClientResponse.class, payload);
System.out.println(response2);
This is RAML based API, its RAML defition for POST call is as below,
Properties
username: required (string - minLength: 3 - maxLength: 100)
Valid email address provided by the organization
Example:
user#example.com
password: required (string)
Example:
sngPa55wrd
fingerprint: (string)
A uniqe identifier for the device used to sign-in
fpData: (array of )
List of parameters used to compute fingerprint
applnType: required (one of Web Application, Android Application, iOS Application)
Type of application the user is logging in from
applnName: required (one of Web APP AngularJS, Mobile Android, Mobile NativeScript)
Can you please help me out to find the issues in this code here.
I am calling the salesforce using REST API provided by maven dependency.
<!-- Force REST API -->
<dependency>
<groupId>com.frejo</groupId>
<artifactId>force-rest-api</artifactId>
<version>0.0.39</version>
</dependency>
I have implemented the below code, but I am wondering without sending the access_token how am I getting the result ? Isn't that not secure or how its implemented ? Will be good to go ahead with this API?
public class ForceApiExample {
public static void main(String[] args) {
ForceApi api = new ForceApi(new ApiConfig()
.setUsername("XXX#xy.com")
.setPassword("XXX")
.setClientId("XXXXXMB8EGsF3NRtJ0")
.setClientSecret("XXX"));
ApiSession session = api.getSession();
String accessToken = session.getAccessToken();
System.out.println("ACCESS_TOKEN : "+accessToken);
List<Map> result = api.query("SELECT name FROM Account").getRecords();
System.out.println("RESULT : "+result.size());
}
}
Here is the result:
ACCESS_TOKEN : 00D7F0000001I8v!ARgAQB8tHuuquPRL5Z4uj4TBJG0cg0dJYBxy00jPhioEWKI86RlHqgXKSM0DfSTWHYVLl5i9HbbPxjXlgxlUP7XmLKejKrP4
RESULT : 535
Actually Salesforce internally provides the client the authentication token when a request is made. For REST API Salesforce uses OAuth 2.0 for authentication. There exists different kinds of OAuth authentication process. One of them is through User credentials. When you are sending the request using the user credentials you are actually asking Salesforce to provide you the access token. After getting the request Salesforce verifies the credentials and sends the access token back. But here no refresh tokens can be used because the user does not directly authorize the app.
Follow Understanding Authentication link for more details.
EDIT: Originally this question asked how I could authenticate with the Google Analytics API using only my API key. As vlatko pointed out, this isn't possible. Now I'm just focused on getting OAuth2 to work. I will be trying vlatko's suggestions when I get a chance and will update the question. In the meantime, feel free to contribute answers with anything you think I'm missing.
ORIGINAL QUESTION:
I'm trying to make requests to the Google Analytics API. I'm walking through the Hello Analytics tutorial trying to replicate the steps. Whatever I try, I can't seem to authenticate succesfully.
The tutorial says the following:
Open the file you created named HelloAnalyticsApi.java and add the
following method:
private static Analytics initializeAnalytics() throws Exception {
// Authorization.
Credential credential = OAuth2Native.authorize(
HTTP_TRANSPORT, JSON_FACTORY, new LocalServerReceiver(),
Arrays.asList(AnalyticsScopes.ANALYTICS_READONLY));
// Set up and return Google Analytics API client.
return Analytics.builder(HTTP_TRANSPORT, JSON_FACTORY)
.setApplicationName("Google-Analytics-Hello-Analytics-API-Sample")
.setHttpRequestInitializer(credential)
.build();
}
When a user encounters this script, the application will attempt to
open the default browser and navigate the user to a URL hosted on
google.com. At this point, the user will be prompted to login and
grant the application access to their data. Once granted, the
application will attempt to read a code from the browser window, then
close the window.
The difference is that I'm trying to do this with a servlet application, and I want to use simple API access with an API key (rather than an OAuth 2.0 client ID). I know that OAuth 2.0 is recommended, but I only need to access data that I own and want to simplify the technical requirements. I based this decision on this page, which says:
An API key is a unique key that you generate using the Console. When
your application needs to call an API that's enabled in this project,
the application passes this key into all API requests as a key={API_key}
parameter. Use of this key does not require any user action or
consent, does not grant access to any account information, and is not
used for authorization.
If you are only calling APIs that do not require user data, such as
the Google Custom Search API, then API keys may be simpler to
implement. However, if your application already uses an OAuth 2.0
access token, then there is no need to generate an API key as well. In
fact, Google ignores passed API keys if an OAuth 2.0 access token is
already associated with the corresponding project.
I can't find many code examples of auth flow just using the API key - most everything I've found shows using the client ID with the downloaded .p12 file, for example the GoogleCredential javadoc. The one example application I could find was Google's Books Sample app. Anyway, here's what I tried (mimicking the first request in the tutorial, which gets a list of the accounts from the management API):
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, null)
.setApplicationName("Dev API Access")
.build();
Management.Accounts.List list =
analytics.management().accounts().list().setKey(apiKey);
Accounts accounts = list.execute();
Where "Dev API Access" is the "Name" field in my API console dashboard. The API key is a server key restricted to my IP address. This fails with the following response:
401 Unauthorized
{
"code": 401,
"errors": [
{
"domain": "global",
"location": "Authorization",
"locationType": "header",
"message": "Login Required",
"reason": "required"
}
],
"message": "Login Required"
}
I also tried this:
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, null)
.setApplicationName("Dev API Access")
.setGoogleClientRequestInitializer(new AnalyticsRequestInitializer(apiKey))
.build();
Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();
Which shows the same error. What am I doing wrong here? Is OAuth2 required for analytics calls? If so, why does just using the API key work in the Books Sample app?
Moving on, I went ahead and tried OAuth2 anyway - I created a client ID and downloaded the .p12 private key file. But I couldn't get that working either. Here's what I tried:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(serviceAccountId)
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.setServiceAccountUser(serviceAccountUser)
.build();
Analytics analytics =
new Analytics.Builder(httpTransport, jsonFactory, credential)
.setApplicationName("Dev API Access")
.build();
Management.Accounts.List list = analytics.management().accounts().list();
Accounts accounts = list.execute();
Where serviceAccountId is the email address of the Google account owning the project and serviceAccountUser is the email address listed on the generated client ID. This fails with the following:
400 Bad Request
{
"error": "invalid_grant"
}
What does "invalid grant" mean, and how do I successfully authenticate (ideally without OAuth2)?
To answer your first question: in general, OAuth2.0 is used for authorized access to user's private data, so getting user consent and obtaining an access token is required. In the case with Google Books API, however, if you're accessing public data, there is no need for end user consent so an API key is sufficient. If you try accessing non public data with the Books API, you'll still need an OAuth2 token.
The good news for your case is that even with OAuth2, you can bypass user involvement and streamline your flow with Service Accounts - assuming your application has access to the API. There is a way to set that up for the Analytics API, explained here (check the steps in the Service Accounts section). I think you are on the right track with your Credential builder, but I don't think you need to set the service account user in there, since you are not doing any user impersonation.
vlatko's answer got me on the right track. The issue turned out to be that I was confusing the owner email address with the service account email address. For example, I was doing the following:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("owneremail#gmail.com") //wrong
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.setServiceAccountUser("xxx#developer.gserviceaccount.com") //unnecessary
.build();
When I needed to do this:
Credential credential =
new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId("xxx#developer.gserviceaccount.com")
.setServiceAccountScopes(AnalyticsScopes.ANALYTICS_READONLY)
.setServiceAccountPrivateKeyFromP12File(new File(p12FilePath))
.build();
Also, I had added owneremail#gmail.com as a user on my Analytics application - this similarly needed to be the service account email instead.