invalid_grant próbuje uzyskać token OAuth z Google

121

Ciągle invalid_grantpojawia się błąd podczas próby uzyskania tokena OAuth od Google, aby połączyć się z ich interfejsem API kontaktów. Wszystkie informacje są poprawne i potrójnie to sprawdziłem, więc jestem zaskoczony.

Czy ktoś wie, co może być przyczyną tego problemu? Próbowałem ustawić inny identyfikator klienta, ale otrzymałem ten sam wynik, próbowałem połączyć wiele różnych sposobów, w tym próbę wymuszonego uwierzytelnienia, ale wciąż ten sam wynik.

André Figueira
źródło
dla mnie problem był na stronie z danymi uwierzytelniającymi google ... utworzyłem kolejną ... i rozwiązałem problem ....
costamatrix

Odpowiedzi:

59

Napotkałem ten problem, gdy nie zażądałem wyraźnie dostępu w trybie offline podczas wysyłania użytkownika do protokołu OAuth „Czy chcesz zezwolić tej aplikacji na dotykanie Twoich rzeczy?” strona.

Upewnij się, że w swoim żądaniu określisz access_type = offline.

Szczegóły tutaj: https://developers.google.com/accounts/docs/OAuth2WebServer#offline

(Ponadto: wydaje mi się, że Google dodało to ograniczenie pod koniec 2011 r. Jeśli masz stare tokeny sprzed tego czasu, musisz wysłać swoich użytkowników na stronę uprawnień, aby zezwolić na używanie w trybie offline).

bonkydog
źródło
9
@Adders Zgadzam się. Ustawić access_typena offlineten błąd nadal się dzieje.
pokaz
To nie powinna być akceptowana odpowiedź.
Kishan Solanki
Przejrzyj tę dokumentację dla developers.google.com/android-publisher/authorization i przeczytaj wszystko, co trzeba wdrożyć
Kishan Solanki
70

access_typeNapotkałem ten sam problem pomimo określenia „offline” w mojej prośbie zgodnie z odpowiedzią bonkydog. Krótko mówiąc stwierdziłem, że opisane tutaj rozwiązanie działa dla mnie:

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

Zasadniczo, kiedy dodasz klienta OAuth2 w konsoli Google API, Google poda Ci „Identyfikator klienta” i „Adres e-mail” (zakładając, że jako typ klienta wybierzesz „webapp”). Pomimo wprowadzających w błąd konwencji nazewnictwa Google oczekują, że wyślesz „Adres e-mail” jako wartość client_idparametru, gdy uzyskasz dostęp do ich interfejsu API OAuth2.

Dotyczy to wywoływania obu tych adresów URL:

Pamiętaj, że wywołanie pierwszego adresu URL powiedzie się, jeśli zadzwonisz do niego przy użyciu swojego „ID klienta” zamiast „adresu e-mail”. Jednak użycie kodu zwróconego z tego żądania nie zadziała podczas próby uzyskania tokenu okaziciela z drugiego adresu URL. Zamiast tego zostanie wyświetlony komunikat „Error 400” i „invalid_grant”.

aroth
źródło
66
Absolutnie absurdalne: szczególnie część, w której działa z client_id, jeśli otrzymasz początkowy token odświeżania. Googles API i ich dokumentacja to bałagan.
Traubenfuchs
7
Przez wiele godzin waliłem głową w tę sprawę. Nigdy nie spodziewałem się, że „client_id” nie jest tym, czego oczekiwano dla pola „client_id”. Z wyjątkiem sporadycznych przypadków, kiedy otrzymasz refresh_token i to działa. Jestem prawie pewien, że słów, które mam w tej chwili dla Google, nie można powiedzieć w SO.
Justin
6
Cześć. Nie mogę znaleźć tego adresu e-mail, o którym mówicie. to jest to, co mam w mojej konsoli -> pbs.twimg.com/media/CVVcEBWUwAAIiCy.png:large
omarojo
4
Gdzie jest ten adres e-mail? Mam ten sam problem
Isma Haro,
4
Nigdy nie ufaj dokumentacji Google. Najgorsza dokumentacja i interfejsy API pochodzą od Google, najcenniejszej firmy na świecie. Musiałem spędzić niezliczone godziny, aby korzystać z Google API. Pojawiły się problemy po problemach, a następnie ich własne biblioteki .Net dla różnych interfejsów API nie kompilowały się razem z powodu różnych problemów z zależnościami i wszystkich. Kod działa teraz dobrze dla większości użytkowników, ale w przypadku niektórych użytkowników nadal otrzymuję nieprawidłowe dane, nieprawidłowe dane logowania itp. Bez żadnego konkretnego powodu.
Allen King
56

