python - app - social auth django google
Decida cuándo actualizar el token OAUTH2 con Python Social Auth (2)
Creo que esto es principalmente una pregunta sobre las mejores prácticas.
Tengo un proveedor OAUTH2 que emite tokens de acceso (válido durante 10 horas) siempre que actualice los tokens.
Encontré aquí que es bastante fácil actualizar el token de acceso, pero no puedo entender cómo decidir cuándo es el momento de actualizar.
La respuesta fácil es probablemente "cuando ya no funciona", es decir, cuando obtengo un HTTP 401 desde el servidor. El problema con esta solución es que no es tan eficiente, y solo puedo suponer que obtuve un 401 porque el token ha expirado.
Mi aplicación django descubrí que la user social auth
del user social auth
tiene un campo de Extra data
contiene algo como esto:
{ "scope": "read write", "expires": 36000, "refresh_token": "xxxxxxxxxxxxx", "access_token": "xxxxxxxxxxxxxxxxxxxxxxxxxxxxxx", "token_type": "Bearer" }
pero no estoy seguro de cómo usar el campo expires
.
Entonces mi pregunta es: ¿cómo sé si un token de acceso ha expirado y necesito actualizarlo?
EDITAR : Acabo de encontrar este comentario que parece relevante, pero no puedo entender cómo conectar esta nueva función en la tubería para trabajar durante la actualización del token.
Finalmente me di cuenta de esto. La razón por la que inicialmente estaba confundido fue porque en realidad hay dos casos:
- Cuando el usuario proviene de un inicio de sesión, cuando básicamente se ejecuta la canalización.
- Cuando el token se actualiza llamando al método social auth user
refresh_token
Para resolver el primer caso
Creé una nueva función para la tubería:
def set_last_update(details, *args, **kwargs): # pylint: disable=unused-argument
"""
Pipeline function to add extra information about when the social auth
profile has been updated.
Args:
details (dict): dictionary of informations about the user
Returns:
dict: updated details dictionary
"""
details[''updated_at''] = datetime.utcnow().timestamp()
return details
en la configuración lo agregué en la tubería justo antes de load_extra_data
SOCIAL_AUTH_PIPELINE = (
''social.pipeline.social_auth.social_details'',
''social.pipeline.social_auth.social_uid'',
''social.pipeline.social_auth.auth_allowed'',
''social.pipeline.social_auth.social_user'',
''social.pipeline.user.get_username'',
''social.pipeline.user.create_user'',
''social.pipeline.social_auth.associate_user'',
# the following custom pipeline func goes before load_extra_data
''backends.pipeline_api.set_last_update'',
''social.pipeline.social_auth.load_extra_data'',
''social.pipeline.user.user_details'',
''backends.pipeline_api.update_profile_from_edx'',
''backends.pipeline_api.update_from_linkedin'',
)
y, aún en la configuración, agregué el nuevo campo en los datos adicionales.
SOCIAL_AUTH_EDXORG_EXTRA_DATA = [''updated_at'']
Para el segundo caso:
refresh_token
método refresh_token
de mi backend para agregar el campo extra.
def refresh_token(self, token, *args, **kwargs):
"""
Overridden method to add extra info during refresh token.
Args:
token (str): valid refresh token
Returns:
dict of information about the user
"""
response = super(EdxOrgOAuth2, self).refresh_token(token, *args, **kwargs)
response[''updated_at''] = datetime.utcnow().timestamp()
return response
Todavía en la clase backend, agregué un campo extra para extraer el campo expires_in
viene del servidor.
EXTRA_DATA = [
(''refresh_token'', ''refresh_token'', True),
(''expires_in'', ''expires_in''),
(''token_type'', ''token_type'', True),
(''scope'', ''scope''),
]
En este punto, tengo la marca de tiempo cuando se ha creado el token de acceso ( updated_at
) y la cantidad de segundos será válido ( expires_in
).
NOTA: el updated_at
es una aproximación, porque se crea en el cliente y no en el servidor del proveedor.
Ahora, lo único que falta es una función para verificar si es el momento de actualizar el token de acceso.
def _send_refresh_request(user_social):
"""
Private function that refresh an user access token
"""
strategy = load_strategy()
try:
user_social.refresh_token(strategy)
except HTTPError as exc:
if exc.response.status_code in (400, 401,):
raise InvalidCredentialStored(
message=''Received a {} status code from the OAUTH server''.format(
exc.response.status_code),
http_status_code=exc.response.status_code
)
raise
def refresh_user_token(user_social):
"""
Utility function to refresh the access token if is (almost) expired
Args:
user_social (UserSocialAuth): a user social auth instance
"""
try:
last_update = datetime.fromtimestamp(user_social.extra_data.get(''updated_at''))
expires_in = timedelta(seconds=user_social.extra_data.get(''expires_in''))
except TypeError:
_send_refresh_request(user_social)
return
# small error margin of 5 minutes to be safe
error_margin = timedelta(minutes=5)
if datetime.utcnow() - last_update >= expires_in - error_margin:
_send_refresh_request(user_social)
Espero que esto pueda ser útil para otras personas.
Actualmente, el campo extra_data
ahora tiene auth_time
. Puede usar esto junto con expires
para determinar la validez de access_token
como tal:
if (social.extra_data[''auth_time''] + social.extra_data[''expires''] - 10) <= int(time.time()):
from social_django.utils import load_strategy
strategy = load_strategy()
social.refresh_token(strategy)
Los "10" segundos adicionales están ahí para evitar una condición de carrera donde un access_token
podría caducar antes de que se ejecute más código.
Se brindan más detalles en esta pregunta: ¿cómo puedo actualizar el token con social-auth-app-django?