Não está recebendo o token de atualização do Google OAuth

270

Quero obter o token de acesso do Google. A API do Google diz que, para obter o token de acesso, envie o código e outros parâmetros para a página de geração de token, e a resposta será um objeto JSON como:

{
"access_token" : "ya29.AHES6ZTtm7SuokEB-RGtbBty9IIlNiP9-eNMMQKtXdMP3sfjL1Fc",
"token_type" : "Bearer",
"expires_in" : 3600,
"refresh_token" : "1/HKSmLFXzqP0leUihZp2xUt3-5wkU7Gmu2Os_eBnzw74"
}

No entanto, não estou recebendo o token de atualização. A resposta no meu caso é:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}
Muhammad Usman
fonte
Eu tive um problema semelhante. Verifique minha resposta aqui
Arithran

Respostas:

675

O refresh_tokené fornecido apenas na primeira autorização do usuário. As autorizações subsequentes, como as que você faz ao testar uma integração OAuth2, não retornarão refresh_tokennovamente. :)

  1. Vá para a página que mostra os aplicativos com acesso à sua conta: https://myaccount.google.com/u/0/permissions .
  2. No menu Aplicativos de terceiros, escolha seu aplicativo.
  3. Clique em Remover acesso e clique em OK para confirmar
  4. A próxima solicitação do OAuth2 que você fizer retornará um refresh_token(desde que também inclua o parâmetro de consulta 'access_type = offline').

Como alternativa, você pode adicionar os parâmetros de consulta prompt=consent&access_type=offlineao redirecionamento do OAuth (consulte a página do Google OAuth 2.0 para aplicativos de servidor da Web ).

Isso solicitará que o usuário autorize o aplicativo novamente e sempre retornará a refresh_token.

Rich Sutton
fonte
20
Isso não funcionou para mim, mas adicionar o parâmetro "access_type = offline" parecia funcionar: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse
87
Você precisa access_type=offlineem todos os casos quando quiser refresh_token.
DANH
5
Mas como atualizo o token após sua expiração neste caso?
vivek_jonam
5
@vivek_jonam Armazene o token de atualização e a data de validade. Quando expira, você solicita um novo token usando o token de atualização. Veja aqui: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis
4
Eu trabalhei com isso $client->setAccessType('offline'). O function setApprovalPrompt()já é passado force, por padrão.
21415
57

Para obter o token de atualização, é necessário adicionar ambos approval_prompt=forcee, access_type="offline" se você estiver usando o cliente java fornecido pelo Google, terá a seguinte aparência:

GoogleAuthorizationCodeFlow flow = new GoogleAuthorizationCodeFlow.Builder(
            HTTP_TRANSPORT, JSON_FACTORY, getClientSecrets(), scopes)
            .build();

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");
Gal Morad
fonte
No nó: var authUrl = oauth2Client.generateAuthUrl ({access_type: 'offline', escopo: SCOPES, aprovação_prompt: 'force'});
Joris Mans
2
É escandaloso que o google não tenha abordado isso na documentação deles ou pelo menos não na documentação php ou oath2 que eu observei por 7 horas. Por que o mundo não é este em grande texto em negrito em suas docs
Colin Rickels
Obrigado! Os documentos aqui ( github.com/googlesamples/apps-script-oauth2 ) são muito enganadores sobre esse parâmetro. Quando adicionei aprovação_prompt = força, finalmente recebi um token de atualização.
Alex Zhevzhik
28

Eu procurei uma longa noite e isso está fazendo o truque:

User-example.php modificado de admin-sdk

$client->setAccessType('offline');
$client->setApprovalPrompt('force');
$authUrl = $client->createAuthUrl();
echo "<a class='login' href='" . $authUrl . "'>Connect Me!</a>";

você obtém o código no URL de redirecionamento e a autenticação com o código e obtém o token de atualização

$client()->authenticate($_GET['code']);
echo $client()->getRefreshToken();

Você deve armazená-lo agora;)

Quando a sua tecla de acesso expira, basta

$client->refreshToken($theRefreshTokenYouHadStored);
Norbert
fonte
Perfect @ Norbert, era exatamente isso que eu precisava.
Emmanuel
Obrigado! Resposta exata para a minha pergunta @Norbert
-MVT
16

Isso me causou alguma confusão, então pensei em compartilhar o que aprendi da maneira mais difícil:

Ao solicitar acesso usando os parâmetros access_type=offlinee, approval_prompt=forcevocê deve receber um token de acesso e um token de atualização . O token de acesso expira logo após o recebimento e você precisará atualizá-lo.

Você fez corretamente a solicitação para obter um novo token de acesso e recebeu a resposta que possui seu novo token de acesso . Também fiquei confuso pelo fato de não ter recebido um novo token de atualização . No entanto, é assim que deve ser, pois você pode usar o mesmo token de atualização repetidamente.

