Authenticating with Google API using AccountManager - java

I've been struggling with this for a couple days now. I'm trying to make calls to Google Calendar using authentication via Android's AccountManager. I retrieve an auth token using the usual method:
AccountManager manager = AccountManager.get(this);
String authToken = manager.getAuthToken(account, AUTH_TOKEN_TYPE, true, null, null).getResult().getString(AccountManager.KEY_AUTHTOKEN);
And then, with that token, I create a Calendar instance like this:
HttpTransport transport = AndroidHttp.newCompatibleTransport();
JacksonFactory jsonFactory = new JacksonFactory();
GoogleAccessProtectedResource accessProtectedResource = new GoogleAccessProtectedResource(accessToken);
Calendar calendar = Calendar.builder(transport, jsonFactory).setApplicationName("MyApp/1.0").setJsonHttpRequestInitializer(new JsonHttpRequestInitializer() {
#Override
public void initialize(JsonHttpRequest request) {
CalendarRequest calendarRequest = (CalendarRequest) request;
calendarRequest.setKey(API_KEY);
}
}).setHttpRequestInitializer(accessProtectedResource).build();
However, when I make API calls using this, I receive the 401 Unauthorized error seen below. Note that I have included code to invalidate expired auth tokens, so I don't believe that's the problem here.
com.google.api.client.googleapis.json.GoogleJsonResponseException: 401 Unauthorized
{
"code" : 401,
"errors" : [ {
"domain" : "global",
"location" : "Authorization",
"locationType" : "header",
"message" : "Invalid Credentials",
"reason" : "authError"
} ],
"message" : "Invalid Credentials"
}
Any thoughts on what I might be doing wrong?

