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. 액세스 제거를 클릭 한 다음 확인을 클릭하여 확인하십시오.
  4. 다음 OAuth2 요청 refresh_token은 'access_type = offline'쿼리 매개 변수를 포함하는 경우를 반환합니다 .

또는 쿼리 매개 변수 prompt=consent&access_type=offline를 OAuth 리디렉션에 추가 할 수 있습니다 ( 웹 서버 응용 프로그램 용 Google OAuth 2.0 페이지 참조).

그러면 사용자에게 응용 프로그램을 다시 승인하라는 메시지가 표시되고 항상을 반환합니다 refresh_token.


20
이것은 나를 위해 작동하지 않았지만 매개 변수 "access_type = offline"을 추가하면 트릭을 수행하는 것처럼 보입니다. developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse

87
access_type=offline원하는 경우 모든 경우에 필요 합니다 refresh_token.
DanH

5
그러나이 경우 만료 후 토큰을 어떻게 새로 고치나요?
vivek_jonam

5
@vivek_jonam 새로 고침 토큰과 만료 날짜를 저장하십시오. 만료되면 새로 고침 토큰을 사용하여 새 토큰을 요청합니다. 여기 참조 : developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis 2013 년

4
나는 그것을 가지고 일했다 $client->setAccessType('offline'). 은 function setApprovalPrompt()이미 전달 된 force기본적으로.
moey

57

당신이 모두를 추가해야 토큰 새로 고침을 얻기 위하여 approval_prompt=force그리고 access_type="offline" 당신이 같이 표시됩니다 Google에서 제공하는 자바 클라이언트를 사용하는 경우 :

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', 범위 : SCOPES, Approval_prompt : 'force'});
Joris Mans

2
Google이 문서 에서이 문제를 해결하지 않았거나 적어도 7 시간 동안 쳐다 본 PHP 또는 oath2 문서 에서이 문제를 해결하지 않은 것은 터무니 없습니다. 전 세계에서 왜 문서에 굵은 글씨로 표시되지
않습니까?

감사합니다! 여기 문서 ( github.com/googlesamples/apps-script-oauth2 )는이 매개 변수에 대해 매우 잘못된 내용입니다. Approval_prompt = force를 추가하면 마침내 새로 고침 토큰이 생겼습니다.
Alex Zhevzhik

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 계정에 액세스하려고합니다. 첫 번째 토큰 만 새로 고침 토큰을 받고 다른 모든 토큰은 매번 다시 연결하지 않아도됩니다. 한 시간 내에 만료되는 access_token 대신 후속 인증을 위해 SAME 새로 고침 토큰을 얻는 방법이 없습니까?
SsjCosty

1
API가 새로 고침 토큰을 정확히 한 번 생성하는 것 같습니다 . 토큰의 모든 "공유"는 코드에서 발생해야합니다. 그러나 실수로 사용자에게 새로운 액세스 권한을 부여하지 않도록주의해야합니다. 이를 수행하는 간단한 방법은 애플리케이션 이 자체 저장소 (SQLese의 별도 'table')에서 새로 고침 토큰 및 연관된 계정을 추적하도록하는 것입니다 . 그런 다음 새 액세스 토큰 을 얻으려면 이 공통 토큰을 확인하여 사용하십시오. 특정 방법을 구현하면 코드에서 실제로 토큰을 얻은 사람을 알 필요가 없습니다.
jeteon

1
방금 얻은 새 액세스 토큰과 연결해야하는 새로 고침 토큰을 식별하는 방법을 모르겠습니다. 로그인을하는 사용자는 서로 다르며, 공통적으로 유일하게 사용하는 것은 동일한 Google 계정 (이메일)을 사용하여 API에 연결하는 것입니다. 그러나 Google은 계정이나 이메일의 ID를 보내지 않고 단지 토큰 만 보냅니다. 그래서 두 명의 다른 CMS 사용자를 연결하는 방법을 모르겠습니다 ...
SsjCosty

여기에 내 문제를 완전히 설명했습니다. stackoverflow.com/questions/30217524/…
SsjCosty

YouTube oAuth2 refresh_token은 강제로 사용될 때만 표시됩니다.
Dmitry Polushkin

7

Rich Sutton의 답변은 마침내 그 코드를 access_token과 교환하는 백엔드 요청이 아닌 프론트 엔드 클라이언트의 인증 코드 요청 access_type=offline에 추가되었다는 것을 알게 된 후에 저에게 효과적이었습니다. 토큰 새로 고침에 대한 자세한 내용을 보려면 그의 답변과 Google의이 링크에 의견을 추가했습니다 .

PS Satellizer를 사용하는 경우 AngularJS의 $ authProvider.google에 해당 옵션을 추가하는 방법이 있습니다 .


