ruby on rails - Google:: Apis:: AuthorizationError(no autorizado)
ruby-on-rails google-oauth (5)
Estamos creando una aplicación con Ionic Framework como front-end y Ruby on Rails como back-end. Podemos vincular la cuenta de Gmail en nuestra aplicación. La vinculación de cuentas funciona bien, obtenemos serverAuthCode desde el front-end y luego utilizamos el token de actualización y podemos recuperar los correos electrónicos con ese token de actualización al primer intento. Pero en unos segundos, se vence o se revoca. Consiguiendo el siguiente problema:
Signet::AuthorizationError (Authorization failed. Server message:
{
"error" : "invalid_grant",
"error_description" : "Token has been expired or revoked."
})
Parece que, el token de actualización en sí está expirando en segundos. ¿Alguien tiene alguna idea acerca de cómo solucionarlo?
Actualizar:
El código existente se ve así:
class User
def authentication(linked_account)
client = Signet::OAuth2::Client.new(
authorization_uri: ''https://accounts.google.com/o/oauth2/auth'',
token_credential_uri: Rails.application.secrets.token_credential_uri,
client_id: Rails.application.secrets.google_client_id,
client_secret: Rails.application.secrets.google_client_secret,
scope: ''https://www.googleapis.com/auth/gmail.readonly, https://www.googleapis.com/auth/userinfo.email, https://www.googleapis.com/auth/userinfo.profile'',
redirect_uri: Rails.application.secrets.redirect_uri,
refresh_token: linked_account[:refresh_token]
)
client.update!(access_token: linked_account.token, expires_at: linked_account.expires_at)
return AccessToken.new(linked_account.token) unless client.expired?
auth.fetch_access_token!
end
def get_email(linked_account)
auth = authentication(linked_account)
gmail = Google::Apis::GmailV1::GmailService.new
gmail.client_options.application_name = User::APPLICATION_NAME
gmail.authorization = AccessToken.new(linked_account.token)
query = "(is:inbox OR is:sent)"
gmail.list_user_messages(linked_account[:uid], q: "#{query}")
## Getting error over here ^^
end
end // class end
class AccessToken
attr_reader :token
def initialize(token)
@token = token
end
def apply!(headers)
headers[''Authorization''] = "Bearer #{@token}"
end
end
Enlace de referencia: https://github.com/google/google-api-ruby-client/issues/296
¿Ha intentado actualizar el token de acceso con el token de actualización? Usted puede atrapar el error y volver a intentarlo.
Algo como esto:
begin
gmail.list_user_messages(linked_account[:uid], q: "#{query}")
rescue Google::Apis::AuthorizationError => exception
client.refresh!
retry
end
Hay varias razones por las que un token de actualización dejaría de funcionar.
- Se pone a los tokens de actualización viejos caducan después de seis meses si no se utilizan.
- Un usuario puede volver a declarar su aplicación y obtener un nuevo token de actualización. Ambos tokens de actualización funcionarán. Puede tener un máximo de cincuenta tokens de actualización pendientes y el primero dejará de funcionar.
- El usuario puede revocar su acceso.
- Error de horario de verano de Wakey de 2015 (no hablaremos de eso)
Gmail y restablecer contraseña.
Esto es principalmente debido a un restablecimiento de contraseña. Las concesiones de OAuth con los ámbitos de gmail se revocan cuando un usuario cambia su contraseña.
Ver la revocación automática del token OAuth 2.0 al cambiar la contraseña
En general, los usuarios pueden revocar subvenciones en cualquier momento. Debe poder manejar ese caso con gracia y alertar al usuario de que necesita volver a autorizar si desea continuar usando la funcionalidad provista.
¿Has estado haciendo muchas pruebas que supongo que estás guardando el token de actualización más reciente? Si no, entonces puedes estar usando tokens de actualización viejos y dejarán de funcionar. (Número 2)
La idea de que el refresh token
nunca caducará es en realidad un malentendido. La escena real es que el servidor emite un token de acceso de corta duración y un token de actualización de larga duración. Entonces, en realidad, lo que sucede es que el token de acceso se puede recuperar utilizando los tokens de actualización de larga duración, pero sí, tendrá que solicitar un token de actualización nuevo (¡ya que también expira!). Por ejemplo; Puedes tratar los tokens de actualización como si nunca caducaran. Sin embargo, en el registro de inicio de sesión para una nueva, en caso de que el usuario revoque el token de actualización, en este caso, Google proporcionará un nuevo token de actualización al iniciar sesión, así que solo actualice el token de actualización.
Ahora la condición puede ser que el usuario revoque el acceso a su aplicación. En este caso, el refresh token
caducará (o debería decir que se convertiría en una autorización no autorizada). Entonces, si ese es el escenario en su caso, tendrá que pensar en evitar la revocación del acceso para la aplicación.
Para comprenderlo mejor, puede consultar este document e incluso la documentación de OAuth 2.0 .
No se ha publicado suficiente código, pero lo que se publica parece incorrecto.
-
linked_account
no está definido - En ninguna parte se muestra que el
linked_account.token
se actualice (o establezca). Debe actualizarse cuando se utiliza elrefresh_token
para obtener un nuevo token de acceso. -
auth
Parece queauth
no está definido en la líneaauth.fetch_access_token!
-
GmailService#authorization=
toma unSignet::OAuth2::Client
no unAccessToken
.
Probablemente, lo que está sucediendo es que tiene un token de acceso válido en linked_account.token
hasta que llame a client.update!
, que recupera un nuevo token de acceso e invalida el anterior. Pero como nunca actualiza el linked_account
, las llamadas futuras fallarán hasta que pase por la ruta del código que lo restablece.
Solo necesitas llamar a client.update!
Si el token de acceso ha caducado, y si ha caducado y obtiene uno nuevo, debe almacenar ese nuevo en linked_account.token
.
Por lo que puedo adivinar, el problema parece estar en estas dos líneas. La forma en que se verifica la caducidad del token y el nuevo token se genera. Sería genial si hay un código mínimo reproducible.
return AccessToken.new(linked_account.token) unless client.expired?
auth.fetch_access_token!
Así es como obtengo mi token de acceso:
def self.access_token(refresh_token)
Cache.fetch(refresh_token, expires_in: 60.minutes) do
url = GoogleService::TOKEN_CREDENTIAL_URI
# p.s. TOKEN_CREDENTIAL_URI = ''https://www.googleapis.com/oauth2/v4/token''
_, response = Request.post(
url,
payload: {
"client_id": GoogleService::CLIENT_ID,
"client_secret": GoogleService::CLIENT_SECRET,
"refresh_token": refresh_token,
"grant_type": "refresh_token"
}
)
response[''access_token'']
end
end
Y luego use este token de acceso para cualquier propósito. Avíseme cómo va y también si puede crear una versión reproducible de la API. Eso será grandioso.