Google OAuth更新トークンを受け取っていない


270

Googleからアクセストークンを取得したい。 Google APIによると、アクセストークンを取得するには、コードとその他のパラメーターをトークン生成ページに送信すると、応答は次のようなJSONオブジェクトになります。

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

ただし、更新トークンを受け取っていません。私の場合の応答は:

{
 "access_token" : "ya29.sddsdsdsdsds_h9v_nF0IR7XcwDK8XFB2EbvtxmgvB-4oZ8oU",
"token_type" : "Bearer",
"expires_in" : 3600
}

同様の問題がありました。ここで
Arithran

回答:


675

これrefresh_tokenは、ユーザーからの最初の承認時にのみ提供されます。OAuth2統合のテスト中に行った種類などのその後の承認では、refresh_token再び返されません。:)

  1. アカウントにアクセスできるアプリが表示されているページにアクセスします:https : //myaccount.google.com/u/0/permissions
  2. [サードパーティアプリ]メニューで、アプリを選択します。
  3. [アクセスを削除]をクリックし、[OK]をクリックして確認します
  4. 次のOAuth2リクエストはrefresh_token( 'access_type = offline'クエリパラメータも含まれている場合)を返します。

または、クエリパラメータprompt=consent&access_type=offlineをOAuthリダイレクトに追加することもできます(ウェブサーバーアプリケーション用の GoogleのOAuth 2.0ページをご覧ください)。

これにより、アプリケーションを再度承認するようユーザーに求められ、常にを返しますrefresh_token


20
これは私にとってはうまくいきませんでしたが、パラメーター "access_type = offline"を追加することで問題が解決したようです:developer.google.com/accounts/docs/OAuth2WebServer#offline
Jesse

87
access_type=offlineすべてのケースで、必要なときに必要ですrefresh_token
DanH 2013年

5
しかし、この場合、トークンの有効期限が切れた後にトークンを更新するにはどうすればよいですか?
vivek_jonam 2013

5
@vivek_jonam更新トークンと有効期限を保存します。有効期限が切れたら、更新トークンを使用して新しいトークンをリクエストします。こちらをご覧ください:developer.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis

4
で動作しました$client->setAccessType('offline')function setApprovalPrompt()すでに渡されforce、デフォルトでは、。
moey

57

更新トークンを取得するには、両方を追加する 必要があります。Googleが提供するJavaクライアントを使用している場合はapproval_prompt=forceaccess_type="offline"次のようになります。

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

AuthorizationCodeRequestUrl authorizationUrl =
            flow.newAuthorizationUrl().setRedirectUri(callBackUrl)
                    .setApprovalPrompt("force")
                    .setAccessType("offline");

ノード内:var authUrl = oauth2Client.generateAuthUrl({access_type: 'offline'、scope:SCOPES、Approval_prompt: 'force'});
Joris Mans

2
グーグルが彼らのドキュメントで、または少なくとも私が7時間見つめていたphpまたはoath2ドキュメントでこれに対処しなかったことはばかげたことです。世界でこれは彼らのドキュメントに大きな太字でないのはなぜ
コリン・Rickels

ありがとうございました!ここのドキュメント(github.com/googlesamples/apps-script-oauth2)は、このパラメーターについて非常に誤解を招くものです。私がapproval_prompt = forceを追加すると、ようやく更新トークンを取得しました。
Alex Zhevzhik 2017年

28

私は長い夜を検索しました、そしてこれはトリックをしています:

admin-sdkから変更されたuser-example.php

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

次に、リダイレクトURLでコードを取得し、コードで認証して、更新トークンを取得します

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

今すぐ保存してください;)

アクセスキーがタイムアウトしたときは、

$client->refreshToken($theRefreshTokenYouHadStored);

パーフェクト@Norbert、これはまさに私が必要としていたものでした。
Emmanuel、

ありがとう!私の質問に対する正確な回答@Norbert
varun teja-MVT

16

これは私にいくつかの混乱を引き起こしたので、私は私が難しい方法を学ぶために来たものを共有したいと思いました:

パラメータaccess_type=offlineapproval_prompt=forceパラメータを使用してアクセスをリクエストすると、アクセストークンと更新トークンの両方を受け取る必要があります。アクセストークンは、あなたがそれを受け取り、あなたがそれを更新する必要があります後すぐに期限が切れます。

あなたは正しく新しい取得するための要求を作っアクセストークンをして、新しい持つ応答受信アクセストークンを。また、新しい更新トークンを取得できなかったので困惑しました。ただし、同じリフレッシュトークンを何度も使用できるため、これが本来の目的です。

