google-app-engine oauth-2.0 google-cloud-endpoints

google app engine - Inicio de sesión en Facebook en Google Cloud Endpoints



google-app-engine oauth-2.0 (5)

¿Puede alguien explicar los pasos para implementar el proceso de inicio de sesión con otros proveedores de OAuth2? Este enlace Google Cloud Endpoints con otro proveedor de oAuth2 brinda poca información sobre cómo escribir autenticación personalizada, pero creo que para principiantes como yo no es suficiente, brinde pasos detallados. Especialmente, interesado en Facebook.


Así que en realidad traté de implementar ese flujo de autenticación personalizado. Parece funcionar bien, aunque podría haber una mayor consideración por el lado de la seguridad.

Primero, el usuario va a mi aplicación y se autentica con Facebook, la aplicación obtuvo su user_id y access_token. Luego, la aplicación llama a la API de autenticación del servidor con esta información.

class AuthAPI(remote.Service): @classmethod def validate_facebook_user(cls, user_id, user_token): try: graph = facebook.GraphAPI(user_token) profile = graph.get_object("me", fields=''email, first_name, last_name, username'') except facebook.GraphAPIError, e: return (None, None, str(e)) if (profile is not None): # Check if match user_id if (profile.get(''id'', '''') == user_id): # Check if user exists in our own datastore (user, token) = User.get_by_facebook_id(user_id, ''auth'', user_token) # Create new user if not if user is None: #print ''Create new user'' username = profile.get(''username'', '''') password = security.generate_random_string(length=20) unique_properties = [''email_address''] if (username != ''''): (is_created, user) = User.create_user( username, unique_properties, email_address = profile.get(''email'', ''''), name = profile.get(''first_name'', ''''), last_name = profile.get(''last_name'', ''''), password_raw = password, facebook_id = user_id, facebook_token = user_token, verified=False, ) if is_created==False: return (None, None, ''Cannot create user'') token_str = User.create_auth_token(user.get_id()) #print (user, token_str) # Return if user exists if token is not None: return (user, token.token, ''Successfully logged in'') else: return (None, None, ''Invalid token'') return (None, None, ''Invalid facebook id and token'') # Return a user_id and token if authenticated successfully LOGIN_REQ = endpoints.ResourceContainer(MessageCommon, type=messages.StringField(2, required=True), user_id=messages.StringField(3, required=False), token=messages.StringField(4, required=False)) @endpoints.method(LOGIN_REQ, MessageCommon, path=''login'', http_method=''POST'', name=''login'') def login(self, request): type = request.type result = MessageCommon() # TODO: Change to enum type if we have multiple auth ways if (type == "facebook"): # Facebook user validation user_id = request.user_id access_token = request.token (user_obj, auth_token, msg) = self.validate_facebook_user(user_id, access_token) # If we can get user data if (user_obj is not None and auth_token is not None): print (user_obj, auth_token) result.success = True result.message = msg result.data = json.dumps({ ''user_id'': user_obj.get_id(), ''user_token'': auth_token }) # If we cannot else: result.success = False result.message = msg return result

Además de esto, es posible que desee implementar el flujo de autenticación de usuario normal siguiendo las instrucciones aquí: http://blog.abahgat.com/2013/01/07/user-authentication-with-webapp2-on-google-app-engine/ .

Esto se debe a que webapp2_extras.appengine.auth proporciona user_id y user_token que obtengo .

Implementación de User.get_by_facebook_id:

class User(webapp2_extras.appengine.auth.models.User): @classmethod def get_by_facebook_id(cls, fb_id, subj=''auth'', fb_token=""): u = cls.query(cls.facebook_id==fb_id).get() if u is not None: user_id = u.key.id() # TODO: something better here, now just append the facebook_token to a prefix token_str = "fbtk" + str(fb_token) # get this token if it exists token_key = cls.token_model.get(user_id, subj, token_str) print token_key, fb_token if token_key is None: # return a token that created from access_token string if (fb_token == ""): return (None, None) else: token = cls.token_model.create(user_id, subj, token_str) else: token = token_key return (u, token) return (None, None)

Servidor verificar si el usuario está autenticado con Facebook una vez más. Si se aprueba, el usuario se considera conectado. En este caso, el servidor devuelve un user_token (generado en base a facebook_token) y user_id desde nuestro almacén de datos.

Cualquier otra llamada API debería usar este user_id y user_token

def get_request_class(messageCls): return endpoints.ResourceContainer(messageCls, user_id=messages.IntegerField(2, required=False), user_token=messages.StringField(3, required=False)) def authenticated_required(endpoint_method): """ Decorator that check if API calls are authenticated """ def check_login(self, request, *args, **kwargs): try: user_id = request.user_id user_token = request.user_token if (user_id is not None and user_token is not None): # Validate user (user, timestamp) = User.get_by_auth_token(user_id, user_token) if user is not None: return endpoint_method(self, request, user, *args, **kwargs ) raise endpoints.UnauthorizedException(''Invalid user_id or access_token'') except: raise endpoints.UnauthorizedException(''Invalid access token'') @endpoints.api(name=''blah'', version=''v1'', allowed_client_ids = env.CLIENT_IDS, auth=AUTH_CONFIG) class BlahApi(remote.Service): # Add user_id/user_token to the request Blah_Req = get_request_class(message_types.VoidMessage) @endpoints.method(Blah_Req, BlahMessage, path=''list'', name=''list'') @authenticated_required def blah_list(self, request, user): newMessage = BlahMessage(Blah.query().get()) return newMessage

