invalid_grant tentando obter o token oAuth do google


120

Sempre recebo um invalid_granterro ao tentar obter um token oAuth do Google para se conectar à API de contatos. Todas as informações estão corretas e eu verifiquei isso de forma meio perplexa.

Alguém sabe o que pode estar causando esse problema? Eu tentei configurar um ID de cliente diferente para ele, mas obtive o mesmo resultado, tentei conectar de várias maneiras diferentes, incluindo a tentativa de autenticação forçada, mas ainda o mesmo resultado.


para mim o problema estava na página de credenciais do Google ... criei outra ... e resolvi o problema ....
costamatrix

Respostas:


59

Eu tive esse problema quando não solicitei explicitamente o acesso "offline" ao enviar o usuário para o OAuth "Deseja dar a este aplicativo permissão para tocar nas suas coisas?" página.

Certifique-se de especificar access_type = offline em sua solicitação.

Detalhes aqui: https://developers.google.com/accounts/docs/OAuth2WebServer#offline

(Além disso: acho que o Google adicionou essa restrição no final de 2011. Se você tiver tokens antigos de antes dessa data, precisará enviar seus usuários à página de permissão para autorizar o uso off-line.)


9
@Adders eu concordo. Eu configurei access_typepara offline, este erro ainda acontece.
slideshowp2

Esta não deve ser a resposta aceita.
Kishan Solanki

Acesse esta documentação developers.google.com/android-publisher/authorization e leia tudo para implementar
Kishan Solanki

70

Encontrei o mesmo problema, apesar de especificar "offline" access_typeem minha solicitação de acordo com a resposta de bonkydog. Resumindo, descobri que a solução descrita aqui funcionou para mim:

https://groups.google.com/forum/#!topic/google-analytics-data-export-api/4uNaJtquxCs

Em essência, quando você adiciona um cliente OAuth2 ao console da API do Google, o Google fornece um "ID de cliente" e um "endereço de e-mail" (supondo que você selecione "webapp" como seu tipo de cliente). E, apesar das convenções de nomenclatura enganosas do Google, eles esperam que você envie o "endereço de e-mail" como o valor do client_idparâmetro ao acessar suas APIs OAuth2.

Isso se aplica ao chamar esses dois URLs:

Observe que a chamada para o primeiro URL será bem - sucedida se você chamá-lo com seu "ID do cliente" em vez de seu "endereço de e-mail". No entanto, usar o código retornado dessa solicitação não funcionará ao tentar obter um token de portador do segundo URL. Em vez disso, você receberá um 'Erro 400' e uma mensagem "invalid_grant".


66
Absolutamente ridículo: especialmente a parte em que funciona com o client_id se você receber um token de atualização inicial. API Googles e sua documentação é uma bagunça.
Traubenfuchs

7
Eu bati minha cabeça contra esse problema por tantas horas. Nunca esperei que 'client_id' não fosse o que era esperado para o campo 'client_id'. Exceto quando ocasionalmente você obtém um refresh_token e ele funciona. Tenho certeza de que as palavras que tenho para google no momento não podem ser ditas no SO.
Justin

6
Olá .. Não consigo encontrar o endereço de "e-mail" de que vocês estão falando. isto é o que tenho em meu console -> pbs.twimg.com/media/CVVcEBWUwAAIiCy.png:large
omarojo

4
Onde está esse endereço de e-mail? Estou tendo o mesmo problema
Isma Haro

4
Nunca confie na documentação do Google. A documentação e as APIs mais ruins vêm do Google, a empresa mais valiosa do mundo. Tive que gastar inúmeras horas para usar a API do Google. Houve problemas após problemas e, em seguida, suas próprias bibliotecas .Net para diferentes APIs não sendo compiladas juntas devido a diferentes problemas de dependência e tudo mais. O código agora funciona bem para a maioria dos usuários, mas para alguns usuários ainda recebo invalid_grant, invalid_credentials etc. por nenhum motivo específico.
Allen King

56

Embora esta seja uma questão antiga, parece que muitos ainda a encontram - passamos dias a fio rastreando isso nós mesmos.

