invalid_grant пытается получить токен oAuth от Google

Я продолжаю получать invalid_grant ошибка при попытке получить токен oAuth от Google для подключения к их контактам API. Вся информация верна, и я трижды проверил это так глупо.

Кто-нибудь знает, что может быть причиной этой проблемы? Я попытался настроить другой идентификатор клиента для него, но я получил тот же результат, я попытался подключить много разных способов, включая попытку принудительной аутентификации, но все же тот же результат.

36 ответов

Решение

Я столкнулся с этой проблемой, когда явно не запрашивал "автономный" доступ при отправке пользователя в OAuth "Хотите ли вы дать этому приложению разрешение касаться ваших вещей?" стр.

Убедитесь, что вы указали access_type=offline в вашем запросе.

Подробности здесь: https://developers.google.com/accounts/docs/OAuth2WebServer

(Также: я думаю, что Google добавил это ограничение в конце 2011 года. Если у вас есть старые токены с тех пор, вам нужно отправить своих пользователей на страницу разрешений, чтобы авторизовать автономное использование.)

Хотя это старый вопрос, кажется, что многие все еще сталкиваются с ним - мы потратили дни на то, чтобы выследить это сами.

В спецификации OAuth2 "invalid_grant" является своего рода универсальным средством для всех ошибок, связанных с недействительными / просроченными / отозванными токенами (токен авторизации или обновления).

Для нас проблема была двоякой:

  1. Пользователь активно отозвал доступ к нашему приложению
    Имеет смысл, но получите это: через 12 часов после отзыва Google перестает отправлять сообщение об ошибке в своем ответе:“error_description” : “Token has been revoked.”
    Это вводит в заблуждение, потому что вы будете предполагать, что сообщение об ошибке всегда присутствует, что не так. Вы можете проверить, есть ли у вашего приложения доступ на странице разрешений приложений.

  2. Пользователь сбросил / восстановил свой пароль Google
    В декабре 2015 года Google изменила свое поведение по умолчанию, чтобы сброс пароля для пользователей, не являющихся пользователями Служб Google, автоматически отменял все маркеры обновления приложений пользователя. При отзыве сообщение об ошибке следует тому же правилу, что и в предыдущем случае, поэтому "error_description" вы получите только в течение первых 12 часов. Кажется, нет никакого способа узнать, отменил ли пользователь доступ вручную (умышленно) или это произошло из-за сброса пароля (побочный эффект).

Помимо них, существует множество других потенциальных причин, которые могут вызвать ошибку:

  1. Часы / время сервера не синхронизированы
  2. Не авторизован для автономного доступа
  3. Задушено Google
  4. Использование просроченных токенов обновления
  5. Пользователь неактивен 6 месяцев
  6. Используйте электронную почту работника службы вместо идентификатора клиента
  7. Слишком много токенов доступа за короткое время
  8. Клиентский SDK может быть устаревшим
  9. Неправильный / неполный токен обновления

Я написал небольшую статью с кратким изложением каждого элемента с некоторыми рекомендациями по отладке, чтобы помочь найти виновника. Надеюсь, поможет.

Я столкнулся с этой же проблемой, несмотря на указание "офлайн" access_type в моем запросе согласно ответу bonkydog. Короче говоря, я обнаружил, что решение, описанное здесь, работает для меня:

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

В сущности, когда вы добавляете OAuth2 Client в консоли Google API, Google выдаст вам "Client ID" и "Email адрес" (при условии, что вы выбрали "webapp" в качестве типа клиента). И несмотря на вводящие в заблуждение соглашения об именах Google, они ожидают, что вы отправите "Адрес электронной почты" в качестве значения client_id Параметр, когда вы получаете доступ к их OAuth2 API.

Это применяется при вызове обоих этих URL:

Обратите внимание, что вызов по первому URL будет успешным, если вы позвоните по нему с помощью своего "идентификатора клиента" вместо "адреса электронной почты". Однако использование кода, возвращенного из этого запроса, не будет работать при попытке получить токен на предъявителя со второго URL. Вместо этого вы получите сообщение "Ошибка 400" и сообщение "invalid_grant".

Если вы тестируете это в почтальоне / бессоннице и просто пытаетесь заставить его работать, подсказка: код аутентификации сервера (параметр кода) годен только один раз. Это означает, что если вы заполните любой из других параметров в запросе и получите обратно 400, вам нужно будет использовать новый код аутентификации сервера, иначе вы просто получите еще 400.

Мы перепробовали так много всего, и в конце концов проблема заключалась в том, что клиент отключил «Незащищенный доступ к приложениям» в настройках своей учетной записи Google.