Yes this is possible. Once you have a handle on the Google account (as you described), you just need to request an auth token from the AccountManager for the GData service.
If the android device already has an auth token (for the particular GData service you're trying to access), it will be returned to you. If not, the AccountManager will request one and return it to you. Either way, you don't need to worry about this as the AccountManager handles it.
In the following example, I am using the Google Spreadsheets API:
ArrayList<Account> googleAccounts = new ArrayList<Account>();
// Get all accounts
Account[] accounts = accountManager.getAccounts();
for(Account account : accounts) {
// Filter out the Google accounts
if(account.type.compareToIgnoreCase("com.google")) {
googleAccounts.add(account);
}
}
AccountManager accountManager = AccountManager.get(activity);
// Just for the example, I am using the first google account returned.
Account account = googleAccounts.get(0);
// "wise" = Google Spreadheets
AccountManagerFuture<Bundle> amf = accountManager.getAuthToken(account, "wise", null, activity, null, null);
try {
Bundle authTokenBundle = amf.getResult();
String authToken = authTokenBundle.getString(AccountManager.KEY_AUTHTOKEN);
// do something with the token
InputStream response = sgc.getFeedAsStream(feedUrl, authToken, null, "2.1");
}
AND
Please have a look at the sample code in the google data api. The important thing to do after authentication is to call GoogleHeaders.setGoogleLogin(String).
I hope this helps.

Ran into a similar issue myself. I found that I need to set the xoauth_requestor_id in the unknown keys list ( reference : http://www.yenlo.nl/2011/google-calendar-api-v3-and-2lo/ )
This worked for me:
com.google.api.services.calendar.Calendar.CalendarList.List list = calendar.calendarList().list();
//Set the requestor id
list.getUnknownKeys().put("xoauth_requestor_id", "user#gmail.com");
After this the API calls went thru.
I wish there was a explanation as to why the requestor id is needed. Can someone explain?

Related

How to read email with proper rights in token

I am trying to upgrade an application (it should fetch emails from a mailbox every few minutes) from Microsoft EWS deprecated API to the new Graph API, but I am facing some issues.
This is my class for the connector :
public class O365graphApiConnector {
private final GraphServiceClient<Request> graphClient;
public O365graphApiConnector(String clientId, String username, String password) {
final UsernamePasswordCredential usernamePasswordCredential =
new UsernamePasswordCredentialBuilder()
.clientId(clientId)
.username(username)
.password(password)
.build();
final TokenCredentialAuthProvider tokenCredentialAuthProvider =
new TokenCredentialAuthProvider(usernamePasswordCredential);
graphClient=GraphServiceClient.builder()
.authenticationProvider(tokenCredentialAuthProvider)
.buildClient();
}
public User getUserProfile() {
return graphClient.me().buildRequest().get();
}
public MessageCollectionPage getOutlookEmails() {
return graphClient.me().messages().buildRequest().get();
}
}
I am using com.azure:azure-identity:1.4.2 and com.microsoft.graph:microsoft-graph:5.8.0.
I build the connector, passing the clientId, username and password. I am able to call getUserProfile , and I am getting something, so the authentication "works".
However, I get a 404 when calling getOutlookEmails :
SEVERE: Throwable detail:
com.microsoft.graph.http.GraphServiceException: Error code:
ResourceNotFound Error message: Resource could not be discovered.
GET https://graph.microsoft.com/v1.0/me/messages SdkVersion :
graph-java/v5.8.0
404 : Not Found [...]
When I run this in debug mode and intercept the token, it seems to be OK though : I have a bunch of rights that my admin has given to the applicative account :
"scp": "EWS.AccessAsUser.All Mail.Read Mail.Read.Shared Mail.ReadBasic Mail.ReadWrite
Mail.ReadWrite.Shared Mail.Send Mail.Send.Shared MailboxSettings.ReadWrite User.Read User.Read.All User.ReadWrite profile openid email"
This is part of what we see on the admin side (more rights were added after the screenshot was taken) :
My understanding is that this should be enough to get access to the emails of the given mailbox programmatically, but apparently, it's not.
Any idea of what I am missing ?
actually, the technical "user" I am using didn't really have a mailbox (despite the user name being an email address.. that confused me).
It had been given the permissions on the given mailbox I am interested in though, so the fix is simply to select the mailbox/user before retrieving the messages :
public MessageCollectionPage getOutlookEmailsFor(String mailbox) {
return graphClient.users(mailbox).messages().buildRequest().get();
}

Google endpoints framework and firebase authentication issue (No auth providers are defined in the config)

I've deployed the following endpoints framework API to my Google appengine application + generated and deployed the OpenAPI configuration for Google's "Client-API service" + generated the endpoints client API for my Android app successfully:
#Api(
name = "endpoint",
version = "v1",
apiKeyRequired = AnnotationBoolean.TRUE,
authenticators = {EspAuthenticator.class},
issuers = {
#ApiIssuer(
name = "firebase",
issuer = "https://securetoken.google.com/MY_PROJECT_ID",
jwksUri = "https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com")
},
issuerAudiences = {
#ApiIssuerAudience(name = "firebase", audiences = "MY_PROJECT_ID")
},
namespace =
#ApiNamespace(
ownerDomain = "xxxxx",
ownerName = "xxxxx"
)
public class API {
#ApiMethod(name = "procesOrderRequest", path = "customer/orders/request")
public Order processCutomerOrderRequest(User user, OrderRequest orderRequest) throws UnauthorizedException {
log.info("procesOrderRequest(): CustomerId: " + orderRequest.getCustomer());
log.info("procesOrderRequest(): bagId: " + orderRequest.getBagId());
log.info("processCutomerOrderRequest(): customerId: " + orderRequest.getCustomer());
if (user == null) {
throw new UnauthenticatedException("Unauthorized user by RR");
}
Order order = new Order();
order.setBagId(orderRequest.getBagId());
order.setPriority(orderRequest.getPriority());
order.setOrderId(orderRequest.getBagId() + 1000);
return order;
}
}
As you can see from above I'm using firebase authentication and API keys.
In my Android application I successfully login my Firebase user, but if I try to execute the following an endpoint client API request
private class ContactBackendTask extends AsyncTask<Void, Void, Void> {
String mIDToken = null;
#Override
protected Void doInBackground(Void... voids) {
FirebaseUser user = mFirebaseAuthenticator.getCurrentUser();
user.getIdToken(true).addOnSuccessListener(new OnSuccessListener<GetTokenResult>() {
#Override
public void onSuccess(GetTokenResult result) {
mIDToken = result.getToken();
//Do whatever
Log.d("attempLogin", "GetTokenResult result = " + mIDToken);
}
});
Endpoint.Builder endpoint = new Endpoint.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), null);
endpoint.setRootUrl("https://MY_PROJECT_ID.appspot.com/_ah/api/");
endpoint.setApplicationName("MY_PROJECT_ID");
Endpoint service = endpoint.build();
OrderRequest orderRequest = new OrderRequest();
orderRequest.setBagId(35);
orderRequest.setPriority(9);
orderRequest.setCustomer("someUser#gmail.com");
try {
Endpoint.ProcesOrderRequest request = service.procesOrderRequest(orderRequest);
Order order = request.setKey("MY_API_KEY").setOauthToken(mIDToken).execute();
Log.d("attempLogin", "OrderId result = " + order.getOrderId());
} catch (IOException ex) {
System.out.println(ex.getMessage());
}
return null
}
I get the following response from Google's Endpoints Management service:
400 Bad Request
{
"code": 400,
"errors": [
{
"domain": "global",
"message": "java.lang.IllegalArgumentException: No auth providers are defined in the config.",
"reason": "badRequest"
}
],
"message": "java.lang.IllegalArgumentException: No auth providers are defined in the config."
}
Any idea, what I'm missing here?
I've followed the following Firebase specific authentication tutorial as well as the following Google Endpoints User Authentication tutorial.
Any idea or hint is very appreciated.
Update:
Here is the SERVICE_CONFIG_FILE used to deploy the endpoints API to Google's Service Management.
SecurityDefinitions from openenapi.json:
securityDefinitions":{
"api_key":{
"in":"query",
"name":"key",
"type":"apiKey"
},
"firebase":{
"authorizationUrl":"",
"flow":"implicit",
"type":"oauth2",
"x-google-issuer":"https://securetoken.google.com/my_project_id",
"x-google-jwks_uri":"https://www.googleapis.com/service_accounts/v1/metadata/x509/securetoken#system.gserviceaccount.com"
}
},
"swagger":"2.0"
Update 2:
Stacktrace from the App Engine console:
java.lang.IllegalArgumentException: No auth providers are defined in the config.
at com.google.api.auth.Authenticator.create (Authenticator.java:178)
at com.google.api.auth.Authenticator.create (Authenticator.java:171)
at com.google.api.server.spi.auth.EspAuthenticator.<init> (EspAuthenticator.java:54)
at sun.reflect.NativeConstructorAccessorImpl.newInstance0 (Native Method)
at sun.reflect.NativeConstructorAccessorImpl.newInstance (NativeConstructorAccessorImpl.java:62)
at sun.reflect.DelegatingConstructorAccessorImpl.newInstance (DelegatingConstructorAccessorImpl.java:45)
at java.lang.reflect.Constructor.newInstance (Constructor.java:423)
at java.lang.Class.newInstance (Class.java:443)
at com.google.api.server.spi.request.Auth$1.apply (Auth.java:57)
at com.google.api.server.spi.request.Auth$1.apply (Auth.java:51)
Update 3:
<env-variables>
<env-var name="ENDPOINTS_SERVICE_NAME" value="my_project_id.appspot.com" />
<env-var name="ENDPOINTS_SERVICE_VERSION" value="2018-03-29r0" />
</env-variables>
Of course,
"my_project_id"
is just an example ID for this post. I don't want to post my real project id here.
And
"2018-03-29r0"
is the the ID which is generated after deploying the openapi.json file
with the command
gcloud endpoints services deploy target/openapi-docs/openapi.json
Update 4:
However I get now the following response from my appengine backend application
503 Service Unavailable
{
"code": 503,
"errors": [
{
"domain": "global",
"message": "com.google.api.auth.UnauthenticatedException: Unauthorized user by RR",
"reason": "backendError"
}
],
"message": "com.google.api.auth.UnauthenticatedException: Unauthorized user by RR"
}
when executing in my Android app
Order order = request.setKey("MY_API_KEY").setOauthToken(mIDToken).execute();
The exception UnauthenticatedException is triggered due to fact, that the backend API method processCutomerOrderRequest() validates user == null although the firebase authentication of the user was signalled as "successfully logged in".
My question: What is wrong here? Maybe I shouldn't use the method setOauthToken() in my client app? Is the call correct?
I found an answer for the question mentioned in Update 4 of the initial post.
Please find it in the answer of the following post.
This stackoverflow question is comprised of multiple "detailed" questions.
Based on self-study all questions could be answered by myself. The link mentioned above can be understood as the last "coffin nail" in finding all answers I was looking for.