Na especificação OAuth2, "invalid_grant" é uma espécie de pega-tudo para todos os erros relacionados a tokens inválidos / expirados / revogados (concessão de autorização ou token de atualização).

Para nós, o problema era duplo:

  1. O usuário revogou ativamente o acesso ao nosso aplicativo
    Faz sentido, mas veja só: 12 horas após a revogação, o Google para de enviar a mensagem de erro em sua resposta: “error_description” : “Token has been revoked.”
    É um tanto enganoso porque você presumirá que a mensagem de erro está lá o tempo todo, o que não é O caso. Você pode verificar se seu aplicativo ainda tem acesso na página de permissão de aplicativos .

  2. O usuário redefiniu / recuperou sua senha do Google
    Em dezembro de 2015, o Google mudou seu comportamento padrão para que as redefinições de senha para usuários que não fossem do Google Apps revogassem automaticamente todos os tokens de atualização de aplicativos do usuário. Na revogação, a mensagem de erro segue a mesma regra do caso anterior, então você só receberá a "descrição_de_erro" nas primeiras 12 horas. Não parece haver nenhuma maneira de saber se o usuário revogou o acesso manualmente (com intenção) ou se aconteceu devido a uma redefinição de senha (efeito colateral).

Além dessas, há uma miríade de outras causas potenciais que podem desencadear o erro:

  1. O relógio / hora do servidor está fora de sincronia
  2. Não autorizado para acesso offline
  3. Regulado pelo Google
  4. Usando tokens de atualização expirados
  5. O usuário está inativo há 6 meses
  6. Use o e-mail do service worker em vez do ID do cliente
  7. Muitos tokens de acesso em pouco tempo
  8. O SDK do cliente pode estar desatualizado
  9. Token de atualização incorreto / incompleto

Escrevi um pequeno artigo resumindo cada item com algumas orientações de depuração para ajudar a encontrar o culpado. Espero que ajude.


1
Outro cenário é tentar obter tokens várias vezes do mesmo código de autenticação.
knownasilya

Esse foi exatamente o meu problema, simplesmente revogar o aplicativo por acidente. Em seguida, teve que executar novamente o refreshToken.php por meio do terminal para gerar outro código de autorização e, em seguida, substituir o refreshToken em todos os lugares para este clientID.
Robert Sinclair

7

Eu encontrei o mesmo problema. Para mim, eu corrigi isso usando o endereço de e-mail (a string que termina com ... @ developer.gserviceaccount.com) em vez do ID do cliente para o valor do parâmetro client_id. A nomenclatura definida pelo Google é confusa aqui.


6
Esta é a mesma resposta dada por @aroth mais de um ano antes
Bryan Ash

3

Meu problema é que usei este URL:

https://accounts.google.com/o/oauth2/token

Quando deveria ter usado este URL:

https://www.googleapis.com/oauth2/v4/token

Isso estava testando uma conta de serviço que desejava acesso offline ao mecanismo de armazenamento .


2

Eu tive a mesma mensagem de erro 'invalid_grant' e foi porque o authResult ['code'] enviado do lado do cliente javascript não foi recebido corretamente no servidor.

Tente enviar de volta do servidor para ver se está correto e não uma string vazia.


1

se você estiver usando a biblioteca de escriba, apenas configure o modo offline, como o bonkydog sugerido aqui está o código:

OAuthService service = new ServiceBuilder().provider(Google2Api.class).apiKey(clientId).apiSecret(apiSecret)
                .callback(callbackUrl).scope(SCOPE).offline(true)
                .build();

https://github.com/codolutions/scribe-java/


1

Usando um clientId Android (sem client_secret), obtive a seguinte resposta de erro:

{
 "error": "invalid_grant",
 "error_description": "Missing code verifier."
}

Não consigo encontrar nenhuma documentação para o campo 'code_verifier', mas descobri que se você configurá-lo com valores iguais nas solicitações de autorização e de token, esse erro será removido. Não tenho certeza de qual deve ser o valor pretendido ou se deve ser seguro. Tem um comprimento mínimo (16? Caracteres), mas descobri que a configuração para nulltambém funciona.