아주 작은 세부 사항이지만 중요한 것입니다. 나를 구했다! 감사합니다 :)
Dexter

@ZackMorris So .. 액세스 토큰을 사용하여 백엔드에서 refresh_token을 얻을 수 없다는 것을 의미합니까?
Nevermore

@Nevermore access_token 자체에서 refresh_token을 얻을 수 없습니다. 서버가 새로 고침을 처리하도록하려면 처음으로 refresh_token을 데이터베이스에 저장해야합니다. 또한 프런트 엔드에서 클라이언트 OAuth 흐름을 수행하는 경우 서버를 새로 고치려면 사용자가 refresh_token을 백엔드로 보내야합니다.
잭 모리스

4

를 얻으려면 OAuth 요청 URL refresh_token에 포함해야합니다 access_type=offline. 사용자를 인증 처음으로 당신은 비 무기 호를 다시 얻을 것이다 때 refresh_token잘 한 access_token만료.

사용자가 계정을 다시 인증 할 수있는 상황이있는 경우 (위의 @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"
)

범위에는 공백으로 구분 된 두 개의 항목이 있습니다. 하나는 Google Analytics에 대한 읽기 전용 액세스이고 다른 하나는 profileOpenID Connect 표준입니다.

결과적으로 Google id_tokenget_token응답 에 추가 속성을 제공합니다 . id_token에서 정보를 얻으려면 Google 문서 에서이 페이지확인하십시오 . 이를 위해이를 검증하고 "디코딩"하는 Google 제공 라이브러리가 몇 가지 있습니다 (Ruby google-id-token gem 사용 ). 구문 분석하면 sub매개 변수는 사실상 고유 한 Google 계정 ID입니다.

주목할 점 은 범위 를 변경 하면 원래 범위로 이미 인증 된 사용자에 대해 새로 고침 토큰을 다시 받게됩니다. 예를 들어 이미 많은 사용자가 있고 Google에서 앱을 모두 인증 해제하지 않으려는 경우에 유용합니다.

아, 그리고 하나의 마지막 메모 : 필요 prompt=select_account 하지는 않지만 사용자가 둘 이상의 Google 계정으로 인증하려는 경우 (예 : 로그인 / 인증에 사용하지 않는 경우) 유용합니다 .


개인 정보를 저장하지 않고 사용자를 식별하는 것이 핵심이라고 생각합니다. 지적 해 주셔서 감사합니다 .Google 문서에서 이에 대한 참조를 보지 못했습니다.
Danielo515

3

1. 'refresh_token'을 얻는 방법?

해결책 : authURL을 생성 할 때 access_type = 'offline'옵션을 사용해야합니다. 출처 : 웹 서버 응용 프로그램에 OAuth 2.0 사용

2. 그러나 'access_type = offline'이 있어도 'refresh_token'을 얻지 못하고 있습니까?

솔루션 : 첫 번째 요청에서만이를 얻을 수 있으므로, 어딘가에 저장하고 이전 만료 후에 새 access_token을 가져올 때 코드에서이 값을 겹쳐 쓰는 조항이있는 경우이 값을 겹쳐 쓰지 않아야합니다.

Google 인증 문서에서 : (이 값 = 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

이제 Google은 내 요청 (access_type, prompt)에서 해당 매개 변수를 거부했습니다 ... :( 그리고 "액세스 취소"버튼이 전혀 없습니다. refresh_token lol을 다시 가져 오기 때문에 좌절합니다.

업데이트 : 여기에서 답변을 찾았습니다 : D https://developers.google.com/identity/protocols/OAuth2WebServer 요청으로 새로 고침 토큰을 다시 얻을 수 있습니다.

curl -H "콘텐츠 유형 : 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 파일을 삭제 한 다음 폴더에 이전 자격 증명 파일을 붙여 넣으십시오! 이 작업을 수행 한 지 1 주일이 지났으며 더 이상 문제가 없었습니다.


0

인증 할 때마다 새 refresh_token을 얻으려면 대시 보드에서 생성 된 OAuth 2.0 자격 증명 유형이 "기타"여야합니다. 또한 위에서 언급 한 것처럼 authURL을 생성 할 때 access_type = 'offline'옵션을 사용해야합니다.

"웹 응용 프로그램"유형의 자격 증명을 사용하는 경우 prompt / approval_prompt 변수 조합이 작동하지 않습니다. 첫 번째 요청에서만 refresh_token을 얻을 수 있습니다.

당사 사이트를 사용함과 동시에 당사의 쿠키 정책개인정보 보호정책을 읽고 이해하였음을 인정하는 것으로 간주합니다.
Licensed under cc by-sa 3.0 with attribution required.