Чтобы включить это:

  1. Перейдите на https://myaccount.google.com/ и управляйте аккаунтом.
  2. Перейдите на вкладку Безопасность
  3. Включите менее безопасный доступ к приложениям

Надеюсь, это кому-то сэкономит время!

Я столкнулся с той же проблемой. Я исправил это, используя адрес электронной почты (строка, заканчивающаяся на...@developer.gserviceaccount.com) вместо идентификатора клиента для значения параметра client_id. Название, заданное Google, сбивает с толку.

Это глупый ответ, но проблема для меня заключалась в том, что я не смог понять, что мне уже выдан активный oAuth-токен для моего пользователя Google, который я не смог сохранить. Решение в этом случае состоит в том, чтобы перейти к консоли API и сбросить секрет клиента.

Есть множество других ответов на SO по этому поводу, например Reset Client Secret OAuth2 - Нужно ли клиентам повторно предоставлять доступ?

Моя проблема заключалась в том, что я использовал этот URL:

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

Когда я должен был использовать этот URL:

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

Это было тестирование учетной записи службы, которая хотела автономный доступ к хранилищу.

Решено путем удаления всех URI авторизованного перенаправления в консоли Google для проекта. Я использую поток на стороне сервера, когда вы используете postmessage в качестве URI перенаправления

Существует две основные причины ошибки invalid_grant, о которых вы должны позаботиться перед запросом POST на токен обновления и токен доступа.

  1. Заголовок запроса должен содержать "content-type: application/x-www-form-urlencoded"
  2. Полезная нагрузка вашего запроса должна быть в форме данных в кодировке URL, а не отправляться как объект json.

RFC 6749 OAuth 2.0 определил invalid_grant как:предоставленный грант авторизации (например, код авторизации, учетные данные владельца ресурса) или токен обновления недействителен, просрочен, отозван, не соответствует URI перенаправления, использованному в запросе авторизации, или был выдан другому клиенту,

Я нашел еще одну хорошую статью, здесь вы найдете много других причин этой ошибки.

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

В моем случае я пытался использовать API-интерфейсы gcloud через программу Go, работающую локально. Недавно я изменил пароль своего пользователя, поэтому сначала попробовалgcloud auth loginно он продолжал выводить то же самоеinvalid_grantошибка.

На самом деле, поскольку вы не взаимодействуете напрямую с API, ваш код, вы должны использоватьgcloud auth application-default loginвместо. Это исправило это для меня.

Используя Android clientId (no client_secret) я получил следующий ответ об ошибке:

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

Я не могу найти никакой документации для поля 'code_verifier', но обнаружил, что если вы установите равные значения как в запросах авторизации, так и в запросах токенов, то эта ошибка будет удалена. Я не уверен, каким должно быть предполагаемое значение или оно должно быть безопасным. Он имеет минимальную длину (16? Символов), но я нашел настройку null тоже работает.

Я использую AppAuth для запроса авторизации в моем клиенте Android, который имеет setCodeVerifier() функция.

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

Вот пример запроса токена в узле:

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);
  }
);

Я проверил, и это работает с обоими https://www.googleapis.com/oauth2/v4/token а также https://accounts.google.com/o/oauth2/token,

Если вы используете GoogleAuthorizationCodeTokenRequest вместо:

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

Код, который вы получаете в URL-адресе после согласия пользователя, имеет очень короткий срок действия. Получите код еще раз и попытайтесь получить токен доступа в течение нескольких секунд (вам нужно поторопиться), и он должен работать. Я не могу узнать срок годности кода, но он буквально очень короткий.

Возможно, вам придется удалить устаревший / недействительный ответ OAuth.

Credit: node.js пример google oauth2 перестал работать invalid_grant

Примечание. Ответ OAuth также станет недействительным, если был изменен пароль, использованный при первоначальной авторизации.

Если в среде bash, вы можете использовать следующее для удаления устаревшего ответа:

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

Попробуйте изменить свой URL для запроса

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

У меня было то же сообщение об ошибке "invalid_grant", и это было потому, что отправка authResult['code'] со стороны клиента javascript не была правильно получена на сервере.

Попробуйте вывести его обратно с сервера, чтобы убедиться, что оно правильное, а не пустая строка.

В моем случае я просто не прочитал документацию должным образом, потому что пытался сделать const { tokens } = await oauth2Client.getToken(accessToken);каждый раз, чтобы получить авторизованный экземпляр клиента, но для последующих запросов вам нужно только включить refresh_tokenвы сохраняете после первой авторизации пользователя.

      oauth2Client.setCredentials({
  refresh_token: `STORED_REFRESH_TOKEN`
});