他の回答のいくつかは、何らかの理由で自分に新しいリフレッシュトークンを取得したいと想定しており、ユーザーを再認証することを提案したと思いますが、実際には、持っているリフレッシュトークンが機能するまで必要はありません。ユーザーによって取り消されました。


1
さまざまなユーザーがさまざまなGoogleアカウントを使用して分析APIに接続するCMSがあります。ただし、複数のユーザーが同じ企業のgoogleアカウントを使用して接続できる場合がありますが、各ユーザーは異なるAnalyticsアカウントへのアクセスを望んでいます。最初のものだけがリフレッシュトークンを受け取りますが、他のすべては受け取らないため、1時間ごとに再接続する必要があります。1時間以内に期限切れになるaccess_tokenだけでなく、後続の認証用にSAMEリフレッシュトークンを取得する方法はありませんか?
SsjCosty 2015年

1
APIは更新トークンを1回だけ生成するようです。トークンの「共有」は、コードで行う必要があります。ただし、誤ってユーザーに新しいアクセス権限を付与しないように注意する必要があります。これを行う簡単な方法は、アプリケーションにリフレッシュ トークンと関連するアカウントを独自のストレージ(SQLeseの個別の「テーブル」)で追跡させることです。次に、新しいアクセストークンを取得する場合は、そこからこの一般的なトークンを確認して使用します。特定の方法で実装されているため、コードは実際にトークンを取得した人を知る必要はありません。
jeteon 2015年

1
取得した新しいアクセストークンに関連付ける必要のある更新トークンをどのようにして識別できるかわかりません。ログインを行うユーザーはさまざまですが、共通しているのは、同じGoogleアカウント(メール)を使用してAPIに接続していることだけです。ただし、GoogleはアカウントのIDや電子メールを送り返すのではなく、トークンを送り返すだけです。したがって、2つの異なるCMSユーザーを関連付ける方法がわかりません...
SsjCosty

私は完全にここに私の問題を説明していますstackoverflow.com/questions/30217524/...
SsjCosty

Youtube oAuth2 refresh_tokenは、強制的に使用した場合にのみ表示されます。
Dmitry Polushkin 2016

7

Rich Suttonの答えは、認証コードに対するフロントエンドクライアントのリクエストではなく、そのコードをaccess_tokenと交換するバックエンドリクエストで追加access_type=offlineが行われることに気付いた後、ようやくうまくいきました。トークンの更新の詳細については、彼の回答とGoogleのこのリンクにコメントを追加しました。

PS Satellizerを使用している場合、AngularJSの$ authProvider.googleにそのオプションを追加する方法は次のとおりです。


非常にマイナーな詳細ですが重要です。助かりました !ありがとう:)
デクスター

@ZackMorrisでは、アクセストークンを使用してバックエンドからrefresh_tokenを取得できないということですか。
Nevermore

@Nevermore access_token自体からrefresh_tokenを取得することはできません。サーバーで更新を処理する場合は、refresh_tokenをデータベースに初めて保存する必要があります。また、フロントエンドでクライアントOAuthフローを実行している場合、ユーザーがサーバーでリフレッシュするようにするには、ユーザーがバックエンドにrefresh_tokenを送信する必要があります。
ザックモリス

4

を取得refresh_tokenするにaccess_type=offlineは、OAuthリクエストURL に含める必要があります。ユーザーが初めて認証をrefresh_token行うaccess_tokenと、期限が切れた非nilとが返されます。

認証トークンをすでに持っているアカウントをユーザーが再認証する可能性がある場合(上記の@SsjCostyについての説明など)、トークンがどのアカウントのアカウントであるかをGoogleから取得する必要があります。これを行うには、profileスコープに追加します。OAuth2 Ruby gemを使用すると、最終的なリクエストは次のようになります。

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

スコープにはスペースで区切られた2つのエントリがあることに注意してください。1つはGoogleアナリティクスへの読み取り専用アクセス用で、もう1つprofileはOpenID Connect標準であるだけです。

これにより、Google id_tokenget_token応答で呼び出される追加の属性を提供します。id_tokenから情報を取得するには、Googleドキュメントのこのページをご覧ください。これを検証して「デコード」するGoogle提供のライブラリがいくつかあります(私はRubyのgoogle-id-token gemを使用しました)。解析されると、subパラメーターは事実上、一意のGoogleアカウントIDになります。

