Không nhận được mã thông báo làm mới OAuth của Google


270

Tôi muốn nhận mã thông báo truy cập từ Google. API Google nói rằng để có được mã thông báo truy cập, hãy gửi mã và các tham số khác đến trang tạo mã thông báo và phản hồi sẽ là Đối tượng JSON như:

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

Tuy nhiên, tôi không nhận được mã thông báo làm mới. Câu trả lời trong trường hợp của tôi là:

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

Tôi đã có một vấn đề tương tự. Kiểm tra câu trả lời của tôi ở đây
Arithran

Câu trả lời:


675

Điều refresh_tokennày chỉ được cung cấp trên ủy quyền đầu tiên từ người dùng. Các ủy quyền tiếp theo, chẳng hạn như loại bạn thực hiện trong khi thử nghiệm tích hợp OAuth2, sẽ không trả lại refresh_tokenlần nữa. :)

  1. Truy cập trang hiển thị Ứng dụng có quyền truy cập vào tài khoản của bạn: https://myaccount.google.com/u/0/permissions .
  2. Trong menu ứng dụng của bên thứ ba, chọn ứng dụng của bạn.
  3. Nhấp vào Xóa quyền truy cập và sau đó nhấp vào Ok để xác nhận
  4. Yêu cầu OAuth2 tiếp theo bạn thực hiện sẽ trả về một refresh_token(với điều kiện nó cũng bao gồm tham số truy vấn 'access_type = offline'.

Ngoài ra, bạn có thể thêm các tham số truy vấn prompt=consent&access_type=offlinevào chuyển hướng OAuth (xem OAuth 2.0 của Google cho trang Ứng dụng Máy chủ Web ).

Điều này sẽ nhắc người dùng ủy quyền lại ứng dụng và sẽ luôn trả về a refresh_token.


20
Điều này không hiệu quả với tôi, nhưng việc thêm thông số "access_type = offline" dường như thực hiện thủ thuật: developers.google.com/accounts/docs/OAuth2WebServer#offline
Jesse

87
Bạn cần access_type=offlinetrong mọi trường hợp khi bạn muốn refresh_token.
DanH

5
Nhưng làm thế nào để tôi làm mới mã thông báo sau khi hết hạn trong trường hợp này?
vivek_jonam

5
@vivek_jonam Lưu trữ mã thông báo làm mới và ngày hết hạn. Khi nó hết hạn, bạn yêu cầu mã thông báo mới bằng cách sử dụng mã thông báo làm mới. Xem tại đây: developers.google.com/accounts/docs/OAuth2WebServer#refresh
gelviis

4
Tôi đã làm cho nó làm việc với $client->setAccessType('offline'). Cái function setApprovalPrompt()đã được truyền vào force, theo mặc định.
moey

57

Để có được mã thông báo làm mới, bạn phải thêm cả hai approval_prompt=forceaccess_type="offline" Nếu bạn đang sử dụng ứng dụng khách java do Google cung cấp, nó sẽ giống như sau:

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

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

Trong nút: var authUrl = oauth2Client.generateAuthUrl ({access_type: 'offline', scope: SCOPES, Phê duyệt_prompt: 'force'});
Joris Mans

2
Thật kỳ quặc khi google đã không giải quyết vấn đề này trong tài liệu của họ hoặc ít nhất là không có trong tài liệu php hoặc oath2 mà tôi đã nhìn chằm chằm trong 7 giờ. Tại sao trên thế giới này không phải là văn bản in đậm lớn trong tài liệu của họ
Colin Rickels

Cảm ơn bạn! Tài liệu ở đây ( github.com/googlesamples/apps-script-oauth2 ) rất sai lệch về tham số này. Khi tôi thêm Phê duyệt_prompt = lực lượng, cuối cùng tôi đã nhận được mã thông báo làm mới.
Alex Zhevzhik

28

Tôi đã tìm kiếm một đêm dài và điều này đang thực hiện mánh khóe:

Đã sửa đổi user-example.php từ admin-sdk

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

sau đó bạn nhận được mã tại url chuyển hướng và xác thực bằng mã và nhận mã thông báo làm mới

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

Bạn nên lưu trữ nó ngay bây giờ;)

Khi accesskey của bạn hết thời gian chỉ cần làm

$client->refreshToken($theRefreshTokenYouHadStored);

