Reputation: 271
I am creating my first app engine app and having problems with authenticating the users.
I have followed the https://cloud.google.com/appengine/docs/python/endpoints/consume_android#making_authenticated_calls - seems a bit magical, that I just "setAccountName" and it is supposed to work, but w/e, I guess that it should load the app scopes from Android Audience and then just check if the account name I passed has actually logged into the device.
The API call works, passes the authentication, but sadly - the "endpoints.get_current_user()" function on the backend returns None.
So I kept digging, but I can't seem to find anything on the topic. Best thing I have found is http://blog.notdot.net/2010/05/Authenticating-against-App-Engine-from-an-Android-app - but that's an article from 6 years ago and the author uses HTTP client and nothing related to endpoints libs.
All I can think of would be to follow some "non-endpoints" way of adding "login with Google" to my app and then try to pass the credentials I would get to my API builder, but that just feels wrong, like there should be an easier way to do that.
So, am I missing some step, that was not mentioned in https://cloud.google.com/appengine/docs/python/endpoints/consume_android#making_authenticated_calls ?
Actual code (slightly simplified) below:
Backend:
auth_api = endpoints.api(
name='auth_api',
version='v1.0',
auth_level=endpoints.AUTH_LEVEL.REQUIRED,
allowed_client_ids=[
ANDROID_CLIENT_ID,
WEB_CLIENT_ID,
endpoints.API_EXPLORER_CLIENT_ID,
],
audiences=[
WEB_CLIENT_ID,
endpoints.API_EXPLORER_CLIENT_ID,
],
scopes=[
'https://www.googleapis.com/auth/userinfo.profile',
'https://www.googleapis.com/auth/userinfo.email',
],
)
@auth_api.api_class(resource_name='rating')
class RatingHandler(remote.Service):
@endpoints.method(
message_types.VoidMessage,
RatingsMessage,
path='rating/getRatings',
http_method='GET',
)
def getRatings(self, request):
rating_query = Rating.query(
ancestor=ndb.Key(
Account,
endpoints.get_current_user().user_id(), // ERROR! endpoints.get_current_user() is None
)
).order(-Rating.rating)
Client:
// Somewhere these lines exist
if (credential == null || credential.getSelectedAccountName() == null) {
startActivityForResult(
AuthUtils.getCredentials(getActivity()).newChooseAccountIntent(),
AuthUtils.REQUEST_ACCOUNT_PICKER
);
} else {
LoadRatings();
}
@Override
public void onActivityResult(
int requestCode,
int resultCode,
Intent data
) {
super.onActivityResult(requestCode, resultCode, data);
if (data != null && data.getExtras() != null) {
String accountName =
data.getExtras().getString(
AccountManager.KEY_ACCOUNT_NAME
);
if (accountName != null) {
credential = GoogleAccountCredential.usingAudience(
getApplicationContext(),
"server:client_id:" + Constants.ANDROID_AUDIENCE
);
credential.setSelectedAccountName(accountName);
LoadRatings();
}
}
}
public void LoadRatings() {
// AuthApi was auto-generated by Google App Engine based on my Backend
AuthApi.Builder api = new AuthApi.Builder(
AndroidHttp.newCompatibleTransport(),
new AndroidJsonFactory(),
credential
).setApplicationName(getPackageName());
AuthApi service = api.build();
try {
ApiMessagesRatingRatedBeerListMessage result = service.rating().getRatings().
// some stuff done with result, but the Exception is thrown in line above
Upvotes: 2
Views: 275
Reputation: 271
OK, I figured it out. When I removed "scopes" from API declaration, it works. I'm not sure how I am going to access user's email / profile yet, but that's at least a step forward.
This issue has been actually brought up before - How to add more scopes in GoogleCloud Endpoints - sadly, without any answers
Upvotes: 2
Reputation: 10891
You won't have a User
object (Entity that is) on the backend created for you. You have to do that yourself. For example:
@Entity
public class AppEngineUser {
@Id
private String email;
private User user;
private AppEngineUser() {}
public AppEngineUser(User user) {
this.user = user;
this.email = user.getEmail();
}
public User getUser() {
return user;
}
public Key<AppEngineUser> getKey() {
return Key.create(AppEngineUser.class, email);
}
}
When you create an API method and specify a User
object like this:
@ApiMethod(name = "insertRecord", path = "insert_record", httpMethod = HttpMethod.POST)
public Record insertRecord(User user, Record record)
// check if google user is authenticated
throws UnauthorizedException {
if (user == null) {
throw new UnauthorizedException("Authorization required");
}
// user is authenticated... do some stuff!
}
The User
object is an injected type. It is actually: com.google.appengine.api.users.User
. See "injected types" on https://cloud.google.com/appengine/docs/java/endpoints/paramreturn_types.
What this means is that GAE injects the Google user object if the user provided the correct credentials to their Google+ account. If they did not, then User
will be null. If it is, you can throw an UnauthorizedException
the way the above method does.
You can now get things like the User's gmail address among other things if the user
object is not null. From there, you must store those values in your own custom entity, such as AppEngineUser
and save it to the datastore. Then you can do other things with it later, such as load it, check if the user is registered and whatever else all by yourself.
Hope that helps!
Upvotes: 0