12

I am referencing @MinWan 's awesome answer in this post Google Cloud Endpoints and user's authentication, where he describes a way to add custom headers to a request against App Engine's Cloud Endpoints.

It becomes clear that we can add a custom header and write an authenticator per each service (e.g. Google, Twitter, Facebook) against which we want to authenicate, where each authenticator reads a specific header and authenticates against the service. If the token is valid, a service typically returns a response with an email address or user id, plus some extra information [A], from which we generate a com.google.api.server.spi.auth.common.User, which is later passed into the endpoint method as com.google.appengine.api.users.User.

First question: Why do we have two different User entities, e.g. users with different namespaces? As it seems, these are neither sub/superclasses, so they are possibly explicitly cast behind the scenes.

Second question: The problem that comes with the explicitly cast User entity and that there is no custom field where I could put the extra information [A] returned by the service, is that the extra information is lost. Such extra information may be helpful for matching the oauth2 user of the external service to a local user or to oauth2 users returned by other services.

Any input? What's the suggested way of handling multiple authentication services?

Community
  • 1
  • 1
Oliver Hausler
  • 4,900
  • 4
  • 35
  • 70
  • As there seems no solution, what I did as a workaround is "abuse" the `com.google.api.server.spi.auth.common.User` by returning a JSON instead of a String, which I can later unwrap. This way my Authenticators can store additional properties. As I said, not an answer to my question, just an ugly workaround. – Oliver Hausler Jun 10 '15 at 13:20
  • This is an interesting use case. You should make a feature request in the [Public Issue Tracker](https://code.google.com/p/google-appengine/issues/list) for App Engine! – Nick Nov 10 '15 at 22:32
  • Just tested, and you can definitely subclass User to contain whichever private fields you want. Just use class inheritance polymorphism to return an object of that type from the Authenticator method, without changing the type from default User in the method signature. – Nick Nov 11 '15 at 00:03
  • Very nice solution @Nick. If you post this as answer I will give you the kudos you deserve. – Oliver Hausler Nov 11 '15 at 01:33

2 Answers2

6

Just tested, and you can definitely subclass User to contain whichever private fields you want. Just use class inheritance polymorphism to return an object of that type from the Authenticator method, without changing the type from default User in the method signature.

import javax.servlet.http.HttpServletRequest;
import com.google.api.server.spi.auth.common.User;
import com.google.api.server.spi.config.Authenticator;

public class BazUser extends User {
        private String secret; // extra piece of data held by this User
        public BazUser(String email) {
                super(email);
                this.secret = "notasecret";
        }
        public BazUser (String email, String secret) {
                super (email);
                this.secret = secret;
        }
}

public class BazAuthenticator implements Authenticator {
        public User authenticate(HttpServletRequest req) {
                return new BazUser ("userid@baz.com", "secret");
        }
}
Nick
  • 3,581
  • 1
  • 14
  • 36
  • How do you get that extra info from the User object at endpoint side? I'm using latest 1.9.32 sdk and I get the following error: No suitable constructor found for type [simple type, class com.aleskovacic.nearby.backend.models.AuthUser]: can not instantiate from JSON object (need to add/enable type information?) – alesko007 Feb 10 '16 at 14:57
  • You should post this as a new question, and include: 1. full error output, 2. the code you're using, 3. details about the HTTP requests and the content of those requests if possible, etc. All of this information is necessary to determine what's happening on your system. Computers are very complex machines with a truly vast set of possible outputs, and mapping backwards from a partial error message to find a root cause is - except for special circumstances - mostly out of the skill range of 99.9% of programmers. – Nick Feb 10 '16 at 17:25
  • I've posted the question here: http://stackoverflow.com/questions/35351830/how-to-retrieve-custom-user-object-in-gae-endpoints – alesko007 Feb 11 '16 at 23:19
  • So sad, but this only works as long as you have no body in the incoming request. As soon as the request has a body, you can't compile because the compiler thinks com.google.api.server.spi.auth.common.User is the body from the endpoint: "Multiple entity parameters. (...)" – Oliver Hausler Aug 07 '16 at 03:21
  • Hey @OliverHausler, that doesn't seem like it should happen. Maybe post a new question with code and stack trace? – Nick Aug 09 '16 at 14:08
  • Hi @Nick, I guess that's what it is. App Engine documentation speaks of only 3 "built-in" classes, and `google.api.server.spi.auth.common.User` is not one of them [I didn't keep the link but I remember this was the case], so the check which makes sure there are not 2 body parameters fails. I reported this to Google: https://code.google.com/p/googleappengine/issues/detail?id=13197 – Oliver Hausler Aug 09 '16 at 17:46
  • Endpoints developer here. I think you are encountering a bug. Have you considered setting a request attribute instead of extending the user model as a workaround? – saiyr Aug 23 '16 at 22:32
1

Functionally, everything works with:

import com.google.api.server.spi.auth.common.User;

even with gradle:

compile 'com.google.endpoints:endpoints-framework:2.0.0-beta.11'

The IDE warning can be cleared by including @SuppressWarnings("ResourceParameter") as follows:

/**
 * Adds a new PmpUser.
 *
 * @param pmpUser  pmpUser object
 */
@SuppressWarnings("ResourceParameter")
@ApiMethod(
        name = "pmpUser.post",
        path = "pmpUser",
        httpMethod = ApiMethod.HttpMethod.POST)
...
user7653815
  • 186
  • 1
  • 6