I am developing an android app for an online Newspaper company. The company already developed and hosted their APIs on the Google App Engine with OAuth 2.0 support.
So I am to develop an android app that communicates with their deployed backend API with OAuth 2.0 support and fetch the contents Response from their assigned Google API Explorer.
so according to the Google cloud endpoints documentation for android clients(like my app) trying to make authenticated calls to the endpoints with OAuth 2.0 support, I was directed to:
Configure my Android Client(my android app) to provide credentials to the service object
se the account picker to support user choice of login accounts.
I followed the instructions on the Google Cloud Endpoint website but I didn't understand the sample codes that was used to explain, so I tried coming up with this:
class EndpointsAsyncTask extends AsyncTask<Void, Void, Void>{
#Override
protected Void doInBackground(Void... params) {
HttpTransport httpTransport = new NetHttpTransport();
JsonFactory jsonFactory = new AndroidJsonFactory();
try {
GoogleCredential credential = new GoogleCredential.Builder()
.setTransport(httpTransport)
.setJsonFactory(jsonFactory)
.setServiceAccountId(CLIENT_EMAIL)
.setServiceAccountScopes(Collections.singleton("https://www.googleapis.com/auth/userinfo.email"))
.setServiceAccountPrivateKeyFromP12File(new File("file.p12"))
.build();
/*so now what do I do with the credential object
and how do I set the root URL (https://project-id-endpoints.appspot.com/_ah/api)
that the android client(this android app)
will connect to in the backend API call
*/
}
catch(GeneralSecurityException gse){
gse.printStackTrace();
}
catch(IOException ioe){
}
return null;
}
#Override
protected void onPostExecute(Void aVoid) {
super.onPostExecute(aVoid);
}
}
whenever I run my codes, the log report on the Google Developer Console is saying
"Uauthorised access" meaning the authentication call is not working...
Below is my code for opening a GET URL connection to one of the API service content:
public String open() throws IOException{
InputStream inputStream = null;
int len = 500;
try {
URL url = new URL("https://project-name-api-endpoints.appspot.com/_ah/api/core/v5.1.1/info/category/en");
URLConnection urlConnection = url.openConnection();
inputStream = urlConnection.getInputStream();
String content = readIt(inputStream, len);
return content;
}//end try
finally {
if(inputStream != null)
{
inputStream.close();
}
}
}
The question I have is:
What do I do with the credential object and how do I set the root URL (https://project-id-endpoints.appspot.com/_ah/api) that the android client(this android app) will connect to in the backend API call
The problem is that you are not using those credentials you set up when calling the endpoint. That connection you are making is completely unaware of all the OAuth settings and wonderful stuff you get from endpoints + Android.
Ideally what you need to do is:
1) Create the client libraries for Android from your endpoints (As explained here). Do not make "manual" (through URLConenction) calls to your endpoints, although it's technically possible it's absolutely not recommended.
2) With those libs (jars) included in your project all you need to do is call whatever method you need (as explained here) and the libraries will include all required authorization headers, etc based on your app settings. You don't need to worry about anything else regarding OAuth.
TIP: Make sure to include all of your developer's SHA debug signatures on the authorization on Google console. If you don't, you'll only be able to call the endpoints from your "prodcution" app.
Related
I am trying to automate the app publishing process to the Huawei store using the REST APIs as mentioned in the link.
https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Guides/agcapi-overview
I successfully received an access token but other operations(ex: getting the app info, getting the upload URL) are getting failed with the below status code and error.
403 client token authorization fail.
I did not write any code, I simply used the below sample code and updated clientId, clientSecret, appId.
https://developer.huawei.com/consumer/en/doc/development/AppGallery-connect-Examples/agcapi-publish_api_code
What could go wrong?
Update:
Set Project to N/A to define the API client as a team-level one.
Set Roles to Administrator
Please check whether your client role is Administrator.
The role of a member determines the permissions in AppGallery Connect. Administrator has most operation permissions, being able to add member accounts and assigning permissions to them. For details about the mapping between roles and permissions, please refer to Roles and Permissions.
Work with AppGallery Connect API
To call the AppGallery Connect API, you need to obtain authorization from the AppGallery Connect server in advance using either of the following modes: API client mode and OAuth client mode. To call the AppGallery Connect API in the API client mode, you need to manage your API client in AppGallery Connect. An API client can be managed only by your team account holder. The basic process is as follows:
A. Creating an API Client
Sign in to AppGallery Connect and select Users and permissions.
Go to Api Key > AppGalleryConnect API from the navigation tree on the left and click Create.
Set Name to a customized client name, set Roles to the corresponding role, and click Confirm.
After the client is successfully created, record the values of Client ID and Key in the client information list.
Check the screenshot below:
B. Obtaining the Token for Accessing the API
After an API client is created, the API client needs to be authenticated in AppGallery Connect. After the authentication is successful, the API client obtains an access token for accessing the AppGallery Connect API. With this access token, you can access the AppGallery Connect API.
To obtain an access token, you need to add the code for calling the Obtaining a Token API to your app program.
public static String getToken(String domain, String clientId, String clientSecret) {
String token = null;
try {
HttpPost post = new HttpPost(domain + "/oauth2/v1/token");
JSONObject keyString = new JSONObject();
keyString.put("client_id", "18893***83957248");
keyString.put("client_secret", "B15B497B44E080EBE2C4DE4E74930***52409516B2A1A5C8F0FCD2C579A8EB14");
keyString.put("grant_type", "client_credentials");
StringEntity entity = new StringEntity(keyString.toString(), Charset.forName("UTF-8"));
entity.setContentEncoding("UTF-8");
entity.setContentType("application/json");
post.setEntity(entity);
CloseableHttpClient httpClient = HttpClients.createDefault();
HttpResponse response = httpClient.execute(post);
int statusCode = response.getStatusLine().getStatusCode();
if (statusCode == HttpStatus.SC_OK) {
BufferedReader br =
new BufferedReader(new InputStreamReader(response.getEntity().getContent(), Consts.UTF_8));
String result = br.readLine();
JSONObject object = JSON.parseObject(result);
token = object.getString("access_token");
}
post.releaseConnection();
httpClient.close();
} catch (Exception e) {
}
return token;
}
After obtaining an access token, you can use the access token for identity authentication when accessing the AppGallery Connect API. The default validity period of an access token is 48 hours. If an access token expires, you need to obtain a new access token.
C. Accessing the API
After obtaining an access token, you can use the access token to call the AppGallery Connect API to complete function development.
As said in this comment, Once I set the project to NA it started working.
Thanks to #shirley
Background
Suppose I want to search for some email addresses on Github using code (Kotlin/Java).
The problem
I've succeeded doing it without any login token, but as I've read it's limited to just 10 queries per minute, and if I have a token from the user (from login to Github), it adds 30 queries per minute.
To get information from Github without login, I use OKHTTP to reach this (found from here):
https://api.github.com/search/users?q=$email+in:EMAIL_ADDRESS_HERE
And if I could use a token, it would probably be:
https://api.github.com/search/users?q=$email+in:EMAIL_ADDRESS_HERE&access_token=$TOKEN_HERE
But I don't get how to get this token from the user. I can find how to make one for myself, using the website itself.
What I've found and tried
I asked Github about how it's done, but they showed me some curl code, which sadly I'm not familiar with and I have no idea how it's done there and how to convert it to Kotlin/Java. I tried to read for alternatives, but then I've found some missing information about how it's done (missing prior data that is required for the parameters). I don't even get if it's working by using a WebView or directly contacting the Github servers. If it's directly contacting the Github servers, doesn't it mean that I need to have userName&password EditTexts for the user?
This is the code I've seen on the tutorial:
JsonFactory jsonFactory = new JacksonFactory();
HttpTransport httpTransport = new NetHttpTransport();
AuthorizationCodeFlow flow = new AuthorizationCodeFlow.Builder(
BearerToken.authorizationHeaderAccessMethod(),
httpTransport, jsonFactory,
new GenericUrl("https://github.com/login/oauth/access_token"),
new ClientParametersAuthentication( /* Client ID and Secret */ ),
/* Client ID */
"https://github.com/login/oauth/authorize").build();
TokenResponse tokenResponse = flow
.newTokenRequest(code)
.setScopes(Collections.singletonList("user:email"))
.setRequestInitializer(new HttpRequestInitializer() {
#Override
public void initialize(HttpRequest request) throws IOException {
request.getHeaders().setAccept("application/json");
}
}).execute();
So, what's missing here is "Client ID and Secret" , "code", and the part that the user himself provides : the user-name and password to get the token.
The questions
How do I get into the whole login process, and finally get a token to be used?
Should it use a WebView to offer the user with all options to login, or should I use my own UI to put userName&password?
Github supports OAuth 2.0 for authentication. You can setup your application on Github to enable this feature as per this example and you can also use AppAuth to handle the authentication journey.
I am having trouble getting a one-time authorization code from Google. I am attempting to get the authorization code from an Android client so that I can send it to my Rails backend (web client).
In my Google Cloud Developer Console I have an application with two Client IDs:
Client ID for web application (for my rails backend)
Client ID for Android application (for my android client). The SHA1 used is from ~/.android/debug.keystore
Suppose the Web Application Client ID is 12345.apps.googleusercontent.com
Suppose the Android Client ID is 67890.apps.googleusercontent.com
This is some of my code:
private final static String WEB_CLIENT_ID = "12345.apps.googleusercontent.com";
private final static String GOOGLE_CALENDAR_API_SCOPE = "audience:server:client_id:" + WEB_CLIENT_ID;
private void getAndUseAuthToken(final String email) {
AsyncTask task = new AsyncTask<String, Void, String>() {
#Override
protected String doInBackground(String... emails) {
try {
return GoogleAuthUtil.getToken(AddExternalCalendarsActivity.this, emails[0], GOOGLE_CALENDAR_API_SCOPE);
} catch (UserRecoverableAuthException e) {
startActivityForResult(e.getIntent(), IntentConstants.REQUEST_GOOGLE_AUTHORIZATION);
} catch (IOException e) {
e.printStackTrace();
} catch (GoogleAuthException e) {
e.printStackTrace();
} catch (Exception e) {
e.printStackTrace();
}
return null;
}
#Override
protected void onPostExecute(String authToken) {
if (authToken != null) {
saveTokenAndGetCalendars(email, authToken);
}
}
};
String[] emails = new String[1];
emails[0] = email;
task.execute(emails);
}
Some additional notes
I am hitting the GoogleAuthException and receiving "Unknown" as the message detail
I'm unable to add additional members in the permissions of the Google Cloud Console for this project - when adding a new member a popup appears with "Server Error. Whoops! Our bad.". I have sent feedback to Google twice.
I'm referring to this documentation. Notice the quote below. By "fixed", are they saying that I do not need to prepend audience:server:client_id in my GOOGLE_CALENDAR_API_SCOPE variable? I've tried both with and without and still getting the same GoogleAuthException.
In this situation, the Android app can call the
GoogleAuthUtil.getToken() method on behalf of any of the Google
accounts on the device, and with a scope argument value of
audience:server:client_id:9414861317621.apps.googleusercontent.com.
The prefix audience:server:client_id: is fixed, and the rest of the
scope string is the client ID of the web component.
If I use this scope, I can authenticate with google from device. However, the documentation I've read suggests that I need to use the server web client id (which is in the same google console project as the android client id) in the scope in order for the server to hit the google api on behalf of the user who authorized it on the android client:
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:https://www.googleapis.com/auth/calendar";
UPDATE 1
I originally added in answer:
The reason for my first problem - I am hitting the GoogleAuthException and receiving "Unknown" as the message detail - was a mistake I made when adding the android client id in the cloud console. The SHA1 was correct but I did not type the package name correctly. I used com.company.app when my android package is actually com.company.android.app.
The code in the original question works fine. Just make sure you have all the necessary clients in your Google Cloud Console project.
But another problem still exists. When I send the one-time authorization token returned from GoogleAuthUtil.getToken() to the Rails backend, and then try to exchange it for an access_token and refresh_token, I get the follow:
Signet::AuthorizationError:
Authorization failed. Server message:
{
"error" : "invalid_grant"
}
This google documentation and several SO posts suggests that I need to set access_type=offline. But I think that is when you are requesting the one-time authorization code and offline access from a Web Server. I'm trying to request the one-time authorization code from an Android client and send it to the web server.
Is this possible with GoogleAuthUtil.getToken()?
UPDATE 2
Google Plus login must be in the scope even if you're only trying to access the calendar:
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:server:client_id:" + WEB_CLIENT_ID + ":api_scope:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar";
This SO post was helpful. Also, Google's Cross Client identity documentation does state:
[Note: This policy in being rolled out gradually. For the moment, when access tokens are involved, it only applies when the requested scopes include https://www.googleapis.com/auth/plus.login.]
I'll summarize in an answer if the token exchange works on Rails backend.
Two things solved this for me:
Make sure the Android and Web Client IDs are setup in correctly in the same Google Cloud Console project.
Use the correct scope. Plus login is required even if you're only accessing the calendar api:
// the id of the web server that is exchanging the auth code for access and refresh tokens
private final static String WEB_CLIENT_ID = "12345.apps.googleusercontent.com";
private final static String GOOGLE_CALENDAR_API_SCOPE = "oauth2:server:client_id:" + WEB_CLIENT_ID + ":api_scope:https://www.googleapis.com/auth/plus.login https://www.googleapis.com/auth/calendar";
Here's what I've done:
final String authToken = GoogleAuthUtil.getTokenWithNotification (this.context, account.name, "oauth2:" + AuthUtils.profileScope, new Bundle (),
Contract.authority, new Bundle ());
But you plain getToken should work the same for a foreground activity.
Take this token, send it to the server in an a way you can use it like an HTTP header (over HTTPS). As long as the server scope is a subset of the scope used to acquire the token, you shouldn't have a problem. The server uses the server id and the android client uses the android client id.
You should set your scope to either:
oauth2:https://www.googleapis.com/auth/calendar
or
oauth2:https://www.googleapis.com/auth/calendar.readonly
See https://developers.google.com/google-apps/calendar/auth
Edit: OK I see what you are trying to do. The scope value is a space separated list so you would likely need to append audience:server:client_id: to your scope like:
"oauth2:https://www.googleapis.com/auth/calendar audience:server:client_id:12345-your-web-component-client-id"
I have a simple application that lets a user draw pictures. There are Android, IOS, and web-based versions. I also let users store their pictures on our App-engine servers and I want them to be able to collaborate with other users. I want to use Google accounts for authentication and the basis of some permission scheme.
I do not know how to validate/authenticate a user’s Google account on android (or IOS). I am hoping somebody can help or point me in the right direction. Here is what I understand so far:
On the Web-based client, I just use Google-web toolkits UserService. However for my app-engine servlets i'm not sure what I should use. Currently the servlets have code like this:
public void doGet(HttpServletRequest request, HttpServletResponse response)
throws IOException, ServletException
{
OAuthService oauth = null;
oauth = OAuthServiceFactory.getOAuthService();
User user = oauth.getCurrentUser();
// Do stuff
}
In my android application I think I'm supposed to do something like:
1) Get the Account from the AccountManager
2) Call:
accountManager.getAuthToken
(account, // Account
"oauth2:https://www.googleapis.com/auth/userinfo.profile",//AUTH Token Type
null, // Options
this, // Activity
new MyAccountsManagerCallBack(), // call-back
null); // Handler
This will give me authorization token.
3) ?? profit ??
This is where I am lost. Do I send this authorization token as a clear-text query parameter to my app-engine server, then make a request from the web server to the userinfo/profile api? That doesn’t seem secure.
Is there some way to make the pervious code with OAuthService work?
The samples for OAuth 2 use the Google task API, however I want to use my app-engine API. I’ve found information for OAuth 1 using cookies, webviews, title, etc, but nothing on OAuth 2, and none of them really tell me how to validate server side.
I really have no clue what I should be doing here. I would appreciate any assistance.
To clarify, this is an example of my java servlet served on app engine:
public class ServletSecureData extends HttpServlet {
#Override
protected void doGet(HttpServletRequest request, HttpServletResponse response)
throws ServletException, IOException {
UserService usersrvc = null;
usersrvc = UserServiceFactory.getUserService();
User user = usersrvc.getCurrentUser();
if(user == null)
throw new IOException();
Random r = new Random(System.currentTimeMillis());
int num = r.nextInt(10);
PrintWriter out = response.getWriter();
out.printf("Security !! %s radioactive man! %d", user.getEmail(), num);
out.close();
}
}
This servlet was protected with a security constraint defined in the web.xml file. I wanted to be able to access this servlet using an android client. I thought that I had to used Oauth but it turned out I needed to use an older deprecated service ClientLogin
I based my implementation off the code from this site: http://blog.notdot.net/2010/05/Authenticating-against-App-Engine-from-an-Android-app
I followed this Push Notification tutorial.
When I finish the tutorial, I found out that two classes did not use which were AuthenticationUtil and MessageUtil.
Moreover, Google Login, this link seem unworkable. Second, This is the token id for the Android device or the account only? i thought push notification is push message to token id of Android device.
On the others hand, i found out that the bundle.putExtra(key, value), all the keys did not use it. For example put "app" but in C2DMRegistrationReceiver() did not get the key.
In this sendRegistrationIdToServer(), it seem like never being call out.
I am being confused by this tutorial about push notification.
Who can guide me or give me workable tutorial or example about push notification?
I would like pro to point out what's my wrong.
This is my registration id
public static final String[] REGISTRATION_ID = {
"APA91bFV6MwoAH0UNop69PZ2liKpSBUHSHenIuPzh44_6GdGKzVCLvoH_NM31eMZMVLZi-SAIFwP4iZaE72dSWkIh3GaD0RQYpPm9zO0ARWmnoxFyyyreL_KpQ9Qd_p0broclT12RhA4Ymk0cBT00CmpsbSHIwyxig",
"APA91bEwmxgvs7zNbKC4p0n4DoTEM73DTihnQgBOP8Gxhf2sVW-fgltugDgS1Fh2S4KvN1wQHbMNJEIzieJ9F1nNPqs3NWeKGbB7IBYpKJq4xmN4Z7uzkjZQQUKGD8jW--AwfQY5McINBto9GAL_87_u5WkIq-kx3g",
"APA91bH63Zgxn1X_MZ56UzrlRpffvmiLAIsqxvBUTMUHP2O_MT_VU9Ork_edXKHlml-PZSkjKEqdk8EKv5HvxbPdK1Vva3WtmqsPZfhXzEbtNIrwrqIvvRf7hL835rDc4t2E8EKUBj1dX2ta0OxY5pY3Xlhkyb1sBg",
"APA91bGqT5Wo6eUaMdqT5r9TlGbKSX6GN2W6r-RjrRXz5T5v3j87flcQRyfSajmMNGXuPVe-fwZydRmvyYu63tWnYohDmpJyKkXOxs8Vx6P_FplFQ__ufR_hekwqGOspeUc6bfc8fhbMPGN3Ft9l-bfrghJwwk79jw"};
Messageutil
public static int sendMessage(String auth_token, String registrationId,
String message, String title) throws IOException {
StringBuilder postDataBuilder = new StringBuilder();
postDataBuilder.append(PARAM_REGISTRATION_ID).append("=")
.append(registrationId);
postDataBuilder.append("&").append(PARAM_COLLAPSE_KEY).append("=")
.append("0");
postDataBuilder.append("&").append("data.payload").append("=")
.append(URLEncoder.encode(message, UTF8));
postDataBuilder.append("&").append("data.title").append("=")
.append(URLEncoder.encode(title, UTF8));
byte[] postData = postDataBuilder.toString().getBytes(UTF8);
// Hit the dm URL.
URL url = new URL("https://android.clients.google.com/c2dm/send");
HttpsURLConnection
.setDefaultHostnameVerifier(new CustomizedHostnameVerifier());
HttpsURLConnection conn = (HttpsURLConnection) url.openConnection();
conn.setDoOutput(true);
conn.setUseCaches(false);
conn.setRequestMethod("POST");
conn.setRequestProperty("Content-Type",
"application/x-www-form-urlencoded;charset=UTF-8");
conn.setRequestProperty("Content-Length",
Integer.toString(postData.length));
conn.setRequestProperty("Authorization", "GoogleLogin auth="
+ auth_token);
OutputStream out = conn.getOutputStream();
out.write(postData);
out.close();
int responseCode = conn.getResponseCode();
return responseCode;
}
private static class CustomizedHostnameVerifier implements HostnameVerifier {
public boolean verify(String hostname, SSLSession session) {
return true;
}
}
Messagesender
public static void main(String[] args) throws IOException {
String token = AuthenticationUtil.getToken(SecureStorage.USER,
SecureStorage.PASSWORD);
for (int i = 0; i < ServerConfiguration.REGISTRATION_ID.length; i++) {
MessageUtil.sendMessage(token,
ServerConfiguration.REGISTRATION_ID[i], "12358",
"印尼羽賽:馬2單1雙止步入選賽");
System.out.println(ServerConfiguration.REGISTRATION_ID[i]
.toString());
}
System.out.println(token);
}
You should follow this tutorial for android c2dm implementation.
For server, you could use anything, some code sample available on internet. For server I used .NET library called "C2DM Sharp"
The process is very simple like...
First register your google email for c2dm on - https://developers.google.com/android/c2dm/signup
Run the android application on Android 2.2 or higher and send the registrationID which you can get in "C2DMReceiver" or get that ID by writting in LOG
Use the server code, for testing purpose paste your registrationID in Server code and you are ready to go.
The basic flow of C2DM is ...
Register Phone for C2DM -> Get registrationID -> Send registrationID to server -> Server usees google id to get auth token -> server use registrationID and auth token to send message.
Google Cloud Messaging for Android
Important: C2DM(Android Cloud to Device Messaging Framework) has been officially deprecated as of June 26, 2012. This means that C2DM has stopped accepting new users and quota requests. No new features will be added to C2DM. However, apps using C2DM will continue to work. Existing C2DM developers are encouraged to migrate to the new version of C2DM, called Google Cloud Messaging for Android (GCM). See the C2DM-to-GCM Migration document for more information. Developers must use GCM for new development.
Kindly check the following link:
http://developer.android.com/guide/google/gcm/index.html
Please see my question here:
C2DM server. Should we use OAuth now?
There is some info and link to google group with answer.
In short..
Seems like OAuth2 will work, but I didn't find any working sample to implement
Client Login works and this is place where my confusion was. You need to:
Set up google account. I picked something like mynamec2dm#gmail.com.
Register for C2DM using this email. This is important.
On server-side use email/password you setup to get auth token.
Use this token to send messages from server.
Everything else is just like in all tutorials around.