注目に値するのは、スコープを変更すると、元のスコープで既に認証されているユーザーの更新トークンが再び返されることです。これは、たとえば、すでに多数のユーザーがいて、Googleでアプリの認証をすべて解除したくない場合に便利です。

ああ、最後のメモ:は必要あり ませんprompt=select_account、ユーザーが複数のGoogleアカウントで認証したい場合(つまり、これをサインイン/認証に使用していない場合)に役立ちます。 。


個人情報を保存せずにユーザーを特定する部分がカギだと思います。それを指摘してくれてありがとう、それについてのグーグルドキュメント上の参照を見なかった。
Danielo515

3

1.「refresh_token」を取得するにはどうすればよいですか?

解決策: authURLを生成するときは、access_type = 'offline'オプションを使用する必要があります。ソース:WebサーバーアプリケーションでのOAuth 2.0の使用

2.しかし、「access_type = offline」を使用しても、「refresh_token」が取得されません。

解決策:最初のリクエストでのみそれを取得することに注意してください。それをどこかに保存していて、以前の期限が切れた後に新しいaccess_tokenを取得するときにコードでこれを上書きする規定がある場合は、この値を上書きしないようにしてください。

Google Auth Docから:(この値= access_type)

この値は、アプリケーションがトークンの認証コードを初めて交換するときに、更新トークンとアクセストークンを返すようにGoogle承認サーバーに指示します。

再度「refresh_token」が必要な場合は、Rich Suttonの回答に記載されている手順に従って、アプリへのアクセスを削除する必要があります


2

これを設定すると、毎回更新トークンが送信されます。

$client->setApprovalPrompt('force');

以下に例を示します(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');

1

私はCalendarSampleServletGoogle から提供されたものを試しました。1時間後、access_keyがタイムアウトし、401ページへのリダイレクトがあります。上記のオプションをすべて試しましたが、うまくいきませんでした。最後に、「AbstractAuthorizationCodeServlet」のソースコードを確認したところ、資格情報が存在する場合はリダイレクトが無効になることがわかりましたが、理想的には確認する必要がありましたrefresh token!=null。以下にコードを追加CalendarSampleServletしましたが、その後機能しました。何時間もの欲求不満の後の大きな安心。ああ、助かった。

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

0

今グーグルは私のリクエスト(access_type、プロンプト)でこれらのパラメータを拒否しました... :(そして「アクセスを取り消す」ボタンはまったくありません。refresh_tokenlolを取り戻すので私はイライラしています

更新:私はここで答えを見つけました:D https://developers.google.com/identity/protocols/OAuth2WebServerリクエストで更新トークンを取得でき ます

curl -H "Content-type:application / x-www-form-urlencoded" \ https://accounts.google.com/o/oauth2/revoke?token= {token}

トークンは、アクセストークンまたは更新トークンです。トークンがアクセストークンで、対応する更新トークンがある場合、更新トークンも取り消されます。

失効が正常に処理された場合、応答のステータスコードは200です。エラー条件の場合、ステータスコード400がエラーコードとともに返されます。


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

0

オフラインアクセスプロンプトの使用:同意は私にとってうまくいきました:

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

   auth2.grantOfflineAccess({prompt:'consent'}).then(signInCallback); 

0

私の解決策は少し変でした。インターネットで見つけたすべての解決策を試しましたが、何もしませんでした。驚いたことに、これはうまくいきました。credentials.jsonを削除し、更新して、アカウント内のアプリを再び元気にしてください。新しいcredentials.jsonファイルには更新トークンがあります。このファイルをどこかにバックアップします。次に、更新トークンエラーが再び表示されるまでアプリを使用し続けます。エラーメッセージのみが表示されているcrendetials.jsonファイルを削除します(これは私の場合に発生します)。次に、古い資格情報ファイルをフォルダーに貼り付けます。これで完了です!iveがこれを実行してから1週間が経過し、問題は発生しなくなりました。


0

認証のたびに新しいrefresh_tokenを取得するには、ダッシュボードで作成されるOAuth 2.0資格情報のタイプを「その他」にする必要があります。また、上記のように、authURLを生成するときにaccess_type = 'offline'オプションを使用する必要があります。

タイプが「Webアプリケーション」の資格情報を使用する場合、prompt / approval_prompt変数の組み合わせは機能しません。それでも、refresh_tokenは最初のリクエストでのみ取得されます。

弊社のサイトを使用することにより、あなたは弊社のクッキーポリシーおよびプライバシーポリシーを読み、理解したものとみなされます。
Licensed under cc by-sa 3.0 with attribution required.