Acho que algumas das outras respostas presumem que você deseje obter um novo token de atualização por algum motivo e sugeriu que você re-autorize o usuário, mas, na verdade, você não precisa, pois o token de atualização que você possui funcionará até revogado pelo usuário.

jeteon
fonte
1
Eu tenho um CMS em que usuários diferentes usam contas do Google diferentes para se conectar à API do Google Analytics. No entanto, às vezes vários usuários podem se conectar usando a mesma conta do Google corporativa, mas cada um quer acessar uma conta diferente do Google Analytics. Somente o primeiro recebe o token de atualização, enquanto todos os outros não recebem e, portanto, precisam se reconectar a cada hora. Não existe uma maneira de obter o mesmo token de atualização para autenticações subsequentes, em vez de apenas o access_token que expira em uma hora?
SsjCosty
1
A API parece produzir o token de atualização exatamente uma vez. Qualquer "compartilhamento" do token teria que acontecer no seu código. Você teria que ter cuidado para não acidentalmente conceder novos privilégios de acesso aos usuários. Uma maneira simples de fazer isso é fazer com que o aplicativo acompanhe os tokens de atualização e as contas associadas em seu próprio armazenamento ('tabela' separada no SQLese). Então, quando você quiser obter um novo token de acesso, verifique e use esse token possivelmente comum a partir daí. Implementado de uma certa maneira, seu código não precisa saber quem realmente recebeu o token.
jeteon
1
Não sei como identificar qual token de atualização devo associar a um novo token de acesso que acabei de obter. Existem usuários diferentes que fazem o logon, e a única coisa que eles têm em comum é que eles usam a mesma conta do Google (email) para se conectar à API. Mas o Google não envia um ID da conta ou e-mail, apenas envia um token. Então, eu não sei como associar os 2 usuários diferentes CMS ...
SsjCosty
Expliquei totalmente o meu problema aqui: stackoverflow.com/questions/30217524/...
SsjCosty
Refresh_token do YouTube oAuth2 mostrado apenas quando usado com força.
Dmitry Polushkin
7

A resposta de Rich Sutton finalmente funcionou para mim, depois que percebi que a adição access_type=offlineé feita na solicitação de um código de autorização do cliente front-end , não na solicitação de back-end que troca esse código por um access_token. Adicionei um comentário à resposta dele e este link no Google para obter mais informações sobre como atualizar tokens.

PS Se você estiver usando o Satellizer, veja como adicionar essa opção ao $ authProvider.google no AngularJS .

Zack Morris
fonte
Detalhes muito pequenos, mas importantes. Me salvou ! Obrigado :)
Dexter
@ZackMorris Então ... você quer dizer que não consigo obter refresh_token do back-end usando o token de acesso?
Nevermore
@ Nunca mais Você não pode obter um refresh_token a partir do próprio access_token. Se você deseja que seu servidor lide com atualizações, precisará armazenar o refresh_token no seu banco de dados pela primeira vez. Além disso, se você estiver executando um fluxo OAuth do cliente no front-end, os usuários terão que enviar seu refresh_token para o back-end se quiserem que o servidor atualize para eles.
Zack Morris
4

Para obter o que refresh_tokenvocê precisa, inclua access_type=offlineno URL de solicitação do OAuth. Quando um usuário se autentica pela primeira vez, você recupera um valor nulo refresh_tokene um valor access_tokenexpirado.

Se você tiver uma situação em que um usuário possa autenticar novamente uma conta para a qual já possui um token de autenticação (como o @SsjCosty mencionou acima), será necessário recuperar informações do Google em que conta o token é. Para fazer isso, adicione profileaos seus escopos. Usando a gema OAuth2 Ruby, sua solicitação final pode ser algo como isto:

client = OAuth2::Client.new(
  ENV["GOOGLE_CLIENT_ID"],
  ENV["GOOGLE_CLIENT_SECRET"],
  authorize_url: "https://accounts.google.com/o/oauth2/auth",
  token_url: "https://accounts.google.com/o/oauth2/token"
)

# Configure authorization url
client.authorize_url(
  scope: "https://www.googleapis.com/auth/analytics.readonly profile",
  redirect_uri: callback_url,
  access_type: "offline",
  prompt: "select_account"
)

Observe que o escopo possui duas entradas delimitadas por espaço, uma para acesso somente leitura ao Google Analytics e a outra é justa profile, que é um padrão do OpenID Connect.

Isso fará com que o Google forneça um atributo adicional chamado id_tokenna get_tokenresposta. Para obter informações sobre o id_token, confira esta página nos documentos do Google. Existem várias bibliotecas fornecidas pelo Google que validam e decodificam isso para você (usei a gema do Ruby google-id-token ). Depois de analisado, o subparâmetro é efetivamente o ID exclusivo da conta do Google.