Chociaż jest to stare pytanie, wydaje się, że wielu wciąż je napotyka - spędziliśmy wiele dni na śledzeniu tego samodzielnie.

W specyfikacji OAuth2 „invalid_grant” to rodzaj catch-all dla wszystkich błędów związanych z nieprawidłowymi / wygasłymi / unieważnionymi tokenami (token autoryzacji lub odświeżania).

Dla nas problem był dwojaki:

  1. Użytkownik aktywnie odwołał dostęp do naszej aplikacji. Ma to
    sens, ale otrzymaj to: 12 godzin po unieważnieniu Google przestaje wysyłać komunikat o błędzie w swojej odpowiedzi: “error_description” : “Token has been revoked.”
    Jest to raczej mylące, ponieważ zakładasz, że komunikat o błędzie jest wyświetlany przez cały czas, co nie jest walizka. Możesz sprawdzić, czy Twoja aplikacja nadal ma dostęp na stronie uprawnień aplikacji .

  2. Użytkownik zresetował / odzyskał swoje hasło Google
    W grudniu 2015 r. Firma Google zmieniła swoje domyślne zachowanie, tak aby resetowanie haseł dla użytkowników spoza Google Apps automatycznie unieważniało wszystkie tokeny odświeżania aplikacji użytkownika. W przypadku unieważnienia, komunikat o błędzie jest zgodny z tą samą regułą, co w poprzednim przypadku, więc „opis_błędu” otrzymasz tylko w ciągu pierwszych 12 godzin. Wydaje się, że nie ma sposobu, aby dowiedzieć się, czy użytkownik ręcznie odwołał dostęp (celowo), czy stało się to z powodu resetowania hasła (efekt uboczny).

Oprócz tego istnieje wiele innych potencjalnych przyczyn, które mogą wywołać błąd:

  1. Brak synchronizacji zegara / czasu serwera
  2. Brak autoryzacji do dostępu w trybie offline
  3. Ograniczony przez Google
  4. Korzystanie z wygasłych tokenów odświeżania
  5. Użytkownik był nieaktywny od 6 miesięcy
  6. Użyj adresu e-mail pracownika serwisu zamiast identyfikatora klienta
  7. Zbyt wiele tokenów dostępu w krótkim czasie
  8. Pakiet Client SDK może być nieaktualny
  9. Nieprawidłowy / niekompletny token odświeżania

Napisałem krótki artykuł podsumowujący każdy element z pewnymi wskazówkami dotyczącymi debugowania, które pomogą znaleźć winowajcę. Mam nadzieję, że to pomoże.

laander
źródło
1
Innym scenariuszem jest wielokrotna próba uzyskania tokenów z tego samego kodu autoryzacji.
knownasilya
To był dokładnie mój problem, po prostu przypadkowo odwołałem aplikację. Następnie trzeba było ponownie uruchomić refreshToken.php przez terminal, aby wygenerować kolejny kod autoryzacji, a następnie zastąpić refreshToken wszędzie dla tego clientID.
Robert Sinclair
7

Napotkałem ten sam problem. W moim przypadku naprawiłem to, używając adresu e-mail (ciąg kończący się na ... @ developer.gserviceaccount.com) zamiast identyfikatora klienta dla wartości parametru client_id. Nazewnictwo ustawione przez Google jest tutaj mylące.

Tony Vu
źródło
6
To ta sama odpowiedź, której udzielił @aroth ponad rok wcześniej
Bryan Ash
3

Mój problem polegał na tym, że użyłem tego adresu URL:

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

Kiedy powinienem był użyć tego adresu URL:

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

To było testowanie konta usługi, które wymagało dostępu offline do silnika pamięci masowej .

Brad Lee
źródło
2

Otrzymałem ten sam komunikat o błędzie „invalid_grant” i było to spowodowane tym, że authResult [„kod”] wysłany z javascript po stronie klienta nie został poprawnie odebrany na serwerze.

Spróbuj wyprowadzić go z powrotem z serwera, aby sprawdzić, czy jest poprawny i czy nie jest to pusty ciąg.