Hoàn hảo @Norbert, Đây chính xác là những gì tôi cần.
Emmanuel

Cảm ơn! Câu trả lời chính xác cho câu hỏi của tôi @Norbert
varun teja-MVT

16

Điều này đã gây cho tôi một số nhầm lẫn vì vậy tôi nghĩ rằng tôi muốn chia sẻ những gì tôi đã học được một cách khó khăn:

Khi bạn yêu cầu quyền truy cập bằng cách sử dụng access_type=offlineapproval_prompt=forcetham số, bạn sẽ nhận được cả mã thông báo truy cập và mã thông báo làm mới . Mã thông báo truy cập sẽ hết hạn ngay sau khi bạn nhận được và bạn sẽ cần làm mới nó.

Bạn đã thực hiện đúng yêu cầu để nhận mã thông báo truy cập mới và nhận được phản hồi có mã thông báo truy cập mới của bạn . Tôi cũng bối rối bởi thực tế là tôi đã không nhận được mã thông báo mới . Tuy nhiên, đây là cách nó có nghĩa là vì bạn có thể sử dụng cùng một mã thông báo làm mới nhiều lần.

Tôi nghĩ rằng một số câu trả lời khác cho rằng bạn muốn nhận cho mình một mã thông báo mới vì một số lý do và cho rằng bạn ủy quyền lại cho người dùng nhưng thực tế, bạn không cần phải làm mới mã thông báo mà bạn có sẽ hoạt động cho đến khi bị thu hồi bởi người dùng.


1
Tôi có một CMS nơi người dùng khác nhau sử dụng các tài khoản google khác nhau để kết nối với api phân tích. Tuy nhiên, đôi khi một số người dùng có thể kết nối bằng cùng một tài khoản google của công ty, nhưng mỗi người muốn truy cập vào một tài khoản Analytics khác nhau. Chỉ người đầu tiên nhận được mã thông báo làm mới, trong khi tất cả những người khác không nhận được và do đó phải kết nối lại mỗi giờ. Không có cách nào để có được mã thông báo làm mới SAME cho các xác thực tiếp theo thay vì chỉ access_token sẽ hết hạn trong vòng một giờ?
SsjCosty

1
API dường như tạo ra mã thông báo làm mới chính xác một lần. Mọi "chia sẻ" mã thông báo sẽ phải xảy ra trong mã của bạn. Bạn sẽ phải cẩn thận để không vô tình cung cấp đặc quyền truy cập mới cho người dùng. Một cách đơn giản để làm điều này là để ứng dụng theo dõi các mã thông báo làm mới và các tài khoản được liên kết trong bộ lưu trữ của riêng chúng ('bảng' riêng biệt trong SQLese). Sau đó, khi bạn muốn nhận mã thông báo truy cập mới, bạn hãy kiểm tra và sử dụng mã thông báo có thể phổ biến này từ đó. Đã triển khai một cách nhất định, mã của bạn không cần biết ai thực sự nhận được mã thông báo.
jeteon

1
Tôi không biết làm thế nào tôi có thể xác định mã thông báo làm mới nào tôi nên liên kết với mã thông báo truy cập mới mà tôi vừa nhận được. Có nhiều người dùng khác nhau thực hiện đăng nhập và điểm chung duy nhất họ có là họ sử dụng cùng một tài khoản Google (e-mail) để kết nối với API. Nhưng Google không gửi lại ID của tài khoản hoặc e-mail, nó chỉ gửi lại mã thông báo. Vì vậy, tôi không biết làm cách nào để liên kết 2 người dùng CMS khác nhau ...
SsjCosty

Tôi đã giải thích đầy đủ vấn đề của mình ở đây: stackoverflow.com/questions/30217524/
Khăn

Youtube oAuth2 refresh_token chỉ hiển thị khi sử dụng vũ lực.
Dmitry Polushkin

7

Câu trả lời của Rich Sutton cuối cùng đã có hiệu quả đối với tôi, sau khi tôi nhận ra rằng việc thêm access_type=offlineđược thực hiện theo yêu cầu của khách hàng ở phía trước đối với mã ủy quyền, chứ không phải yêu cầu phía sau trao đổi mã đó cho access_token. Tôi đã thêm một nhận xét cho câu trả lời của anh ấy và liên kết này tại Google để biết thêm thông tin về việc làm mới mã thông báo.