Vale ressaltar que, se você alterar o escopo, receberá novamente um token de atualização para usuários que já se autenticaram com o escopo original. Isso é útil se, por exemplo, você já tiver vários usuários e não quiser fazer com que todos cancelem a autenticação do aplicativo no Google.

Ah, e uma observação final: você não precisa prompt=select_account , mas é útil se você tiver uma situação em que seus usuários possam querer se autenticar com mais de uma conta do Google (por exemplo, você não está usando isso para fazer login / autenticação) .

coreyward
fonte
Eu acho que a parte de identificar usuários sem armazenar nenhuma informação pessoal é fundamental. Obrigado por apontar, não vi nenhuma referência no google docs sobre isso.
precisa saber é o seguinte
3

1. Como obter o 'refresh_token'?

Solução: a opção access_type = 'offline' deve ser usada ao gerar authURL. fonte: usando o OAuth 2.0 para aplicativos de servidor Web

2. Mas mesmo com 'access_type = offline', não estou recebendo o 'refresh_token'?

Solução: observe que você o obterá apenas na primeira solicitação; portanto, se você o estiver armazenando em algum lugar e houver uma disposição para substituí-lo no seu código ao obter novo access_token após a expiração anterior, certifique-se de não substituir esse valor.

Do documento de autenticação do Google: (este valor = access_type)

Esse valor instrui o servidor de autorização do Google a retornar um token de atualização e um token de acesso na primeira vez em que seu aplicativo troca um código de autorização por tokens.

Se você precisar de 'refresh_token' novamente, precisará remover o acesso ao seu aplicativo, seguindo as etapas escritas na resposta de Rich Sutton .

Mazahir Haroon
fonte
2

Definir isso fará com que o token de atualização seja enviado sempre:

$client->setApprovalPrompt('force');

Um exemplo é dado abaixo (php):

$client = new Google_Client();
$client->setClientId($client_id);
$client->setClientSecret($client_secret);
$client->setRedirectUri($redirect_uri);
$client->addScope("email");
$client->addScope("profile"); 
$client->setAccessType('offline');
$client->setApprovalPrompt('force');
apadana
fonte
1

Para mim, eu estava testando o CalendarSampleServletGoogle. Após 1 hora, o access_key expira e há um redirecionamento para uma página 401. Eu tentei todas as opções acima, mas elas não funcionaram. Finalmente, ao verificar o código-fonte para 'AbstractAuthorizationCodeServlet' , pude ver que o redirecionamento seria desativado se credenciais estivessem presentes, mas, idealmente, ele deveria ter verificado refresh token!=null. Eu adicionei o código abaixo CalendarSampleServlete funcionou depois disso. Grande alívio depois de tantas horas de frustração. Graças a Deus.

if (credential.getRefreshToken() == null) {
    AuthorizationCodeRequestUrl authorizationUrl = authFlow.newAuthorizationUrl();
    authorizationUrl.setRedirectUri(getRedirectUri(req));
    onAuthorization(req, resp, authorizationUrl);
    credential = null;
}
Anoop Isaac
fonte
0

