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:
- Estoy usando esta biblioteca para controlar la autenticación de Facebook en el servidor: https://github.com/pythonforfacebook/facebook-sdk
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.