Nota:


Así que ningún cuerpo ha arrojado luz sobre las cosas del lado del cliente de Android. Dado que, en este caso, no requiere el inicio de sesión de Google, el código para obtener el controlador de la API se verá así:

private Api getEndpointsApiHandle() { Api.Builder api = new Api.Builder(HTTP_TRANSPORT, JSON_FACTORY, null); api.setRootUrl(yourRootUrl); return api.build(); }

Si te das cuenta; Deberá pasar nulo como credencial. Este código funciona como un encanto


Debe implementar las API del lado del cliente de Facebook de acuerdo con su documentación y el entorno en el que está implementando su aplicación cliente (Navegador vs iOS vs Android). Esto incluye registrar su aplicación con ellos. Su aplicación registrada indicará al usuario que realice un flujo de autenticación y, al final, su aplicación cliente tendrá acceso a un token de acceso de corta duración. Facebook tiene múltiples tipos de tokens de acceso, pero el que parece que le interesa se llama token de acceso de usuario ya que identifica a un usuario autorizado.

Pase el token de acceso a su API de Cloud Endpoints a través de un campo o encabezado. Dentro de su código API, reciba el token de acceso e implemente la API de Facebook que verifica la validez del token de acceso. La primera respuesta a esta pregunta sobre SO hace que parezca bastante fácil, pero es probable que desee volver a hacer referencia a su documentación. Si pasa ese control, entonces ejecutará su código API, de lo contrario lanzará una excepción.

Por lo general, también querrá implementar un mecanismo de almacenamiento en caché para evitar llamar a la API de validación del lado del servidor de Facebook para cada solicitud de Cloud Endpoints.

Finalmente, mencioné que su aplicación cliente tiene un token de corta duración. Si tiene una aplicación cliente basada en navegador, probablemente quiera actualizarla a un token de larga duración. Facebook también tiene un flujo para eso, lo que implica que tu código API solicite un token de larga vida con uno de corta duración. Luego, deberá transferir ese token de larga duración a la aplicación cliente para utilizarlo en futuras llamadas API de Cloud Endpoints.

Si su aplicación cliente está basada en iOS o Android, sus tokens son administrados por código de Facebook y simplemente solicita tokens de acceso de las respectivas API cuando los necesita.


Implementé este caso de uso agregando un controlador de webapp2 para intercambiar el token de acceso de Facebook por uno generado por mi propia aplicación, usando la SimpleAuth para la verificación:

class AuthHandler(webapp2.RequestHandler, SimpleAuthHandler): """Authenticates a user to the application via a third-party provider. The return value of this request is an OAuth token response. Only a subset of the PROVIDERS specified in SimpleAuthHandler are currently supported. Tested providers: Facebook """ def _on_signin(self, data, auth_info, provider): # Create the auth ID format used by the User model auth_id = ''%s:%s'' % (provider, data[''id'']) user_model = auth.get_auth().store.user_model user = user_model.get_by_auth_id(auth_id) if not user: ok, user = user_model.create_user(auth_id) if not ok: logging.error(''Unable to create user for auth_id %s'' % auth_id) self.abort(500, ''Unable to create user'') return user def post(self): # Consider adding a check for a valid endpoints client ID here as well. access_token = self.request.get(''x_access_token'') provider = self.request.get(''x_provider'') if provider not in self.PROVIDERS or access_token is None: self.abort(401, ''Unknown provider or access token'') auth_info = {''access_token'': access_token} fetch_user_info = getattr(self, ''_get_%s_user_info'' % provider) user_info = fetch_user_info(auth_info) if ''id'' in user_info: user = self._on_signin(user_info, auth_info, provider) token = user.create_bearer_token(user.get_id()) self.response.content_type = ''application/json'' self.response.body = json.dumps({ ''access_token'': token.token, ''token_type'': ''Bearer'', ''expires_in'': token.bearer_token_timedelta.total_seconds(), ''refresh_token'': token.refresh_token }) else: self.abort(401, ''Access token is invalid'')

El token de acceso intercambiado se puede pasar a cada solicitud de punto final en el encabezado Autorización, o como parte del mensaje RPC, si lo prefiere. Aquí hay un ejemplo de lectura desde el encabezado:

def get_current_user(): token = os.getenv(''HTTP_AUTHORIZATION'') if token: try: token = token.split('' '')[1] except IndexError: pass user, _ = User.get_by_bearer_token(token) return user

Publiqué el ejemplo completo en Github: https://github.com/loudnate/appengine-endpoints-auth-example


Yo también he escrito mi propia solución para este problema. Puede consultar el código aquí: https://github.com/rggibson/Authtopus

Authtopus es una biblioteca de Python para autenticación personalizada con Google Cloud Endpoints. Admite registros de usuario y contraseña básicos + inicios de sesión, así como inicios de sesión a través de Facebook y Google (y probablemente podría ampliarse para admitir a otros proveedores sociales sin demasiada molestia). Sé que esto no responde directamente a la pregunta original, pero parece lo suficientemente relacionado como para compartir.