Estou usando AppAuth para a solicitação de autorização em meu cliente Android, que tem uma setCodeVerifier()função.

AuthorizationRequest authRequest = new AuthorizationRequest.Builder(
                                    serviceConfiguration,
                                    provider.getClientId(),
                                    ResponseTypeValues.CODE,
                                    provider.getRedirectUri()
                            )
                            .setScope(provider.getScope())
                            .setCodeVerifier(null)
                            .build();

Aqui está um exemplo de solicitação de token no nó:

request.post(
  'https://www.googleapis.com/oauth2/v4/token',
  { form: {
    'code': '4/xxxxxxxxxxxxxxxxxxxx',
    'code_verifier': null,
    'client_id': 'xxxxxxxxxxxxxxxxxxxxxx.apps.googleusercontent.com',
    'client_secret': null,
    'redirect_uri': 'com.domain.app:/oauth2redirect',
    'grant_type': 'authorization_code'
  } },
  function (error, response, body) {
    if (!error && response.statusCode == 200) {
      console.log('Success!');
    } else {
      console.log(response.statusCode + ' ' + error);
    }

    console.log(body);
  }
);

Eu testei e isso funciona com ambos https://www.googleapis.com/oauth2/v4/tokenehttps://accounts.google.com/o/oauth2/token .

Se você estiver usando em GoogleAuthorizationCodeTokenRequestvez disso:

final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
                    TRANSPORT,
                    JSON_FACTORY,
                    getClientId(),
                    getClientSecret(),
                    code,
                    redirectUrl
);
req.set("code_verifier", null);          
GoogleTokenResponse response = req.execute();


1

Pode ser necessário remover uma resposta OAuth obsoleta / inválida.

Crédito: a amostra do node.js google oauth2 parou de funcionar invalid_grant

Observação : uma resposta do OAuth também se tornará inválida se a senha usada na autorização inicial for alterada.

Se estiver em um ambiente bash, você pode usar o seguinte para remover a resposta obsoleta:

rm /Users/<username>/.credentials/<authorization.json>


1

Existem duas razões principais para o erro invalid_grant que você deve tomar cuidado antes da solicitação POST para atualizar o token e o token de acesso.

  1. O cabeçalho da solicitação deve conter "content-type: application / x-www-form-urlencoded"
  2. Sua carga útil de solicitação deve ser dados de formulário codificados por url, não envie como objeto json.

RFC 6749 OAuth 2.0 definiu invalid_grant como: A concessão de autorização fornecida (por exemplo, código de autorização, credenciais de proprietário de recurso) ou token de atualização é inválido, expirou, revogado, não corresponde ao URI de redirecionamento usado na solicitação de autorização ou foi emitido para outro cliente .

Encontrei outro bom artigo, aqui você encontrará muitos outros motivos para esse erro.

https://blog.timekit.io/google-oauth-invalid-grant-nightmare-and-how-to-fix-it-9f4efaf1da35


Você quis dizer postar duas respostas quase idênticas? Você pode querer excluir este porque o outro possui uma linha adicional.
Blastfurnace


0

Depois de considerar e tentar todas as outras maneiras aqui, eis como resolvi o problema em nodejs com o googleapismódulo em conjunto com o requestmódulo, que usei para buscar os tokens em vez do getToken()método fornecido :

const request = require('request');

//SETUP GOOGLE AUTH
var google = require('googleapis');
const oAuthConfigs = rootRequire('config/oAuthConfig')
const googleOAuthConfigs = oAuthConfigs.google

//for google OAuth: https://github.com/google/google-api-nodejs-client
var OAuth2 = google.auth.OAuth2;
var googleOAuth2Client = new OAuth2(
    process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId, 
    process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret, 
    process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl);

/* generate a url that asks permissions for Google+ and Google Calendar scopes
https://developers.google.com/identity/protocols/googlescopes#monitoringv3*/
var googleOAuth2ClientScopes = [
    'https://www.googleapis.com/auth/plus.me',
    'https://www.googleapis.com/auth/userinfo.email'
];