PS Nếu bạn đang sử dụng Satellizer, đây là cách thêm tùy chọn đó vào $ authProvider.google trong AngularJS .


Chi tiết rất nhỏ nhưng quan trọng. Cứu tôi ! Cảm ơn :)
Dexter

@ZackMorris Vậy .. bạn có muốn nói rằng tôi không thể nhận refresh_token từ phụ trợ bằng cách sử dụng mã thông báo truy cập không?
Nevermore

@Nevermore Bạn không thể lấy refresh_token từ chính access_token. Nếu bạn muốn máy chủ của mình xử lý làm mới, thì bạn sẽ cần lưu trữ refresh_token trong cơ sở dữ liệu của bạn lần đầu tiên. Ngoài ra, nếu bạn đang thực hiện luồng OAuth của máy khách ở mặt trước, thì người dùng sẽ phải gửi refresh_token của họ tới mặt sau nếu họ muốn máy chủ làm mới cho họ.
Zack Morris

4

Để có được refresh_tokenbạn cần đưa access_type=offlinevào URL yêu cầu OAuth. Khi người dùng xác thực lần đầu tiên, bạn sẽ nhận lại được một số không refresh_tokencũng như access_tokenhết hạn.

Nếu bạn gặp tình huống người dùng có thể xác thực lại tài khoản mà bạn đã có mã xác thực (như @SsjCosty đề cập ở trên), bạn cần lấy lại thông tin từ Google về mã thông báo mà tài khoản đó sử dụng. Để làm điều đó, thêm profilevào phạm vi của bạn. Sử dụng đá quý OAuth2 Ruby, yêu cầu cuối cùng của bạn có thể trông giống như thế này:

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

Lưu ý phạm vi có hai mục được phân tách bằng dấu cách, một mục để truy cập chỉ đọc vào Google Analytics và phần còn lại chỉ profilelà tiêu chuẩn OpenID Connect.

Điều này sẽ dẫn đến việc Google cung cấp một thuộc tính bổ sung được gọi id_tokentrong get_tokenphản hồi. Để lấy thông tin ra khỏi id_token, hãy xem trang này trong tài liệu Google. Có một số thư viện do Google cung cấp sẽ xác thực và Giải mã mã này cho bạn (Tôi đã sử dụng đá quý Ruby -id-token của Ruby ). Khi bạn đã phân tích cú pháp, subtham số này thực sự là ID tài khoản Google duy nhất.

Đáng lưu ý, nếu bạn thay đổi phạm vi, bạn sẽ nhận lại mã thông báo làm mới cho người dùng đã được xác thực với phạm vi ban đầu. Điều này hữu ích nếu, giả sử, bạn đã có một nhóm người dùng và không muốn làm cho tất cả họ không tự động xác nhận ứng dụng trong Google.

Ồ, và một lưu ý cuối cùng: bạn không cần prompt=select_account , nhưng thật hữu ích nếu bạn gặp tình huống người dùng của bạn có thể muốn xác thực bằng nhiều tài khoản Google (nghĩa là bạn không sử dụng tài khoản này để đăng nhập / xác thực) .


Tôi nghĩ rằng phần xác định người dùng mà không lưu trữ bất kỳ thông tin cá nhân nào là chìa khóa. Cảm ơn bạn đã chỉ ra, tôi không thấy bất kỳ tài liệu tham khảo nào trên tài liệu google về điều đó.
Danielo515

3

1. Làm thế nào để có được 'refresh_token'?

Giải pháp: nên sử dụng tùy chọn access_type = 'offline' khi tạo authURL. nguồn: Sử dụng OAuth 2.0 cho các ứng dụng máy chủ web

2. Nhưng ngay cả với 'access_type = offline', tôi vẫn không nhận được 'refresh_token'?

Giải pháp: Xin lưu ý rằng bạn sẽ chỉ nhận được yêu cầu đầu tiên, vì vậy nếu bạn đang lưu trữ nó ở đâu đó và có một điều khoản ghi đè mã này trong mã của bạn khi nhận access_token mới sau khi hết hạn trước đó, thì hãy đảm bảo không ghi đè giá trị này.

Từ Google Auth Doc: (giá trị này = access_type)