agora o google recusou esses parâmetros no meu pedido (access_type, prompt) ... :( e não existe o botão "Revogar acesso". Estou frustrado por ter voltado meu refresh_token lol

ATUALIZAÇÃO: Encontrei a resposta aqui: D você pode recuperar o token de atualização mediante uma solicitação https://developers.google.com/identity/protocols/OAuth2WebServer

curl -H "Tipo de conteúdo: application / x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token= {token}

O token pode ser um token de acesso ou um token de atualização. Se o token for um token de acesso e tiver um token de atualização correspondente, o token de atualização também será revogado.

Se a revogação for processada com êxito, o código de status da resposta será 200. Para condições de erro, um código de status 400 será retornado junto com um código de erro.

Tran Tien Duc
fonte
0
    #!/usr/bin/env perl

    use strict;
    use warnings;
    use 5.010_000;
    use utf8;
    binmode STDOUT, ":encoding(utf8)";

    use Text::CSV_XS;
    use FindBin;
    use lib $FindBin::Bin . '/../lib';
    use Net::Google::Spreadsheets::V4;

    use Net::Google::DataAPI::Auth::OAuth2;

    use lib 'lib';
    use Term::Prompt;
    use Net::Google::DataAPI::Auth::OAuth2;
    use Net::Google::Spreadsheets;
    use Data::Printer ;


    my $oauth2 = Net::Google::DataAPI::Auth::OAuth2->new(
         client_id => $ENV{CLIENT_ID},
         client_secret => $ENV{CLIENT_SECRET},
         scope => ['https://www.googleapis.com/auth/spreadsheets'],
    );
    my $url = $oauth2->authorize_url();
    # system("open '$url'");
    print "go to the following url with your browser \n" ;
    print "$url\n" ;
    my $code = prompt('x', 'paste code: ', '', '');
    my $objToken = $oauth2->get_access_token($code);

    my $refresh_token = $objToken->refresh_token() ;

    print "my refresh token is : \n" ;
    # debug p($refresh_token ) ;
    p ( $objToken ) ;


    my $gs = Net::Google::Spreadsheets::V4->new(
            client_id      => $ENV{CLIENT_ID}
         , client_secret  => $ENV{CLIENT_SECRET}
         , refresh_token  => $refresh_token
         , spreadsheet_id => '1hGNULaWpYwtnMDDPPkZT73zLGDUgv5blwJtK7hAiVIU'
    );

    my($content, $res);

    my $title = 'My foobar sheet';

    my $sheet = $gs->get_sheet(title => $title);

    # create a sheet if does not exit
    unless ($sheet) {
         ($content, $res) = $gs->request(
              POST => ':batchUpdate',
              {
                    requests => [
                         {
                              addSheet => {
                                    properties => {
                                         title => $title,
                                         index => 0,
                                    },
                              },
                         },
                    ],
              },
         );

         $sheet = $content->{replies}[0]{addSheet};
    }

    my $sheet_prop = $sheet->{properties};

    # clear all cells
    $gs->clear_sheet(sheet_id => $sheet_prop->{sheetId});

    # import data
    my @requests = ();
    my $idx = 0;

    my @rows = (
         [qw(name age favorite)], # header
         [qw(tarou 31 curry)],
         [qw(jirou 18 gyoza)],
         [qw(saburou 27 ramen)],
    );

    for my $row (@rows) {
         push @requests, {
              pasteData => {
                    coordinate => {
                         sheetId     => $sheet_prop->{sheetId},
                         rowIndex    => $idx++,
                         columnIndex => 0,
                    },
                    data => $gs->to_csv(@$row),
                    type => 'PASTE_NORMAL',
                    delimiter => ',',
              },
         };
    }

    # format a header row
    push @requests, {
         repeatCell => {
              range => {
                    sheetId       => $sheet_prop->{sheetId},
                    startRowIndex => 0,
                    endRowIndex   => 1,
              },
              cell => {
                    userEnteredFormat => {
                         backgroundColor => {
                              red   => 0.0,
                              green => 0.0,
                              blue  => 0.0,
                         },
                         horizontalAlignment => 'CENTER',
                         textFormat => {
                              foregroundColor => {
                                    red   => 1.0,
                                    green => 1.0,
                                    blue  => 1.0
                              },
                              bold => \1,
                         },
                    },
              },
              fields => 'userEnteredFormat(backgroundColor,textFormat,horizontalAlignment)',
         },
    };

    ($content, $res) = $gs->request(
         POST => ':batchUpdate',
         {
              requests => \@requests,
         },
    );

    exit;

    #Google Sheets API, v4

    # Scopes
    # https://www.googleapis.com/auth/drive   View and manage the files in your Google D# # i# rive
    # https://www.googleapis.com/auth/drive.file View and manage Google Drive files and folders that you have opened or created with this app
    # https://www.googleapis.com/auth/drive.readonly   View the files in your Google Drive
    # https://www.googleapis.com/auth/spreadsheets  View and manage your spreadsheets in Google Drive
    # https://www.googleapis.com/auth/spreadsheets.readonly  View your Google Spreadsheets
Yordan Georgiev
fonte
0

Usando o acesso offline e o prompt: o consentimento funcionou bem para mim:

   auth2 = gapi.auth2.init({
                    client_id: '{cliend_id}' 
   });

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 
Carlos Eduardo Ki Lee
fonte
0

Minha solução foi um pouco estranha .. eu tentei todas as soluções que encontrei na internet e nada. Surpreendentemente, isso funcionou: exclua as credenciaiss.json, atualize, vincule seu aplicativo à sua conta novamente. O novo arquivo credentials.json terá o token de atualização. Faça backup desse arquivo em algum lugar. Continue usando seu aplicativo até que o erro de atualização do token volte novamente. Exclua o arquivo crendetials.json que agora está apenas com uma mensagem de erro (isso aconteceu no meu caso) e cole o arquivo de credenciais antigo na pasta, pronto! Já faz uma semana desde que fiz isso e não tive mais problemas.

Guilherme Canoa
fonte
0

Para obter novos refresh_token a cada vez na autenticação, o tipo de credenciais do OAuth 2.0 criadas no painel deve ser "Outro". Também conforme mencionado acima, a opção access_type = 'offline' deve ser usada ao gerar o authURL.

Ao usar credenciais com o tipo "Aplicativo da Web", nenhuma combinação de variáveis ​​de prompt / aprovação_prompt funcionará - você ainda obterá o refresh_token somente na primeira solicitação.

Martin Kask
fonte