I have an Android application with GAE server. I tried to authenticate the user as described on developers.google.com, I added the user parameter to the endpoint methods etc. I get a User which is not null, but this method getUserId() returns null. It is similar to this, rather old problem:
Function User.getUserId() in Cloud endpoint api returns null for a user object that is not null
But I still don't know how to work around it. How do you handle this error? Have you ever encountered it?
In android client here's what I did (its simplified) :
credentials = GoogleAccountCredential.usingAudience(getApplicationContext(), "server:client_id:" + WEB_CLIENT_ID);
credentials.setSelectedAccountName(accountName);
WarriorEntityEndpoint.Builder endpointBuilder = new WarriorEntityEndpoint.Builder(AndroidHttp.newCompatibleTransport(), new GsonFactory(), credentials);
warriorEntityEndpoint = endpointBuilder.build();
new AsyncTask<Void, Void, Void>() {
#Override
protected Void doInBackground(Void... params) {
// TODO Auto-generated method stub
try {
warriorEntityEndpoint.getWarrior().execute();
} catch (Exception e) {
}
return null;
}
}.execute();
And on GAE:
#Api(name = "warriorEntityEndpoint", namespace = #ApiNamespace(ownerDomain = "szpyt.com", ownerName = "szpyt.com", packagePath = "mmorpg.monsters"),
version = "version1",
scopes = {"https://www.googleapis.com/auth/userinfo.email", "https://www.googleapis.com/auth/userinfo.profile"},
clientIds = {Constants.ANDROID_CLIENT_ID, Constants.WEB_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public class WarriorEntityEndpoint {
private static final Logger log = Logger.getLogger(WarriorEntityEndpoint.class.getName());
#ApiMethod(name = "getWarrior")
public WarriorEntity getWarrior(User user) throws OAuthRequestException, IOException {
log.log(Level.SEVERE, "this gives correct email: " + user.getEmail());
log.log(Level.SEVERE, "this is null: " + user.getUserId());
I have also another very important question: is this user authenticated, if getMail() gives me correct account, but getUserId() gives null? I read that user object should be null if it was not authenticated but I am not sure any more...
I'm using App engine SDK 1.8.7. I'm testing on a real device and backend deployed to GAE.
I asked the same question a while ago and got an answer. See link:
Function User.getUserId() in Cloud endpoint api returns null for a user object that is not null
The cause is a bug on appengine.
I guess there is no good solution for it right now. I store e-mail as a normal property and remove it from default fetch group, I use long as a primary key (generated by AppEngine) and I query the entity by the e-mail property. I don't like my solution, I'll accept ( and implement :) ) a better one if anyone can provide.
This is a known issue which has been filed with google, I've attached the issue link below.
There are two workarounds (1) save the user and read back from the store, if it refers to a valid account the user id will be populated (this sucks because you pay the saving / loading / deletion cost for each API access that is authenticated even if it is tiny, and obviously some performance cost) and (2) you could use the google+ ID but that is NOT the same as the user id.
This is extremely frustrating and there is currently no ETA as they are working on some fundamental issues with the auth design as far as I understand.
Please, vote for that issue by starring it. You can find all the information here
https://code.google.com/p/googleappengine/issues/detail?can=2&start=0&num=100&q=&colspec=ID%20Type%20Component%20Status%20Stars%20Summary%20Language%20Priority%20Owner%20Log&groupby=&sort=&id=8848
And here is the current formally approved workaround [(1) above], which you can also find in the link above, but for ease it's here: How can I determine a user_id based on an email address in App Engine?
For workaround (2) mentioned above, you can look at the first link, and go to post #39.
Related
I have an Android application in which I'm using Azure AD B2C to authenticate users. Users login and logout of the application as needed. I would like to give the user the option to delete their own account.
I understand that I need to use the Azure AD Graph API to delete the user. This is what I have so far:
According to this link, it looks like deleting a user from a personal account (which is what the B2C users are using) is not possible. Is that correct?
Here's my code snippet for the Graph API call. Feel free to ignore it if I'm off track and there is a better way to solve this.
I believe I need a separate access token than what my app currently has (as the graph API requires other API consent). So, I'm getting the access token as follows:
AcquireTokenParameters parameters = new AcquireTokenParameters.Builder()
.startAuthorizationFromActivity(getActivity())
.fromAuthority(B2CConfiguration.getAuthorityFromPolicyName(B2CConfiguration.Policies.get("SignUpSignIn")))
.withScopes(B2CConfiguration.getGraphAPIScopes())
.withPrompt(Prompt.CONSENT)
.withCallback(getGraphAPIAuthCallback())
.build();
taxApp.acquireToken(parameters);
In the getGraphAPIAuthCallback() method, I'm calling the Graph API using a separate thread (in the background):
boolean resp = new DeleteUser().execute(authenticationResult.getAccessToken()).get();
Finally, in my DeleterUser() AsyncTask, I'm doing the following:
#Override
protected Boolean doInBackground(String... aToken) {
final String asToken = aToken[0];
//this method will be running on background thread so don't update UI from here
//do your long running http tasks here,you dont want to pass argument and u can access the parent class' variable url over here
IAuthenticationProvider mAuthenticationProvider = new IAuthenticationProvider() {
#Override
public void authenticateRequest(final IHttpRequest request) {
request.addHeader("Authorization",
"Bearer " + asToken);
}
};
final IClientConfig mClientConfig = DefaultClientConfig
.createWithAuthenticationProvider(mAuthenticationProvider);
final IGraphServiceClient graphClient = new GraphServiceClient.Builder()
.fromConfig(mClientConfig)
.buildClient();
try {
graphClient.getMe().buildRequest().delete();
} catch (Exception e) {
Log.d(AccountSettingFragment.class.toString(), "Error deleting user. Error Details: " + e.getStackTrace());
}
return true;
}
Currently, my app fails when trying to get an access token with a null pointer exception:
com.microsoft.identity.client.exception.MsalClientException: Attempt to invoke virtual method 'long java.lang.Long.longValue()' on a null object reference
Any idea what I need to do to provide the user the option to users to delete their own account? Thank you!
Thanks for the help, #allen-wu. Due to his help, this azure feedback request and this azure doc, I was able to figure out how to get and delete users silently (without needing intervention).
As #allen-wu stated, you cannot have a user delete itself. So, I decided to have the mobile app call my server-side NodeJS API when the user clicks the 'Delete Account' button (as I do not want to store the client secret in the android app) and have the NodeJS API call the Azure AD endpoint to delete the user silently. The one caveat is that admin consent is needed the first time you try to auth. Also, I have only tested this for Graph API. I'm not a 100% sure if it works for other APIs as well.
Here are the steps:
Create your application in your AAD B2C tenant. Create a client secret and give it the following API permissions: Directory.ReadWrite.All ;
AuditLog.Read.All (I'm not a 100% sure if we need the AuditLog permission. I haven't tested without it yet).
In a browser, paste the following link:
GET https://login.microsoftonline.com/{tenant}/adminconsent?
client_id=6731de76-14a6-49ae-97bc-6eba6914391e
&state=12345
&redirect_uri=http://localhost/myapp/permissions
Login using an existing admin account and provide the consent to the app.
Once you've given admin consent, you do not have to repeat steps 1-3 again. Next, make the following call to get an access token:
POST https://login.microsoftonline.com/{B2c_tenant_name}.onmicrosoft.com/oauth2/v2.0/token
In the body, include your client_id, client_secret, grant_type (the value for which should be client_credentials) and scope (value should be 'https://graph.microsoft.com/.default')
Finally, you can call the Graph API to manage your users, including deleting them:
DELETE https://graph.microsoft.com/v1.0/users/{upn}
Don't forget to include the access token in the header. I noticed that in Postman, the graph api had a bug and returned an error if I include the word 'Bearer' at the start of the Authorization header. Try without it and it works. I haven't tried it in my NodeJS API yet, so, can't comment on it so far.
#allen-wu also suggested using the ROPC flow, which I have not tried yet, so, cannot compare the two approaches.
I hope this helps!
There is a line of code: graphClient.getUsers("").buildRequest().delete();
It seems that you didn't put the user object id in it.
However, we can ignore this problem because Microsoft Graph doesn't allow a user to delete itself.
Here is the error when I try to do it.
{
"error": {
"code": "Request_BadRequest",
"message": "The principal performing this request cannot delete itself.",
"innerError": {
"request-id": "8f44118f-0e49-431f-a0a0-80bdd954a7f0",
"date": "2020-06-04T06:41:14"
}
}
}
I've been wracking my brain these past two days to try and understand how to use the authentication built into ASP.NET's WebAPI 2 using Google as an external authentication, and not being familiar with OAuth 2, I'm quite lost. I have followed this tutorial to set up the sign-in button on my Android client and send the "idToken" to the Web API. I've also followed this (now out of date) tutorial on setting up Google as an external login.
The problem happens when I try to send it I get {"error":"unsupported_grant_type"} as a response. Some other tutorials lead me to believe that the POST to mysite.com/token does not contain the correct data. This means I'm either building the request incorrectlyon the client, I'm somehow handling it incorrectly on the backend, I'm sending it to the wrong url, or I'm doing something entirely else wrong.
I found this SO answer which says to get a URL from /api/Accounts/ExternalLogins, but the sign-in button already gives me the access token that would supply to me (if I understand that correctly).
If someone could help me out here on what the exact process should be from start to finish, that would be amazing.
UPDATE: Okay, so here are some things that I've learned since I asked this question.
website.com/token URI is the redirect for the built in OAuth server in the WebAPI2 template. This is not useful for this particular problem.
The id_token is an encoded JWT token.
The website.com/signin-google URI is the redirect for normal Google login, but does not accept these tokens.
I may have to write my own AuthenticationFilter that uses the Google Client library to authorize through the Google API.
UPDATE 2: I'm still working on getting this AuthenticationFilter Implementation. Things seem to be going well at this point, but I'm getting stuck on some things. I've been using this example to get the token verification code, and this tutorial to get the AuthenticationFilter code. The result is a mix of both of them. I'll post it here as an answer once it's complete.
Here are my current problems:
Producing an IPrincipal as output. The verification example makes a ClaimPrincipal, but the AuthenticationFilter example code uses a UserManager to match the username to an existing user and returns that principal. The ClaimsPrincipal as created in the verification example directly does not auto-associate with the existing user, so I need to attempt to match some element of the claims to an existing user. So how do I do that?
I still have an incomplete idea of what a proper flow for this is. I'm currently using the Authentication header to pass my id_token string using a custom scheme: "goog_id_token". The client must send their id_token for every method called on the API with this custom AuthenticationFilter. I have no idea how this would usually be done in a professional environment. It seems like a common enough use case that there would be tons of information about it, but I haven't seen it. I have seen the normal OAuth2 flow, and since I'm only using an ID Token, and not an Access Token I'm a bit lost on what an ID Token is supposed to be used for, where it falls in a flow, and where it's supposed to live in an HTTP packet. And because I didn't know these things, I've kind of been making it up as I go along.
Wow, I did it. I figured it out. I... I can't believe it.
As metioned in my question Update 2, this code is assembled from Google's official API C# example and Microsoft's Custom AuthenticationFilter tutorial and code example. I'm going to paste the AuthorizeAsync() here and go over what each block of code does. If you think you see an issue, please feel free to mention it.
public async Task AuthenticateAsync(HttpAuthenticationContext context, CancellationToken cancellationToken)
{
bool token_valid = false;
HttpRequestMessage request = context.Request;
// 1. Look for credentials in the request
//Trace.TraceInformation(request.ToString());
string idToken = request.Headers.Authorization.Parameter.ToString();
The client adds the Authorization header field with the scheme followed by a single space, followed by the id token. It looks something like Authorization: id-token-goog IaMS0m3.Tok3nteXt.... Putting the ID token in the body as given in the google documentation made no sense in this filter so I decided to put it in the header. For some reason it was difficult to pull custom headers from the HTTP packets so I just decided to use the Authorization header with a custom scheme followed by the ID token.
// 2. If there are no credentials, do nothing.
if (idToken == null)
{
Trace.TraceInformation("No credentials.");
return;
}
// 3. If there are credentials, but the filter does not recognize
// the authentication scheme, do nothing.
if (request.Headers.Authorization.Scheme != "id-token-goog")
// Replace this with a more succinct Scheme title.
{
Trace.TraceInformation("Bad scheme.");
return;
}
This whole point of a filter is to ignore requests that the filter doesn't govern (unfamiliar auth schemes, etc), and make judgement on requests that it's supposed to govern. Allow valid authentication to pass to the downstream AuthorizeFilter or directly to the Controller.
I made up the scheme "id-token-goog" because I had no idea if there was an existing scheme for this use case. If there is, somebody please let me know and I'll fix it. I guess it doesn't particularly matter at the moment as long as my clients all know the scheme.
// 4. If there are credentials that the filter understands, try to validate them.
if (idToken != null)
{
JwtSecurityToken token = new JwtSecurityToken(idToken);
JwtSecurityTokenHandler jsth = new JwtSecurityTokenHandler();
// Configure validation
Byte[][] certBytes = getCertBytes();
Dictionary<String, X509Certificate2> certificates =
new Dictionary<String, X509Certificate2>();
for (int i = 0; i < certBytes.Length; i++)
{
X509Certificate2 certificate =
new X509Certificate2(certBytes[i]);
certificates.Add(certificate.Thumbprint, certificate);
}
{
// Set up token validation
TokenValidationParameters tvp = new TokenValidationParameters()
{
ValidateActor = false, // check the profile ID
ValidateAudience =
(CLIENT_ID != ConfigurationManager
.AppSettings["GoogClientID"]), // check the client ID
ValidAudience = CLIENT_ID,
ValidateIssuer = true, // check token came from Google
ValidIssuer = "accounts.google.com",
ValidateIssuerSigningKey = true,
RequireSignedTokens = true,
CertificateValidator = X509CertificateValidator.None,
IssuerSigningKeyResolver = (s, securityToken, identifier, parameters) =>
{
return identifier.Select(x =>
{
// TODO: Consider returning null here if you have case sensitive JWTs.
/*if (!certificates.ContainsKey(x.Id))
{
return new X509SecurityKey(certificates[x.Id]);
}*/
if (certificates.ContainsKey(x.Id.ToUpper()))
{
return new X509SecurityKey(certificates[x.Id.ToUpper()]);
}
return null;
}).First(x => x != null);
},
ValidateLifetime = true,
RequireExpirationTime = true,
ClockSkew = TimeSpan.FromHours(13)
};
This is all unchanged from the Google example. I have almost no idea what it does. This basically does some magic in creating a JWTSecurityToken, a parsed, decoded version of the token string, and sets up the validation parameters. I'm not sure why the bottom portion of this section is in it's own statement block, but it has something to do with the CLIENT_ID and that comparison. I'm not sure when or why the value of CLIENT_ID would ever change, but apparently it's necessary...
try
{
// Validate using the provider
SecurityToken validatedToken;
ClaimsPrincipal cp = jsth.ValidateToken(idToken, tvp, out validatedToken);
if (cp != null)
{
cancellationToken.ThrowIfCancellationRequested();
ApplicationUserManager um =
context
.Request
.GetOwinContext()
.GetUserManager<ApplicationUserManager>();
Get the user manager from the OWIN context. I had to dig around in context intellisense until I found GetOwinCOntext(), and then found that I had to add using Microsoft.Aspnet.Identity.Owin; in order to add the partial class that included the method GetUserManager<>().
ApplicationUser au =
await um
.FindAsync(
new UserLoginInfo(
"Google",
token.Subject)
);
This was the very last thing I had to fix. Again, I had to dig through um Intellisense to find all of the Find functions and their overrides. I had noticed from the Identity Framework-created tables in my database that there is one called UserLogin, whose rows contain a provider, a provider key, and a user FK. The FindAsync() takes a UserLoginInfo object, which contains only a provider string and a provider key. I had a hunch that these two things were now related. I had also recalled that there was a field in the token format that included a key-looking field that was a long number that started with a 1.
validatedToken seems to be basically empty, not null, but an empty SecurityToken. This is why I use token instead of validatedToken. I'm thinking there must be something wrong with this, but since the cp is not null, which is a valid check for a failed validation, it makes enough sense that the original token is valid.
// If there is no user with those credentials, return
if (au == null)
{
return;
}
ClaimsIdentity identity =
await um
.ClaimsIdentityFactory
.CreateAsync(um, au, "Google");
context.Principal = new ClaimsPrincipal(identity);
token_valid = true;
Here I have to create a new ClaimsPrincipal since the one created above in validation is empty (apparently that's correct). Took a guess on what the third parameter of CreateAsync() should be. It seems to work that way.
}
}
catch (Exception e)
{
// Multiple certificates are tested.
if (token_valid != true)
{
Trace.TraceInformation("Invalid ID Token.");
context.ErrorResult =
new AuthenticationFailureResult(
"Invalid ID Token.", request);
}
if (e.Message.IndexOf("The token is expired") > 0)
{
// TODO: Check current time in the exception for clock skew.
Trace.TraceInformation("The token is expired.");
context.ErrorResult =
new AuthenticationFailureResult(
"Token is expired.", request);
}
Trace.TraceError("Error occurred: " + e.ToString());
}
}
}
}
The rest is just exception catching.
Thanks for checking this out. Hopefully you can look at my sources and see which components came from which codebase.
I have been doing extensive research on how to authenticate your client (Android, iOS, web-app) with Cloud Endpoints without requiring your user to use their Google account login the way the documentation shows you.
The reason for this is that I want to secure my API or "lock it down" to only my specified clients. Sometimes I will have an app that does not have a user login. I would hate to pester my user to now sign in just so my API is secure. Or other times, I just want to manage my own users like on a website and not use Google+, Facebook, or whatever else login authentication.
To start, let me first show the way you can authenticate your Android app with your Cloud Endpoints API using the Google Accounts login as specified in the documentation. After that I will show you my findings and a potential area for a solution which I need help with.
(1) Specify the client IDs (clientIds) of apps authorized to make requests to your API backend and (2) add a User parameter to all exposed methods to be protected by authorization.
public class Constants {
public static final String WEB_CLIENT_ID = "1-web-apps.apps.googleusercontent.com";
public static final String ANDROID_CLIENT_ID = "2-android-apps.googleusercontent.com";
public static final String IOS_CLIENT_ID = "3-ios-apps.googleusercontent.com";
public static final String ANDROID_AUDIENCE = WEB_CLIENT_ID;
public static final String EMAIL_SCOPE = "https://www.googleapis.com/auth/userinfo.email";
}
import com.google.api.server.spi.auth.common.User; //import for the User object
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
public class MyEndpoint {
/** A simple endpoint method that takes a name and says Hi back */
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, User user) throws UnauthorizedException {
if (user == null) throw new UnauthorizedException("User is Not Valid");
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
(3) In Android call the API method in an Asynctask making sure to pass in the credential variable in the Builder:
class EndpointsAsyncTask extends AsyncTask<Pair<Context, String>, Void, String> {
private static MyApi myApiService = null;
private Context context;
#Override
protected String doInBackground(Pair<Context, String>... params) {
credential = GoogleAccountCredential.usingAudience(this,
"server:client_id:1-web-app.apps.googleusercontent.com");
credential.setSelectedAccountName(settings.getString(PREF_ACCOUNT_NAME, null));
if(myApiService == null) { // Only do this once
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), credential)
// options for running against local devappserver
// - 10.0.2.2 is localhost's IP address in Android emulator
// - turn off compression when running against local devappserver
.setRootUrl("http://<your-app-engine-project-id-here>/_ah/api/")
.setGoogleClientRequestInitializer(new GoogleClientRequestInitializer() {
#Override
public void initialize(AbstractGoogleClientRequest<?> abstractGoogleClientRequest) throws IOException {
abstractGoogleClientRequest.setDisableGZipContent(true);
}
});
// end options for devappserver
myApiService = builder.build();
}
context = params[0].first;
String name = params[0].second;
try {
return myApiService.sayHi(name).execute().getData();
} catch (IOException e) {
return e.getMessage();
}
}
#Override
protected void onPostExecute(String result) {
Toast.makeText(context, result, Toast.LENGTH_LONG).show();
}
}
What is happening is that in your Android app you are showing the Google account picker first, storing that Google account email in you shared preferences, and then later setting it as part of the GoogleAccountCredential object (more info on how to do that here).
The Google App Engine server receives your request and checks it. If the Android Client is one of the ones you specified in the #Api notation, then the server will inject the com.google.api.server.spi.auth.common.User object into your API method. It is now your responsibility to check if that User object is null or not inside your API method. If the User object is null, you should throw an exception in your method to prevent it from running. If you do not do this check, your API method will execute (a no-no if you are trying to restrict access to it).
You can get your ANDROID_CLIENT_ID by going to your Google Developers Console. There, you provide the package name of your Android App and the SHA1 which generates for you an android client id for you to use in your #Api annotation (or put it in a class Constants like specified above for usability).
I have done some extensive testing with all of the above and here is what I found:
If you specify a bogus or invalid Android clientId in your #Api annotation, the User object will be null in your API method. If you are doing a check for if (user == null) throw new UnauthorizedException("User is Not Valid"); then your API method will not run.
This is surprising because it appears there is some behind the scenes validation going on in Cloud Endpoints that check whether the Android ClientId is valid or not. If it is invalid, it won't return the User object - even if the end user logged in to their Google account and the GoogleAccountCredential was valid.
My question is, does anyone know how I can check for that type of ClientId validation on my own in my Cloud Endpoints methods? Could that information be passed around in an HttpHeader for example?
Another injected type in Cloud Endpoints is the javax.servlet.http.HttpServletRequest. You can get the request like this in your API method:
#ApiMethod(name = "sayHi")
public MyBean sayHi(#Named("name") String name, HttpServletRequest req) throws UnauthorizedException {
String Auth = req.getHeader("Authorization");//always null based on my tests
MyBean response = new MyBean();
response.setData("Hi, " + name);
return response;
}
}
But I am not sure if the necessary information is there or how to get it.
Certainly somewhere there must be some data that tells us if the Client is an authorized and specified one in the #Api clientIds.
This way, you could lock-down your API to your Android app (and potentially other clients) without ever having to pester your end users to log in (or just create your own simple username + password login).
For all of this to work though, you would have to pass in null in the third argument of your Builder like this:
MyApi.Builder builder = new MyApi.Builder(AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), null)
Then in your API method extract whether or not the call came from an authenticated client, and either throw an exception or run whatever code you wanted to.
I know this is possible because when using a GoogleAccountCredential in the Builder, somehow Cloud Endpoints knows whether or not the call came from an authenticated client and then either injects its User object into the API method or not based on that.
Could that information be in the header or body somehow? If so, how can I get it out to later check if it is there or not in my API method?
Note: I read the other posts on this topic. They offer ways to pass in your own authentication token - which is fine - but your .apk will still not be secure if someone decompiles it. I think if my hypothesis works, you will be able to lock-down your Cloud Endpoints API to a client without any logins.
Custom Authentication for Google Cloud Endpoints (instead of OAuth2)
Authenticate my "app" to Google Cloud Endpoints not a "user"
Google Cloud Endpoints without Google Accounts
EDIT:
We used Gold Support for the Google Cloud Platform and have been talking back and forth with their support team for weeks. This is their final answer for us:
"Unfortunately, I haven't had any luck on this. I've asked around my
team, and checked all of the documentation. It looks like using OAuth2
is your only option. The reason is because the endpoint servers handle
the authentication before it reaches your app. This means you wouldn't
be able to develop your own authentication flow, and would get results
much like what you were seeing with the tokens.
I would be happy to submit a feature request for you. If you could
provide a little more information about why the OAuth2 flow doesn't
work for your customers, I can put the rest of the information
together and submit it to the product manager."
(frowny face) - however, maybe it is still possible?
I have implemented Endpoint Auth using a custom header "Authorization" and it works just fine. In my case this token is set after login but should work all the same with your app. Check your tests because the value should be there.
The way to retrieve that header is indeed:
String Auth = req.getHeader("Authorization");
You could take it a step further and define your own implementations of an Authenticator and apply it to your secure API calls.
So you don't have any user specific info, but just want to ensure that only your app is able to communicate with your backend...
This is what i think,
change
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID,
Constants.IOS_CLIENT_ID,
Constants.API_EXPLORER_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
to
#Api(name = "myApi", version = "v1",
namespace = #ApiNamespace(ownerDomain = "${endpointOwnerDomain}",
ownerName = "${endpointOwnerDomain}",
packagePath="${endpointPackagePath}"),
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.ANDROID_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
{
...
}
The Client ID is generated from the signature of your app. It can't be replicated. If you only allow your endpoints to accept requests from the Android App, your problem would be solved.
Tell me if this works.
Faced the same problem to find a solution to call my API safely from my endpoints, without using Google Account. We can't decompile an IOS App (Bundle), but decompile an Android App is so simple..
The solution I found is not perfect but do the job pretty good:
On android APP, I just create an constant String variable, named APIKey, with simply content (For example "helloworld145698")
Then I encrypt it with sha1, next md5, and finally sha1 (Order and frequency of encryption up to you) and store the variable on SharedPref (For Android) in private mode (Do this action on an random class in your App) It's this result encrypted I authorize on my Backend !
On my backend, I just add a parameter (named token for exemple) on every request
Example:
#ApiMethod(name = "sayHi")
public void sayHi(#Named("name") String name, #Named("Token") String token) {
if (token == tokenStoreOnAPIServer) {
//Allow it
} else {
//Refuse it and print error
}
}
On android, active ProGuard for obfuscated your code. It will be really unreadable for anyone who tried to decompile your app (Reverse engineering is really hardcore)
Not THE perfect secure solution, but it works, and it will be really really (really) difficult to find the real API key for anyone who try to read your code after decompilation.
Authentication and app engine, there is a lot to be read about it, but a lot seems to be outdated!
Even the google pages https://developers.google.com/appengine/docs/java/endpoints/consume_android#making-authenticated-calls
Here, they talk about 'GoogleAccountCredential.usingAudience', but nowadays, you should use GoogleAuthUtil (as far as I know, please correct me if I'm wrong).
I am trying to set up an app engine as a backend to my Android app (and in future, my iOS app).
I am using Android Studio, used the 'new module' and chose app engine with cloud messaging there.
I created a simple endpoint, and have a function there, here is some code:
public class ReviewEndpoint {
// Make sure to add this endpoint to your web.xml file if this is a web application.
private static final Logger LOG = Logger.getLogger(ReviewEndpoint.class.getName());
/**
* This method gets the <code>Review</code> object associated with the specified <code>id</code>.
* #param id The id of the object to be returned.
* #return The <code>Review</code> associated with <code>id</code>.
*/
#ApiMethod(name = "getReview")
public Review getReview(#Named("id") Long id) {
// Implement this function
Review r = new Review();
r.setData("test!");
As you can see, this is nicely generated by Android Studio. I implemented some stuf like creating the 'review' object and return it at the end.
On the Android side, I can do this:
ReviewEndpoint.Builder b = new ReviewEndpoint.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), null);
ReviewEndpoint ep = b.build();
Review review = ep.getReview(1L).execute();
data = review.getData();
and yes, I get 'test!' :)
Now, I want to have this authenticated. I want to know which user wrote what, so I thought I am going to use GMail account and Facebook later.
Here I'm stuck. I am able to get a token from the user on Android:
token = GoogleAuthUtil.getToken(MainScreenActivity.this, mAccount.name, "oauth2:https://www.googleapis.com/auth/plus.me https://www.googleapis.com/auth/userinfo.profile");
then you are able to add this token as credential to the request:
Credential cr = new Credential(BearerToken.authorizationHeaderAccessMethod()).setAccessToken(token);
ReviewEndpoint.Builder b = new ReviewEndpoint.Builder(AndroidHttp.newCompatibleTransport(), new AndroidJsonFactory(), cr);
Then in the app engine I tried to get the user info, but how?
Will it be supplied as 'bearer'? How do I get this bearer token? Should I then do API request to get the data on the server?
this does not work:
OAuthService service = OAuthServiceFactory.getOAuthService();
try {
User user = service.getCurrentUser();
can anyone give me a heads up?
So finally, today, I found out how to do it! I had questions on Stackoverflow on this before and never had an answer, but these to sites gave me the answer:
https://developers.google.com/appengine/docs/java/endpoints/auth
https://developers.google.com/appengine/docs/java/endpoints/consume_android
The first shows what needs to be done on the app engine side. The second page will tell you how to get the credentials. I was quite close. I am not sure if the adjusting of the build.gradle file mentioned in the second link is necessary. What I added to the App Engine:
#Api(name = "reviewEndpoint", version = "v1", ...<<some more stuff here >>
scopes = {Constants.EMAIL_SCOPE},
clientIds = {Constants.WEB_CLIENT_ID, Constants.ANDROID_CLIENT_ID},
audiences = {Constants.ANDROID_AUDIENCE})
and then get the credentials:
// Initialize the scope using the client ID you got from the Console.
final String scope = "server:client_id:" + Constants.WEB_CLIENT_ID;
credential = GoogleAccountCredential.usingAudience(activity,scope);
You have to add the e-mail address of the user:
credential.setSelectedAccountName("some-mail-address#gmail.com");
you can get the e-mail address using the account picker (also example shown when you follow the link)
and next. you do a call to the endpoint, using the credential, I think Play Services will validate the user, because if I use an e-mail that is not logged in on the device, it will not work. The following code will throw an GoogleAuthIOException :
ReviewEndpoint.Builder b = new ReviewEndpoint.Builder(
AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(), id_token);
ReviewEndpoint ep = b.build();
Review review;
review = ep.getReview(1L).execute();
for testing, I've put the e-mail address I get at the server side as a string in the review object, and there it gave me the e-mail address instead of the user object being null. Ow! I forgot to tell you, you need a user argument on the app engine side. Even though you do not see the 'user' argument in the 'getReview' call above, it will be added by App Engine.
So this is how my getReview looks now:
#ApiMethod(name = "getReview")
public Review getReview(#Named("id") Long id, User user) {
// Implement this function
Review r = new Review();
r.setData("user == " + (user == null ? "NULL " : user.toString()));
Hope this will help someone
Me and my friend are working on an app., and we wish to use Parse.com as our data base from which we can retrieve info.
We can't decide what is the best way to access the data on Parse. For the sake of the example, our app. (i.e. client side) needs something stored on the Parse data base (say some number) - should it directly run the query using the Parse API, or should it make a request to a server side, let it retrieve that number from Parse, and send it back to the client?
We know there's no definite answer, but we couldn't find answer regarding this specific situation. We read this post: When to use client-side or server-side?,
but this not exactly the same case.
I claim that we should try to seperate as much as possible from client side and data bases, and leave these queries run by someone who's in charge (server), where my friend claims this adds unnecessary complication, since it's very natural to use the tools supplied by Parse to access the data base from the client side, without the need for a protocol etc.
We'd appriciate any advice,
Thank you.
In general, go right ahead and make a normal call.
I'd encourage you to do that first in any case, to get everything working on both ends.
Then if necessary go to Cloud Code.
If you are going to do more than one platform (ie iOS and Android), cloud code can be a huge timesaver.
BUT don't forget that for simple calls, cloud code is a waste of time. "Normal" Parse calls are amazingly, incredibly, amazingly, fast and quick to work with.
There is absolutely nothing "wrong" with using normal Parse calls - so do that.
Regarding the question, when do you literally have to use a cloud code call -- you'll know, because you won't be able to do it with a normal call :)
Don't forget very often you can simply use "afterSave" or "beforeSave" in cloud code, to do a huge amount of work. You often don't literally need to go to a "custom call" in cloud code.
Here's a fantastic
Rule of thumb for Parse cloud code --------->
If you have to do "more than one thing" ... in that case you will likely have to make it a cloud code function. If you have to do "three or more things" then DEFINITELY make it a cloud code function.
That's a good rule of thumb.
(Again, as I say, often just an "afterSave" or similar works brilliantly...rather than literally writing a full custom call.)
Here's a typical example of a cloud call that saves 18 billion lines of code in all the platforms covered by the dotcom. First the cloud code...
Parse.Cloud.define("clientRequestHandleInvite", function(request, response)
{
// called from the client, to accept an invite from invitorPerson
var thisUserObj = request.user;
var invitorPersonId = request.params.invitorPersonId;
var theMode = request.params.theMode;
// theMode is likely "accept" or "ignore"
console.log( "clientRequestAcceptInvite called.... invitorPersonId " + invitorPersonId + " By user: " + thisUserObj.id );
console.log( "clientRequestAcceptInvite called.... theMode is " + theMode );
if ( invitorPersonId == undefined || invitorPersonId == "" )
{
response.error("Problem in clientRequestAcceptInvite, 'invitorPersonId' missing or blank?");
return;
}
var query = new Parse.Query(Parse.User);
query.get(
invitorPersonId,
{
success: function(theInvitorPersonObject)
{
console.log("clientRequestFriendRemove ... internal I got the userObj ...('no response' mode)");
if ( theMode == "accept" )
{
createOneNewHaf( thisUserObj, theInvitorPersonObject );
createOneNewHaf( theInvitorPersonObject, thisUserObj );
}
// in both cases "accept" or "ignore", delete the invite in question:
// and on top of that you have to do it both ways
deleteFromInvites( theInvitorPersonObject, thisUserObj );
deleteFromInvites( thisUserObj, theInvitorPersonObject );
// (those further functions exist in the cloud code)
// for now we'll just go with the trick of LETTING THOSE RUN
// so DO NOT this ........... response.success( "removal attempt underway" );
// it's a huge problem with Parse that (so far, 2014) is poorly handled:
// READ THIS:
// parse.com/questions/can-i-use-a-cloud-code-function-within-another-cloud-code-function
},
error: function(object,error)
{
console.log("clientRequestAcceptInvite ... internal unusual failure: " + error.code + " " + error.message);
response.error("Problem, internal problem?");
return;
}
}
);
}
);
If you are new to Parse it's incredibly hard to figure out how to call these from Android or iOS! Here's that one being called from Android ...
this will save you a day of messing about with HashMaps :)
private static void handleInvite( ParseUser invitor, final boolean accepted )
{
String invitorId = invitor.getObjectId();
// you must SEND IDs, NOT PARSEUSER OBJECTS to cloud code. Sucks!
String cloudKode;
cloudKode = (accepted? "accept" : "ignore");
HashMap<String, Object> dict = new HashMap<String, Object>();
dict.put( "invitorPersonId", invitorId );
dict.put( "theMode", cloudKode );
Toast.makeText(State.mainContext, "contacting...", Toast.LENGTH_SHORT).show();
ParseCloud.callFunctionInBackground(
"clientRequestHandleInvite",
dict,
new FunctionCallback<Object>()
{
#Override
public void done(Object s, ParseException e)
{
Toast.makeText(State.mainContext, "blah", Toast.LENGTH_SHORT).show();
// be careful with handling the exception on return...
}
});
}
And here's the same cloud call from iOS ... well for now, until you have to do it in SWIFT
-(void)tableView:(UITableView *)tableView
commitEditingStyle:(UITableViewCellEditingStyle)editingStyle
forRowAtIndexPath:(NSIndexPath *)indexPath
{
int thisRow = indexPath.row;
PFUser *delFriend = [self.theFriends objectAtIndex:thisRow];
NSLog(#"you wish to delete .. %#", [delFriend fullName] );
// note, this cloud call is happily is set and forget
// there's no return either way. life's like that sometimes
[PFCloud callFunctionInBackground:#"clientRequestFriendRemove"
withParameters:#{
#"removeThisFriendId":delFriend.objectId
}
block:^(NSString *serverResult, NSError *error)
{
if (!error)
{
NSLog(#"ok, Return (string) %#", serverResult);
}
}];
[self back]; // that simple
}
Note For the iOS/Swift experience, click to: How to make this Parse.com cloud code call? which includes comments from the Parse.com team. Hope it saves someone some typing, cheers