var googleOAuth2ClientRedirectURL = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl; 

var googleOAuth2ClientAuthUrl = googleOAuth2Client.generateAuthUrl({
  access_type: 'offline', // 'online' (default) or 'offline' (gets refresh_token)
  scope: googleOAuth2ClientScopes // If you only need one scope you can pass it as string
});

//AFTER SETUP, THE FOLLOWING IS FOR OBTAINING TOKENS FROM THE AUTHCODE


        const ci = process.env.GOOGLE_OAUTH_CLIENT_ID || googleOAuthConfigs.clientId
        const cs = process.env.GOOGLE_OAUTH_CLIENT_SECRET || googleOAuthConfigs.clientSecret
        const ru = process.env.GOOGLE_OAUTH_CLIENT_REDIRECT_URL || googleOAuthConfigs.callbackUrl
        var oauth2Client = new OAuth2(ci, cs, ru);

        var hostUrl = "https://www.googleapis.com";
        hostUrl += '/oauth2/v4/token?code=' + authCode + '&client_id=' + ci + '&client_secret=' + cs + '&redirect_uri=' + ru + '&grant_type=authorization_code',
        request.post({url: hostUrl}, function optionalCallback(err, httpResponse, data) {
            // Now tokens contains an access_token and an optional refresh_token. Save them.
            if(!err) {
                //SUCCESS! We got the tokens
                const tokens = JSON.parse(data)
                oauth2Client.setCredentials(tokens);

                //AUTHENTICATED PROCEED AS DESIRED.
                googlePlus.people.get({ userId: 'me', auth: oauth2Client }, function(err, response) {
                // handle err and response
                    if(!err) {
                        res.status(200).json(response);
                    } else {
                        console.error("/google/exchange 1", err.message);
                        handleError(res, err.message, "Failed to retrieve google person");
                    }
                });
            } else {
                console.log("/google/exchange 2", err.message);
                handleError(res, err.message, "Failed to get access tokens", err.code);
            }
        });

Eu simplesmente uso requestpara fazer a solicitação da API via HTTP, conforme descrito aqui: https://developers.google.com/identity/protocols/OAuth2WebServer#offline

POST /oauth2/v4/token HTTP/1.1
Host: www.googleapis.com
Content-Type: application/x-www-form-urlencoded

code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7&
client_id=8819981768.apps.googleusercontent.com&
client_secret={client_secret}&
redirect_uri=https://oauth2.example.com/code&
grant_type=authorization_code


0

Pra gente do futuro ... Eu li muitos artigos e blogs mas tive sorte com a solução abaixo ...

GoogleTokenResponse tokenResponse =
      new GoogleAuthorizationCodeTokenRequest(
          new NetHttpTransport(),
          JacksonFactory.getDefaultInstance(),
          "https://www.googleapis.com/oauth2/v4/token",
          clientId,
          clientSecret,
          authCode,
          "") //Redirect Url
     .setScopes(scopes)
     .setGrantType("authorization_code")
     .execute();

este blog descreve diferentes casos em que ocorre o erro "invalid_grant".

Aproveitar!!!


0

para mim, eu tive que ter certeza de que redirect_urié uma correspondência exata para o console do desenvolvedor Authorised redirect URIs, que corrigiu para mim, fui capaz de depurar e saber qual era exatamente o problema depois de mudar de https://accounts.google.com/o/oauth2/tokenparahttps://www.googleapis.com/oauth2/v4/token

Recebi um erro adequado:

{"error": "redirect_uri_mismatch",  "error_description": "Bad Request"}

0

Tive esse problema depois de habilitar uma nova API de serviço no console do Google e tentar usar as credenciais feitas anteriormente.

Para resolver o problema, tive que voltar à página da credencial, clicar no nome da credencial e clicar em "Salvar" novamente . Depois disso, eu poderia autenticar muito bem.


0

No meu caso, o problema estava no meu código. Por engano, tentei iniciar o cliente 2 vezes com os mesmos tokens. Se nenhuma das respostas acima ajudou, certifique-se de não gerar 2 instâncias do cliente.