Giá trị này hướng dẫn máy chủ ủy quyền của Google trả lại mã thông báo làm mới và mã thông báo truy cập vào lần đầu tiên ứng dụng của bạn trao đổi mã ủy quyền cho mã thông báo.

Nếu bạn cần 'refresh_token' một lần nữa, thì bạn cần xóa quyền truy cập cho ứng dụng của mình bằng cách làm theo các bước được viết trong câu trả lời của Rich Sutton .


2

Đặt cài đặt này sẽ khiến mã thông báo làm mới được gửi mỗi lần:

$client->setApprovalPrompt('force');

một ví dụ được đưa ra dưới đây (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

Đối với tôi, tôi đã cố gắng CalendarSampleServletcung cấp bởi Google. Sau 1 giờ, access_key hết thời gian và có một chuyển hướng đến một trang 401. Tôi đã thử tất cả các tùy chọn trên nhưng chúng không hoạt động. Cuối cùng, khi kiểm tra mã nguồn cho 'AbstractAuthorizationCodeServlet' , tôi có thể thấy rằng chuyển hướng sẽ bị vô hiệu hóa nếu có thông tin xác thực, nhưng lý tưởng là nó nên được kiểm tra refresh token!=null. Tôi đã thêm mã dưới đây CalendarSampleServletvà nó hoạt động sau đó. Cứu trợ tuyệt vời sau nhiều giờ thất vọng. Cảm ơn Chúa.

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

0

bây giờ google đã từ chối các tham số đó trong yêu cầu của tôi (access_type, prompt) ... :( và hoàn toàn không có nút "Thu hồi quyền truy cập". Tôi bực bội vì nhận lại lol của mình

CẬP NHẬT: Tôi đã tìm thấy câu trả lời ở đây: D bạn có thể lấy lại mã thông báo làm mới bằng một yêu cầu https://developers.google.com/identity/prot Protocol / OuthuthWebServer

curl -H "Kiểu nội dung: application / x-www-form-urlencoding" \ https://accounts.google.com/o/oauth2/revoke?token= {token}

Mã thông báo có thể là mã thông báo truy cập hoặc mã thông báo làm mới. Nếu mã thông báo là mã thông báo truy cập và nó có mã thông báo làm mới tương ứng, mã thông báo làm mới cũng sẽ bị thu hồi.

Nếu việc thu hồi được xử lý thành công, thì mã trạng thái của phản hồi là 200. Đối với các điều kiện lỗi, mã trạng thái 400 được trả về cùng với mã lỗi.


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

Sử dụng truy cập ngoại tuyếnlời nhắc: sự đồng ý làm việc tốt với tôi:

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

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

0

Giải pháp của tôi hơi kỳ lạ..tôi đã thử mọi giải pháp tôi tìm thấy trên internet và không có gì. Chính xác thì điều này đã hoạt động: xóa thông tin xác thực.json, làm mới, làm lại ứng dụng của bạn trong tài khoản của bạn. Tệp certs.json mới sẽ có mã thông báo làm mới. Sao lưu tập tin này ở đâu đó. Sau đó tiếp tục sử dụng ứng dụng của bạn cho đến khi lỗi mã thông báo làm mới xuất hiện trở lại. Xóa tệp crendetials.json hiện chỉ có thông báo lỗi (điều này đã bị xóa trong trường hợp của tôi), sau đó dán tệp thông tin xác thực cũ vào thư mục, đã xong! Đã 1 tuần kể từ khi tôi làm điều này và không có vấn đề nữa.


0

Để có được refresh_token mới mỗi lần xác thực, loại thông tin xác thực OAuth 2.0 được tạo trong bảng điều khiển phải là "Khác". Ngoài ra, như đã đề cập ở trên, nên sử dụng tùy chọn access_type = 'offline' khi tạo authURL.

Khi sử dụng thông tin đăng nhập với loại "Ứng dụng web", không có sự kết hợp của các biến prompt / Phê duyệt_prompt sẽ hoạt động - bạn vẫn sẽ chỉ nhận được refresh_token trong yêu cầu đầu tiên.

Khi sử dụng trang web của chúng tôi, bạn xác nhận rằng bạn đã đọc và hiểu Chính sách cookieChính sách bảo mật của chúng tôi.
Licensed under cc by-sa 3.0 with attribution required.