How to identify and verifying tokenId from google API?

I can't find any solution for as i think very general issue...
I use google API for LogIn, and according to this article i can get tokenId using this method
private String getTokenId(GoogleSignInResult result) {
String tokenId = null;
GoogleSignInAccount googleSignInAccount = result.getSignInAccount();
if (googleSignInAccount != null) {
tokenId = googleSignInAccount.getIdToken();
}
return tokenId;
}
it is standard method, but why this method retern back token id which the contains 857 char of String? And i can't verify it with standard link
https://www.googleapis.com/oauth2/v3/tokeninfo?access_token= <857 char>;
and i get back this response
{
"error_description": "Invalid Value"
}
I am sure if google provide method to get token id there is have to be a method to verify it...
What am i doing wrong?

Regarding google +domain api ............ getting an error 403 forbidden while listing the circle using installed application

i have just listing down the circle name in my google+domain api but getting an error of
i have used installed application-> other option while making the application in google developer console
I am developing a small installed application wherein I am integrating with Google+ Domain API's. I am using OAuth2 authentication.I have generated client_id and client_secret for my Installed application from Google API console. Using Google+ Domain API's, I am able to generate the access token.
Also I am using ---some----#gmail.com using gmail account
my code is as :-
enter code here
private static final String CLIENT_ID = "xyxxxxxxxx something in there";
private static final String CLIENT_SECRET = "Fhh1LYQ__UTso48snXHyqSQ2";
public static void main(String[] args) throws IOException {
// List the scopes your app requires:
try{
List<String> SCOPE = Arrays.asList(
"https://www.googleapis.com/auth/plus.me",
"https://www.googleapis.com/auth/plus.stream.write",
"https://www.googleapis.com/auth/plus.circles.read");
final String REDIRECT_URI = "urn:ietf:wg:oauth:2.0:oob";
GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
new NetHttpTransport(),
new JacksonFactory(),
CLIENT_ID, // This comes from your Developers Console project
CLIENT_SECRET, // This, as well
SCOPE)
.setApprovalPrompt("force")
// Set the access type to offline so that the token can be refreshed.
// By default, the library will automatically refresh tokens when it
// can, but this can be turned off by setting
// dfp.api.refreshOAuth2Token=false in your ads.properties file.
.setAccessType("offline").build();
// This command-line se`enter code here`rver-side flow example requires the user to open the
// authentication URL in their browser to complete the process. In most
// cases, your app will use a browser-based server-side flow and your
// user will not need to copy and paste the authorization code. In this
// type of app, you would be able to skip the next 5 lines.
// You can also look at the client-side and one-time-code flows for other
// options at https://developers.google.com/+/web/signin/
String url = flow.newAuthorizationUrl().setRedirectUri(REDIRECT_URI).build();
System.out.println("Please open the following URL in your browser then " +
"type the authorization code:");
System.out.println(" " + url);
BufferedReader br = new BufferedReader(new InputStreamReader(System.in));
String code = br.readLine();
// End of command line prompt for the authorization code.
GoogleTokenResponse tokenResponse = flow.newTokenRequest(code)
.setRedirectUri(REDIRECT_URI).execute();
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(new NetHttpTransport())
.setJsonFactory(new JacksonFactory())
.setClientSecrets(CLIENT_ID, CLIENT_SECRET)
.addRefreshListener(new CredentialRefreshListener() {
#Override
public void onTokenResponse(Credential credential, TokenResponse tokenResponse) {
// Handle success.
System.out.println("Credential was refreshed successfully.");
}
#Override
public void onTokenErrorResponse(Credential credential,
TokenErrorResponse tokenErrorResponse) {
// Handle error.
System.err.println("Credential was not refreshed successfully. "
+ "Redirect to error page or login screen.");
}
})
// You can also add a credential store listener to have credentials
// stored automatically.
//.addRefreshListener(new CredentialStoreRefreshListener(userId, credentialStore))
.build();
// Set authorized credentials.
credential.setFromTokenResponse(tokenResponse);
// Though not necessary when first created, you can manually refresh the
// token, which is needed after 60 minutes.
credential.refreshToken();
// Create a new authorized API client
PlusDomains service = new PlusDomains.Builder(new NetHttpTransport(), new JacksonFactory(), credential).setApplicationName("Get-me").setRootUrl("https://www.googleapis.com/").build();
PlusDomains.Circles.List listCircles = service.circles().list("me");
listCircles.setMaxResults(5L);
CircleFeed circleFeed = listCircles.execute();
List<Circle> circles = circleFeed.getItems();
// Loop until no additional pages of results are available.
while (circles != null) {
for (Circle circle : circles) {
System.out.println(circle.getDisplayName());
}
// When the next page token is null, there are no additional pages of
// results. If this is the case, break.
if (circleFeed.getNextPageToken() != null) {
// Prepare the next page of results
listCircles.setPageToken(circleFeed.getNextPageToken());
// Execute and process the next page request
circleFeed = listCircles.execute();
circles = circleFeed.getItems();
} else {
circles = null;
}
}
}catch(Exception e)
{
System.out.println("Exception "+e);
}
}
Its an INSTAlled Application -> other Options........ in google developer console
i get the below error:---
Exception com.google.api.client.googleapis.json.GoogleJsonResponseException: 403 Forbidden
{
"code" : 403,
"errors" : [ {
"domain" : "global",
"message" : "Forbidden",
"reason" : "forbidden"
} ],
"message" : "Forbidden"
}
Note: I have also enabled Google+ Domain API in my Google API Console.
REDIRECT_URI ="urn:ietf:wg:oauth:2.0:oob" since it's a Installed app. Any Suggestions?
Please help me out guys
See this other answer: the Google+ Domains API is only "for people who use Google Apps at college, at work, or at home." It appears that Google does not currently allow apps to list circles for regular Google+ accounts.