Mihai Crăiță
źródło
1

jeśli używasz biblioteki scribe, po prostu ustaw tryb offline, tak jak sugeruje bonkydog tutaj jest kod:

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

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

Oleksii Kyslytsyn
źródło
1

Używając Android clientId (no client_secret) otrzymałem następującą odpowiedź o błędzie:

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

Nie mogę znaleźć żadnej dokumentacji dla pola „code_verifier”, ale odkryłem, że jeśli ustawisz je na równe wartości zarówno w żądaniach autoryzacji, jak i tokenów, usunie to błąd. Nie jestem pewien, jaka powinna być zamierzona wartość lub czy powinna być bezpieczna. Ma jakąś minimalną długość (16 znaków), ale okazało się, że ustawienie nullrównież działa.

Używam AppAuth do żądania autoryzacji w moim kliencie z Androidem, który ma setCodeVerifier()funkcję.

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

Oto przykładowe żądanie tokena w węźle:

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

Testowałem i to działa zarówno z, jak https://www.googleapis.com/oauth2/v4/tokeni https://accounts.google.com/o/oauth2/token.

Jeśli GoogleAuthorizationCodeTokenRequestzamiast tego używasz :

final GoogleAuthorizationCodeTokenRequest req = new GoogleAuthorizationCodeTokenRequest(
                    TRANSPORT,
                    JSON_FACTORY,
                    getClientId(),
                    getClientSecret(),
                    code,
                    redirectUrl
);
req.set("code_verifier", null);          
GoogleTokenResponse response = req.execute();
Justin Fiedler
źródło
1

To głupia odpowiedź, ale problemem dla mnie było to, że nie zdałem sobie sprawy, że otrzymałem już aktywny token OAuth dla mojego użytkownika Google, którego nie udało mi się zapisać. Rozwiązaniem w tym przypadku jest przejście do konsoli API i zresetowanie klucza tajnego klienta.

Istnieje wiele innych odpowiedzi na SO w tej sprawie, na przykład Reset Client Secret OAuth2 - Czy klienci muszą ponownie przyznać dostęp?

Paweł
źródło
1

Może być konieczne usunięcie nieaktualnej / nieprawidłowej odpowiedzi OAuth.

Źródło : próbka node.js google oauth2 przestała działać nieprawidłowy_grant

Uwaga : odpowiedź OAuth również stanie się nieważna, jeśli hasło użyte podczas początkowej autoryzacji zostanie zmienione.

Jeśli jesteś w środowisku bash, możesz użyć następujących metod, aby usunąć przestarzałą odpowiedź:

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

Lindauson
źródło
1

Istnieją dwa główne powody błędu invalid_grant, na które trzeba uważać przed żądaniem POST o odświeżenie tokena i tokena dostępu.

  1. Nagłówek żądania musi zawierać „content-type: application / x-www-form-urlencoded”
  2. Ładunek żądania powinien być zakodowany w postaci danych formularza w postaci adresu URL, a nie wysyłany jako obiekt json.

RFC 6749 OAuth 2.0 zdefiniowano nieprawidłowy_grant jako: Podane nadanie autoryzacji (np. Kod autoryzacji, poświadczenia właściciela zasobów) lub token odświeżania jest nieprawidłowy, wygasł, unieważniony, nie pasuje do identyfikatora URI przekierowania użytego w żądaniu autoryzacji lub został wydany innemu klientowi .

Znalazłem kolejny dobry artykuł, tutaj znajdziesz wiele innych przyczyn tego błędu.

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

Hisham Javed
źródło
Czy chciałeś zamieścić dwie prawie identyczne odpowiedzi? Możesz chcieć usunąć ten jeden, ponieważ drugi ma dodatkową linię.
Blastfurnace,
1

Jeśli testujesz to w listonoszu / bezsenności i po prostu próbujesz sprawić, by działało, wskazówka: kod autoryzacji serwera (parametr kodu) jest dobry tylko raz. Oznacza to, że jeśli wypełnisz którykolwiek z innych parametrów w żądaniu i odzyskasz 400, będziesz musiał użyć nowego kodu autoryzacji serwera lub po prostu otrzymasz kolejne 400.

Jason Sultana
źródło
0

w tej witrynie console.developers.google.com

ta tablica konsoli wybierz swój projekt wprowadź adres URL przysięgi. adres URL wywołania zwrotnego oauth zostanie przekierowany, gdy zakończy się powodzeniem