Ни один из ответов не сработал для меня. После звонка

      https://accounts.google.com/o/oauth2/v2/auth?redirect_uri=<CALLBACK_URL_YOU_SET_ON_GOOGLE>&prompt=consent&response_type=code&client_id=<YOUR CLIENT ID>&scope=openid%20email%20profile&access_type=offline

В ответ я получал URL-адрес ответа, который имел значение

      4%2F0ARtbsJoGfJYFT6Cy1VpPiONgYV1vIzVhGtTK9MvyQLZfuGv_sAPDRDOzqGkRu07qOIxBfg

Вместо декодирования полученного URL-адреса я скопировал это значение и передал его API моего приложения и получил сообщение об ошибке.invalid_grant.
%2Fв приведенном выше коде следует заменить на/. Должен быть

      4/0ARtbsJoGfJYFT6Cy1VpPiONgYV1vIzVhGtTK9MvyQLZfuGv_sAPDRDOzqGkRu07qOIxBfg

Обратите внимание, что этоcodeработает только один раз.

Урок: Всегда расшифровывайте полученный URL!

Это может произойти, если ваш redirect_url не совпадает с тем, который вы указали при создании токена в Google Gloud. Так что убедитесь, что это правильно

Если вы используете библиотеку scribe, просто установите автономный режим, как предложено bonkydog, вот код:

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

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

В моем случае это был URL-адрес обратного вызова, который отличался от исходного запроса. Таким образом, URL-адрес обратного вызова должен быть одинаковым для запроса аутентификации и обмена кодом.

Для меня проблема заключалась в том, что у меня было несколько клиентов в моем проекте, и я почти уверен, что это совершенно нормально, но я удалил всех клиентов для этого проекта и создал новый, и все начали работать на меня (Понял эту идею из справки плагина WP_SMTP Поддержка форума) Я не могу найти эту ссылку для справки

Если вы дезинфицируете ввод пользователя (например, $_GET["code"] в php) Убедитесь, что вы случайно что-то не заменили в коде.

Регулярное выражение, которое я использую, сейчас /[^A-Za-z0-9\/-]/

Для меня я должен был убедиться, что redirect_uri точное соответствие с тем, что в консоли разработчика Authorised redirect URIs, это исправило это для меня, я смог отладить и знаю, что именно было проблема после перехода сhttps://accounts.google.com/o/oauth2/token в https://www.googleapis.com/oauth2/v4/token

Я получил правильную ошибку:

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

В моем случае проблема была в моем коде. По ошибке я пытался инициировать клиента 2 раза с одинаковыми токенами. Если ни один из приведенных выше ответов не помог, убедитесь, что вы не сгенерировали 2 экземпляра клиента.

Мой код до исправления:

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)

как только я изменю его на (используйте только один экземпляр):

@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)

это исправило мои проблемы с типом гранта.

Для меня это было вызвано последующими вызовами с тем же кодом.

А именно, в NestJS моя конечная точка обратного вызова была украшена @UseGuards(AuthGuard('google')) и я пытался позвонить getToken в обратном вызове.

Рассмотрев и опробовав все остальные способы здесь, вот как я решил проблему в nodejs с помощью googleapis модуль в сочетании с request модуль, который я использовал для получения токенов вместо предоставленного getToken() метод:

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);
            }
        });

Я просто использую request сделать запрос API через HTTP, как описано здесь: https://developers.google.com/identity/protocols/OAuth2WebServer

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

Для будущих людей... Я прочитал много статей и блогов, но мне повезло с решением ниже...

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();

Этот блог описывает различные случаи, когда возникает ошибка "invalid_grant".

Наслаждаться!!!

Посмотрите на это https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988

Сначала вам нужен 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;

Сохраните токен доступа, токен обновления и expire_in в базе данных. Срок действия токена доступа истекает через $ expires_in секунд. Затем вам нужно получить новый токен доступа (и сохранить его в базе данных) со следующим запросом:

$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;

Не забывайте добавить домен redirect_uri к своим доменам в консоли Google: https://console.cloud.google.com/apis/credentials на вкладке "OAuth 2.0-Client-IDs". Там вы также найдете свой Client-ID и Client-Secret.

Существует недокументированный тайм-аут между тем, когда вы впервые перенаправляете пользователя на страницу аутентификации Google (и получаете обратно код), и когда вы берете возвращенный код и отправляете его по URL-адресу токена. Он отлично работает для меня с фактическим предоставленным Google client_id, а не с "недокументированным адресом электронной почты". Мне просто нужно было начать процесс заново.

Другие вопросы по тегам