Google OAuth, handle a revoked authorization

I've been using Google OAuth to let users authorize access to the Calendar Service for my Web Application. After a successful 3-legged auth flow, I was storing all user's credentials in a common file on the app Server. The next time the app needs to use the service, it will check if the credentials exist, and if yes, it will assume they are valid
code works like that
#Override
public void _authorize(String userId) throws IOException {
// Check if user has already authorised the service.
Credential credents = flow.loadCredential(userId);
// Checking if the given user is not authorized
if (credents == null) {
//Create credentials now. user will be redirected to authorise
try {
//Creating a LocalServer Receiver
// Getting the redirect URI
// Creating a new authorization URL
// Setting the redirect URI
// Building the authorization URL
// Receiving authorization code
// Exchanging it for an access token
// Storing the credentials for later access
credents = flow.createAndStoreCredential(response, id);
} finally {
// Releasing resources
}
} else {
// Assume the credentials are valid. so there's nothing left to do here, let's get that client
//Update: Nooooooot! the user might have revoked the authorization, so credents != null BUT they are invalid
//TODO: handle an Exception here, and manage the revoked credentials
}
// Setting up the calendar service client
client = new com.google.api.services.calendar.Calendar.Builder(httpTransport, jsonFactory, credents).setApplicationName(APPLICATION_NAME)
.build();
}
This works fine, as long as the user never changes his mind. But if the user decides to manually revoke the authorization using the Google Account security options, the com.google.api.services.calendar.Calendar retrieval will Fail.
My question is :
Is there a way to check if the credentials are still valid, before trying to use them ?
Else, I can only guess that the failure to get the client object, is the only way to have my portal realize that the credentials are no more valid ?
What should I do about the invalid/revoked credentials ? should I just call flow.createAndStoreCredential and they are going to be overwritten? Or do I have to delete the old ones first ? (how ?)
You can use the refreshToken() method for this. See example:
// Fetch credential using the GoogleAuthorizationCodeFlow
GoogleAuthorizationCodeFlow authorizationCodeFlow;
Credential credential = authorizationCodeFlow.loadCredential(userId);
if (credential != null) {
try {
// refresh the credential to see if the refresh token is still valid
credential.refreshToken();
System.out.println("Refreshed: expires in: " + credential.getExpiresInSeconds());
} catch (TokenResponseException e) {
// process exception here.
// This will catch the Exception.
// This Exception contains the HTTP status and reason etc.
// In case of a revoke, this will throw something like a 401 - "invalid_grant"
return;
}
} else {
// No credential yet known.
// Flow for creating a new credential here
}
EDIT
If you indeed have an invalid refresh token and you want to renew it, then you need to repeat the steps that you did in the first place to get the credentials. So:
genererate a new authorization URL
redirect the user to it
user accepts the consent screen
catch the authorization code from the redirect back to your app
request a new token from Google using the authorization code
create and store a new Credential using the response from Google
No need to delete the old credential. But if you want to explicitly do so, it is possible.
Something like:
// This userId is obviously the same as you used to create the credential
String userId = "john.doe";
authorizationCodeFlow.getDataStore().delete(userId);
You can use the endpoint https://www.googleapis.com/oauth2/v1/tokeninfo to determine if an OAuth2 token is still valid. More information is available in the OAuth2 guide.
Answer to the first question:
When using the Service object for retrieving calendar items from Google Calendar, the token are automatically verified. When they are invalid, they will be refreshed automatically, and stored in the datastore you provided to the flow.
this can also be done manually. A token is valid for 3600 seconds (one hour). When retrieving a token you get this value with the timestamp when it was issued. You could manually determine if a token is valid. If it is not valid call the following async method.
await credents.RefreshtokenAsync(CancellationToken.None);
This function gets you fresh tokens, and stores them in the datastore you provided.
You could check token with tokeninfo and if token is not valid:
- remove credential from datastore
- invoke new auth
protected void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException
{
UserService userService = UserServiceFactory.getUserService();
if (userService.isUserLoggedIn()) {
User user = userService.getCurrentUser();
log.info(String.format("LoggedUser: %s %s", user.getEmail(), user.getUserId()));
Credential credential = this.getCredential();
Tokeninfo tokenInfo = OAuth2Utils.getTokenInfo(credential, null);
if (tokenInfo != null)
log.info(String.format("Token expires in: %d", tokenInfo.getExpiresIn()));
else {
OAuth2Utils.deleteCredential(user.getUserId());
response.sendRedirect(request.getRequestURI()); // recall this servlet to require new user authorization
return;
}
}
public static Tokeninfo getTokenInfo(Credential credential, String accessToken) {
Oauth2 service = new Oauth2.Builder(new NetHttpTransport(), Constant.JSON_FACTORY, credential).setApplicationName(Constant.APP_NAME).build();
Tokeninfo tokenInfo = null;
try {
tokenInfo = service.tokeninfo().setAccessToken( accessToken == null ? credential.getAccessToken() : accessToken ).execute();
} catch (IOException e) {
log.warning("An error occurred: " + e);
}
return tokenInfo;
}

Categories

Resources