Meu código antes da correção:

def gc_service
      oauth_client = Signet::OAuth2::Client.new(client_options)
      oauth_client.code = params[:code]
      response = oauth_client.fetch_access_token!
      session[:authorization] = response
      oauth_client.update!(session[:authorization])

      gc_service = Google::Apis::CalendarV3::CalendarService.new
      gc_service.authorization = oauth_client

      gc_service
    end
primary_calendar_id = gc_service.list_calendar_lists.items.select(&:primary).first.id

gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)

assim que eu mudar para (use apenas uma instância):

@gc_service = gc_service
primary_calendar_id = @gc_service.list_calendar_lists.items.select(&:primary).first.id

@gc_service.insert_acl(primary_calendar_id, acl_rule_object, send_notifications: false)

ele corrigiu meus problemas com o tipo de concessão.


0

Para mim, o problema era que eu tinha vários clientes em meu projeto e tenho quase certeza de que está tudo bem, mas eu apaguei todos os clientes desse projeto e criei um novo e todos começaram a trabalhar para mim (peguei esta ideia para a ajuda do plugin WP_SMTP fórum de suporte) Não consigo encontrar esse link para referência


0

Se você estiver limpando a entrada do usuário (por exemplo, $_GET["code"]em php), certifique-se de não substituir acidentalmente algo no código.

A regex que estou usando agora é /[^A-Za-z0-9\/-]/


0

Veja este https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988

Primeiro você precisa de um access_token:

$code = $_GET['code'];

$clientid = "xxxxxxx.apps.googleusercontent.com";
$clientsecret = "xxxxxxxxxxxxxxxxxxxxx";

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&code=".urlencode($code)."&grant_type=authorization_code&redirect_uri=". urlencode("https://yourdomain.com"));
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);

$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$refresh_token = $server_output->refresh_token;
$expires_in = $server_output->expires_in;

Proteja o Token de Acesso e o Token de Atualização e o expire_in em um Banco de Dados. O token de acesso expira após $ expires_in segundos. Então você precisa pegar um novo token de acesso (e salvá-lo no banco de dados) com a seguinte solicitação:

$ch = curl_init();
curl_setopt($ch, CURLOPT_URL, "https://www.googleapis.com/oauth2/v4/token");
curl_setopt($ch, CURLOPT_POST, 1);
curl_setopt($ch, CURLOPT_POSTFIELDS, "client_id=".urlencode($clientid)."&client_secret=".urlencode($clientsecret)."&refresh_token=".urlencode($refresh_token)."&grant_type=refresh_token");
curl_setopt($ch, CURLOPT_HTTPHEADER, array('Content-Type: application/x-www-form-urlencoded'));

curl_setopt($ch, CURLOPT_RETURNTRANSFER, true);
$server_output = curl_exec($ch);
curl_close ($ch);

$server_output = json_decode($server_output);
$access_token = $server_output->access_token;
$expires_in = $server_output->expires_in;

Lembre-se de adicionar o domínio redirect_uri aos seus domínios no console do Google: https://console.cloud.google.com/apis/credentials na guia "OAuth 2.0-Client-IDs". Lá você encontra também seu Client-ID e Client-Secret.


0

Há um tempo limite não documentado entre o momento em que você redireciona o usuário pela primeira vez para a página de autenticação do Google (e recebe um código de volta) e o momento em que você pega o código retornado e o publica no url do token. Ele funciona bem para mim com o client_id real fornecido pelo Google, em vez de um "endereço de e-mail não documentado". Eu só precisava reiniciar o processo.


0

Se você está testando isso no postman / insomnia e está apenas tentando fazê-lo funcionar, dica: o código de autenticação do servidor (parâmetro de código) só é válido uma vez. Ou seja, se você colocar qualquer um dos outros parâmetros na solicitação e receber de volta um 400, você precisará usar um novo código de autenticação do servidor ou apenas obterá outro 400.

Ao utilizar nosso site, você reconhece que leu e compreendeu nossa Política de Cookies e nossa Política de Privacidade.
Licensed under cc by-sa 3.0 with attribution required.