user5699596
źródło
0

Po rozważeniu i wypróbowaniu wszystkich innych sposobów tutaj, oto jak rozwiązałem problem w nodejs z googleapismodułem w połączeniu z requestmodułem, którego użyłem do pobrania tokenów zamiast dostarczonej getToken()metody:

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

Po prostu używam requestdo wysyłania żądania API przez HTTP, jak opisano tutaj: 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
lwdthe1
źródło
0

Spróbuj zmienić swój adres URL dla requst na

https://www.googleapis.com/oauth2/v4/token
Sergiy Voytovych
źródło
0

Dla przyszłych ludzi ... Czytałem wiele artykułów i blogów, ale miałem szczęście z poniższym rozwiązaniem ...

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

Ten blog przedstawia różne przypadki, w których pojawia się błąd „invalid_grant”.

Cieszyć się!!!

Kanishk Gupta
źródło
0

dla mnie musiałem się upewnić, że redirect_urijest to dokładne dopasowanie do tego w konsoli programisty Authorised redirect URIs, który naprawił to za mnie, byłem w stanie debugować i wiedziałem, na czym dokładnie polega problem po przełączeniu się z https://accounts.google.com/o/oauth2/tokennahttps://www.googleapis.com/oauth2/v4/token

Mam poprawny błąd:

{"error": "redirect_uri_mismatch",  "error_description": "Bad Request"}
Waqleh
źródło
0

Miałem ten problem po włączeniu nowego interfejsu API usługi w konsoli Google i próbie wykorzystania wcześniej utworzonych poświadczeń.

Aby rozwiązać ten problem, musiałem wrócić do strony danych logowania, klikając na nazwę poświadczeń i kliknięciu przycisku „Zapisz” ponownie . Potem mogłem się uwierzytelnić.

kimphamg
źródło
0

W moim przypadku problem był w moim kodzie. Przez pomyłkę próbowałem 2 razy zainicjować klienta tymi samymi tokenami. Jeśli żadna z powyższych odpowiedzi nie pomogła, upewnij się, że nie generujesz 2 instancji klienta.

Mój kod przed poprawką:

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)

jak tylko zmienię to na (użyj tylko jednej instancji):

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

rozwiązało moje problemy z typem grantu.

Kate Kasinskaya
źródło
0

Dla mnie problem polegał na tym, że miałem wielu klientów w moim projekcie i jestem prawie pewien, że jest to całkowicie w porządku, ale usunąłem całego klienta dla tego projektu i utworzyłem nowego i wszyscy zaczęli dla mnie pracować (Pomysł z pomocy wtyczki WP_SMTP forum pomocy) Nie mogę znaleźć tego linku w celach informacyjnych

Subrata Fouzdar
źródło
0

Jeśli oczyszczasz dane wejściowe użytkownika (na przykład $_GET["code"]w php) Upewnij się, że przypadkowo nie zastąpiłeś czegoś w kodzie.

Wyrażenie regularne, którego używam, jest teraz /[^A-Za-z0-9\/-]/

nathanfranke
źródło
0

Spójrz na to https://dev.to/risafj/beginner-s-guide-to-oauth-understanding-access-tokens-and-authorization-codes-2988

Najpierw potrzebujesz 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;

Zabezpiecz token dostępu i token odświeżania oraz expire_in w bazie danych. Token dostępu wygasa po $ expires_in sekund. Następnie musisz pobrać nowy token dostępu (i zabezpieczyć go w bazie danych) za pomocą następującego żądania:

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

Pamiętaj, aby dodać domenę redirect_uri do swoich domen w konsoli Google: https://console.cloud.google.com/apis/credentials w zakładce „OAuth 2.0-Client-IDs”. Znajdziesz tam również swój Client-ID i Client-Secret.

Konstantin XFlash Stratigenas
źródło
0

Występuje nieudokumentowany limit czasu między pierwszym przekierowaniem użytkownika na stronę uwierzytelniania Google (i odzyskaniem kodu) a pobraniem zwróconego kodu i wysłaniem go na adres URL tokena. W moim przypadku działa dobrze z rzeczywistym identyfikatorem client_id podanym przez Google, a nie z „nieudokumentowanym adresem e-mail”. Musiałem tylko ponownie rozpocząć proces.

sparkyspider
źródło