В этом документе объясняется, как веб-серверные приложения используют клиентские библиотеки API Google или конечные точки Google OAuth 2.0 для реализации авторизации OAuth 2.0 для доступа к API Google.
OAuth 2.0 позволяет пользователям делиться определенными данными с приложением, сохраняя при этом свои имена пользователей, пароли и другую конфиденциальную информацию. Например, приложение может использовать OAuth 2.0, чтобы получить разрешение от пользователей на хранение файлов на их Google Диске.
Этот процесс OAuth 2.0 предназначен специально для авторизации пользователей. Он предназначен для приложений, которые могут хранить конфиденциальную информацию и поддерживать состояние. Правильно авторизованное приложение веб-сервера может обращаться к API, пока пользователь взаимодействует с приложением или после того, как он его покинул.
Веб-серверные приложения часто используют сервисные аккаунты для авторизации запросов к API, особенно при вызове Cloud API для доступа к данным на уровне проекта, а не пользователя. Приложения веб-сервера могут использовать сервисные аккаунты в сочетании с авторизацией пользователей.
Клиентские библиотеки
В приведенных здесь примерах для конкретных языков программирования показана реализация OAuth 2.0 с помощью клиентских библиотек Google API. Чтобы выполнить примеры кода, сначала необходимо установить клиентскую библиотеку для соответствующего языка.
Если вы используете клиентскую библиотеку API Google для обработки процесса OAuth 2.0 в приложении, она выполняет многие действия, которые в противном случае пришлось бы выполнять приложению. Например, он определяет, когда приложение может использовать или обновлять сохраненные токены доступа, а также когда приложению необходимо повторно получить согласие. Клиентская библиотека также генерирует правильные URL перенаправления и помогает реализовать обработчики перенаправления, которые обменивают коды авторизации на токены доступа.
Клиентские библиотеки API Google для серверных приложений доступны для следующих языков:
Требования
Как включить API для проекта
Любое приложение, вызывающее API Google, должно включить эти API в API Console.
Чтобы включить API для вашего проекта:
- Откройте библиотеку API в Google API Console.
- Выберите существующий проект или создайте новый.
- В библиотеке API перечислены все доступные API, сгруппированные по семейству продуктов и популярности. Если нужного API нет в списке, найдите его с помощью поиска или нажмите Показать все в семействе продуктов, к которому он относится.
- Выберите нужный API и нажмите кнопку Включить.
- Если появится запрос, включите оплату.
- При необходимости прочтите и примите Условия использования API.
Как создать учетные данные для авторизации
Любое приложение, которое использует OAuth 2.0 для доступа к API Google, должно иметь учетные данные для авторизации, которые идентифицируют приложение на сервере OAuth 2.0 Google. Ниже описано, как создать учетные данные для проекта. Затем ваши приложения смогут использовать эти учетные данные для доступа к API, которые вы включили для этого проекта.
- Перейдите на страницу клиентов.
- Нажмите Создать клиента.
- Выберите тип приложения Веб-приложение.
- Заполните форму и нажмите Создать. Приложения, в которых используются языки и фреймворки, такие как PHP, Java, Python, Ruby и .NET, должны указывать авторизованные URI переадресации. URI перенаправления – это конечные точки, на которые сервер OAuth 2.0 может отправлять ответы. Эти конечные точки должны соответствовать правилам проверки Google.
Для тестирования можно указать URI, которые ссылаются на локальный компьютер, например
http://localhost:8080. Во всех примерах в этом документе в качестве URI переадресации используетсяhttp://localhost:8080.Рекомендуем разработать конечные точки авторизации приложения так, чтобы оно не раскрывало коды авторизации другим ресурсам на странице.
После создания учетных данных скачайте файл client_secret.json из API Console. Сохраните файл в папке, доступ к которой есть только у вашего приложения.
Как определить области доступа
Области действия позволяют приложению запрашивать доступ только к необходимым ресурсам, а пользователям – контролировать объем доступа, предоставляемого приложению. Таким образом, может существовать обратная зависимость между количеством запрашиваемых областей и вероятностью получения согласия пользователя.
Прежде чем начинать реализацию авторизации OAuth 2.0, рекомендуем определить области, к которым приложению потребуется доступ.
Мы также рекомендуем, чтобы ваше приложение запрашивало доступ к областям авторизации с помощью поэтапной авторизации, в рамках которой приложение запрашивает доступ к данным пользователя в контексте. Это поможет пользователям понять, почему приложению нужен доступ к тем или иным функциям.
В документе Области действия OAuth 2.0 API приведен полный список областей действия, которые можно использовать для доступа к API Google.
Требования для отдельных языков
Чтобы запустить любой из примеров кода в этом документе, вам понадобится аккаунт Google, доступ к интернету и веб-браузер. Если вы используете одну из клиентских библиотек API, ознакомьтесь с требованиями для конкретного языка в разделах ниже.
PHP
Чтобы запустить примеры кода PHP из этого документа, вам потребуется:
- PHP 8.0 или более поздней версии с интерфейсом командной строки (CLI) и установленным расширением JSON.
- Инструмент управления зависимостями Composer.
-
Клиентская библиотека API Google для PHP:
composer require google/apiclient:^2.15.0
Подробнее о клиентской библиотеке API Google для PHP…
Python
Чтобы запустить примеры кода на Python, приведенные в этом документе, вам понадобится:
- Python 3.7 или более поздней версии
- Инструмент управления пакетами pip.
- В версии 2.0 клиентской библиотеки API Google для Python:
pip install --upgrade google-api-python-client
google-auth,google-auth-oauthlibиgoogle-auth-httplib2для авторизации пользователей.pip install --upgrade google-auth google-auth-oauthlib google-auth-httplib2
- Веб-фреймворк Flask для Python.
pip install --upgrade flask
- HTTP-библиотека
requests.pip install --upgrade requests
Если вы не можете обновить Python и связанное с ним руководство по переносу данных, ознакомьтесь с примечаниями к выпуску клиентской библиотеки API Google для Python.
Ruby
Чтобы запустить примеры кода Ruby, приведенные в этом документе, вам потребуется:
- Ruby 2.6 или более поздней версии;
-
Библиотека Google Auth для Ruby:
gem install googleauth
-
Клиентские библиотеки для API Google Диска и Календаря:
gem install google-apis-drive_v3 google-apis-calendar_v3
-
Веб-фреймворк Sinatra Ruby.
gem install sinatra
Node.js
Чтобы запустить примеры кода Node.js в этом документе, вам понадобится:
- Поддерживаемая версия LTS, активная версия LTS или текущая версия Node.js.
-
Клиент Node.js для API Google:
npm install googleapis crypto express express-session
HTTP/REST
Чтобы напрямую вызывать конечные точки OAuth 2.0, не нужно устанавливать никаких библиотек.
Получение токенов доступа OAuth 2.0
Ниже описано, как ваше приложение взаимодействует с сервером OAuth 2.0 Google, чтобы получить согласие пользователя на выполнение запроса к API от его имени. Приложение должно получить такое согласие, прежде чем выполнять запрос к API Google, требующий авторизации пользователя.
Ниже приведен краткий список этих шагов.
- Ваше приложение определяет, какие разрешения ему нужны.
- Приложение перенаправляет пользователя на страницу Google, где перечислены запрашиваемые разрешения.
- Пользователь решает, предоставлять ли вашему приложению разрешения.
- Приложение узнает, какое решение принял пользователь.
- Если пользователь предоставил запрошенные разрешения, ваше приложение получает токены, необходимые для выполнения запросов к API от имени пользователя.
Шаг 1. Задайте параметры авторизации
Первым делом создайте запрос на авторизацию. В этом запросе задаются параметры, которые идентифицируют ваше приложение и определяют разрешения, которые пользователю будет предложено предоставить вашему приложению.
- Если вы используете клиентскую библиотеку Google для аутентификации и авторизации OAuth 2.0, создайте и настройте объект, определяющий эти параметры.
- Если вы вызываете конечную точку Google OAuth 2.0 напрямую, вы создаете URL и задаете для него параметры.
На вкладках ниже перечислены поддерживаемые параметры авторизации для приложений веб-сервера. В примерах для конкретных языков программирования также показано, как использовать клиентскую библиотеку или библиотеку авторизации для настройки объекта, который задает эти параметры:
PHP
Следующий фрагмент кода создает объект Google\Client(), который определяет параметры в запросе авторизации.
Этот объект использует информацию из файла client_secret.json, чтобы идентифицировать ваше приложение. Подробнее о том, как создать учетные данные для авторизации… Объект также определяет области действия, для которых ваше приложение запрашивает разрешение на доступ, и URL конечной точки авторизации приложения, которая будет обрабатывать ответ от сервера OAuth 2.0 Google. Наконец, код задает необязательные параметры access_type и include_granted_scopes.
Например, этот код запрашивает доступ только для чтения и офлайн-доступ к метаданным Google Диска и мероприятиям Календаря пользователя:
use Google\Client; $client = new Client(); // Required, call the setAuthConfig function to load authorization credentials from // client_secret.json file. $client->setAuthConfig('client_secret.json'); // Required, to set the scope value, call the addScope function $client->addScope([Google\Service\Drive::DRIVE_METADATA_READONLY, Google\Service\Calendar::CALENDAR_READONLY]); // Required, call the setRedirectUri function to specify a valid redirect URI for the // provided client_id $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'); // Recommended, offline access will give you both an access and refresh token so that // your app can refresh the access token without user interaction. $client->setAccessType('offline'); // Recommended, call the setState function. Using a state value can increase your assurance that // an incoming connection is the result of an authentication request. $client->setState($sample_passthrough_value); // Optional, if your application knows which user is trying to authenticate, it can use this // parameter to provide a hint to the Google Authentication Server. $client->setLoginHint('hint@example.com'); // Optional, call the setPrompt function to set "consent" will prompt the user for consent $client->setPrompt('consent'); // Optional, call the setIncludeGrantedScopes function with true to enable incremental // authorization $client->setIncludeGrantedScopes(true);
Python
В приведенном ниже фрагменте кода для создания запроса авторизации используется модуль google-auth-oauthlib.flow.
Код создает объект Flow, который идентифицирует ваше приложение, используя информацию из файла client_secret.json, скачанного после создания учетных данных для авторизации. Этот объект также определяет области действия, для которых приложение запрашивает разрешение на доступ, и URL конечной точки аутентификации приложения, которая будет обрабатывать ответ от сервера OAuth 2.0 Google. Наконец, код задает необязательные параметры access_type и include_granted_scopes.
Например, этот код запрашивает доступ только для чтения в офлайн-режиме к метаданным Google Диска и мероприятиям Календаря пользователя:
import google.oauth2.credentials import google_auth_oauthlib.flow # Required, call the from_client_secrets_file method to retrieve the client ID from a # client_secret.json file. The client ID (from that file) and access scopes are required. (You can # also use the from_client_config method, which passes the client configuration as it originally # appeared in a client secrets file but doesn't access the file itself.) flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file('client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/calendar.readonly']) # Required, indicate where the API server will redirect the user after the user completes # the authorization flow. The redirect URI is required. The value must exactly # match one of the authorized redirect URIs for the OAuth 2.0 client, which you # configured in the API Console. If this value doesn't match an authorized URI, # you will get a 'redirect_uri_mismatch' error. flow.redirect_uri = 'https://www.example.com/oauth2callback' # Generate URL for request to Google's OAuth 2.0 server. # Use kwargs to set optional request parameters. authorization_url, state = flow.authorization_url( # Recommended, enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Optional, enable incremental authorization. Recommended as a best practice. include_granted_scopes='true', # Optional, if your application knows which user is trying to authenticate, it can use this # parameter to provide a hint to the Google Authentication Server. login_hint='hint@example.com', # Optional, set prompt to 'consent' will prompt the user for consent prompt='consent')
Ruby
Используйте файл client_secrets.json, который вы создали, чтобы настроить объект клиента в приложении. При настройке объекта клиента вы указываете области действия, необходимые приложению для доступа, а также URL конечной точки аутентификации приложения, которая будет обрабатывать ответ от сервера OAuth 2.0.
Например, этот код запрашивает доступ только для чтения и офлайн-доступ к метаданным Google Диска и мероприятиям Календаря пользователя:
require 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/drive_v3' require 'google/apis/calendar_v3' # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. client_id = Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value # Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar. scope = ['Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY', 'Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY'] # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. token_store = Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # Required, indicate where the API server will redirect the user after the user completes # the authorization flow. The redirect URI is required. The value must exactly # match one of the authorized redirect URIs for the OAuth 2.0 client, which you # configured in the API Console. If this value doesn't match an authorized URI, # you will get a 'redirect_uri_mismatch' error. callback_uri = '/oauth2callback' # To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI # from the client_secret.json file. To get these credentials for your application, visit # https://console.cloud.google.com/apis/credentials. authorizer = Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, callback_uri)
Ваше приложение использует объект клиента для выполнения операций OAuth 2.0, например для создания URL запросов авторизации и применения токенов доступа к HTTP-запросам.
Node.js
Следующий фрагмент кода создает объект google.auth.OAuth2, который определяет параметры в запросе авторизации.
Этот объект использует информацию из файла client_secret.json, чтобы идентифицировать ваше приложение. Чтобы запросить у пользователя разрешения на получение токена доступа, перенаправьте его на страницу согласия. Чтобы создать URL страницы согласия, выполните следующие действия:
const {google} = require('googleapis'); const crypto = require('crypto'); const express = require('express'); const session = require('express-session'); /** * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI * from the client_secret.json file. To get these credentials for your application, visit * https://console.cloud.google.com/apis/credentials. */ const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar. const scopes = [ 'https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/calendar.readonly' ]; // Generate a secure random state value. const state = crypto.randomBytes(32).toString('hex'); // Store state in the session req.session.state = state; // Generate a url that asks permissions for the Drive activity and Google Calendar scope const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true, // Include the state parameter to reduce the risk of CSRF attacks. state: state });
Важное примечание. refresh_token возвращается только при первой авторизации.
Подробнее…
HTTP/REST
Конечная точка OAuth 2.0 Google находится по адресу https://accounts.google.com/o/oauth2/v2/auth. Эта конечная точка доступна только по протоколу HTTPS. Обычные HTTP-подключения отклоняются.
Сервер авторизации Google поддерживает следующие параметры строки запроса для приложений веб-сервера:
| Параметры | |||||||
|---|---|---|---|---|---|---|---|
client_id |
Обязательно
Идентификатор клиента для вашего приложения. Это значение можно найти на странице клиентов в Cloud Console. |
||||||
redirect_uri |
Обязательно
Определяет, куда сервер API перенаправляет пользователя после завершения процесса авторизации. Значение должно точно совпадать с одним из разрешенных URI перенаправления для клиента OAuth 2.0, который вы настроили на странице клиентов в Cloud Console. Если это значение не совпадает с авторизованным URI перенаправления для указанного Обратите внимание, что схема ( |
||||||
response_type |
Обязательно
Определяет, возвращает ли конечная точка Google OAuth 2.0 код авторизации. Для приложений веб-сервера установите значение параметра |
||||||
scope |
Обязательно
Список областей действия, разделенных пробелами, которые определяют ресурсы, к которым ваше приложение может получить доступ от имени пользователя. Эти значения используются в окне запроса согласия, которое Google показывает пользователю. Области действия позволяют приложению запрашивать доступ только к тем ресурсам, которые ему нужны, а пользователям – контролировать объем доступа, который они предоставляют приложению. Таким образом, существует обратная зависимость между количеством запрашиваемых областей и вероятностью получения согласия пользователя. Рекомендуем по возможности запрашивать доступ к областям авторизации в контексте. Запрашивая доступ к данным пользователей в контексте с помощью поэтапной авторизации, вы помогаете пользователям понять, зачем вашему приложению нужен доступ, который оно запрашивает. |
||||||
access_type |
Рекомендуется
Указывает, может ли приложение обновлять токены доступа, когда пользователь не работает в браузере. Допустимые значения параметра: Установите значение |
||||||
state |
Рекомендуется
Любое строковое значение, которое ваше приложение использует для сохранения состояния между запросом авторизации и ответом сервера авторизации.
Сервер возвращает точное значение, которое вы отправляете в виде пары Этот параметр можно использовать для разных целей, например чтобы направлять пользователя к нужному ресурсу в приложении, отправлять одноразовые коды и предотвращать подделку межсайтовых запросов. Поскольку |
||||||
include_granted_scopes |
Необязательно
Позволяет приложениям использовать поэтапную авторизацию, чтобы запрашивать доступ к дополнительным областям в контексте. Если вы зададите для этого параметра значение |
||||||
enable_granular_consent |
Необязательно
Значение по умолчанию – Когда Google включит детализированные разрешения для приложения, этот параметр перестанет действовать. |
||||||
login_hint |
Необязательно
Если приложение знает, какой пользователь пытается выполнить аутентификацию, оно может использовать этот параметр, чтобы предоставить подсказку серверу аутентификации Google. Сервер использует подсказку, чтобы упростить процесс входа, заполнив поле адреса электронной почты в форме входа или выбрав соответствующий сеанс с несколькими входами. В качестве значения параметра укажите адрес электронной почты или идентификатор |
||||||
prompt |
Необязательно
Список запросов, разделенных пробелами, с учетом регистра, которые будут показаны пользователю. Если вы не укажете этот параметр, запрос будет показан только при первом обращении вашего проекта. Подробнее о том, как запросить повторное согласие… Возможные значения:
|
||||||
Шаг 2. Перенаправление на сервер OAuth 2.0 Google
Перенаправьте пользователя на сервер OAuth 2.0 Google, чтобы запустить процесс аутентификации и авторизации. Обычно это происходит, когда приложению впервые требуется доступ к пользовательским данным. В случае инкрементной авторизации этот шаг также выполняется, когда приложению впервые требуется доступ к дополнительным ресурсам, на которые у него нет разрешения.
PHP
- Сгенерируйте URL, чтобы запросить доступ у сервера OAuth 2.0 от Google:
$auth_url = $client->createAuthUrl(); - Перенаправьте пользователя на страницу
$auth_url:header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL));
Python
В этом примере показано, как перенаправить пользователя на URL авторизации с помощью веб-фреймворка Flask:
return flask.redirect(authorization_url)
Ruby
- Сгенерируйте URL, чтобы запросить доступ у сервера OAuth 2.0 от Google:
auth_uri = authorizer.get_authorization_url(request: request)
- Перенаправьте пользователя на страницу
auth_uri.
Node.js
-
Используйте сгенерированный URL
authorizationUrlиз шага 1generateAuthUrl, чтобы запросить доступ у сервера OAuth 2.0 Google. -
Перенаправьте пользователя на страницу
authorizationUrl.res.redirect(authorizationUrl);
HTTP/REST
Пример перенаправления на сервер авторизации Google
Ниже приведен пример URL с разрывами строк и пробелами для удобства чтения.
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly& access_type=offline& include_granted_scopes=true& response_type=code& state=state_parameter_passthrough_value& redirect_uri=https%3A//developers.google.com/oauthplayground& client_id=client_id
После создания URL запроса перенаправьте на него пользователя.
Сервер OAuth 2.0 от Google проверяет подлинность пользователя и получает его согласие на доступ вашего приложения к запрошенным областям действия. Ответ отправляется обратно в ваше приложение с помощью указанного вами URL переадресации.
Шаг 3. Google запрашивает согласие пользователя
На этом этапе пользователь решает, предоставлять ли приложению запрошенный доступ. На этом этапе Google показывает окно запроса согласия, в котором указаны название вашего приложения и сервисы API Google, к которым оно запрашивает доступ с учетными данными авторизации пользователя, а также краткое описание областей действия, которые будут предоставлены. Затем пользователь может предоставить доступ к одному или нескольким областям действия, запрошенным вашим приложением, или отклонить запрос.
На этом этапе приложению не нужно ничего делать. Оно ожидает ответа от сервера OAuth 2.0 Google, в котором будет указано, был ли предоставлен доступ. Этот ответ описан на следующем шаге.
Ошибки
При запросах к конечной точке авторизации OAuth 2.0 от Google могут показываться пользовательские сообщения об ошибках вместо ожидаемых процессов аутентификации и авторизации. Ниже приведены распространенные коды ошибок и способы их устранения.
admin_policy_enforced
Аккаунт Google не может авторизовать одну или несколько запрошенных областей действия из-за правил, установленных администратором Google Workspace. Подробнее о том, как администратор может ограничить доступ ко всем областям действия или только к конфиденциальным и с ограниченным доступом, пока доступ не будет явно предоставлен вашему идентификатору клиента OAuth, рассказывается в справочной статье Справочного центра для администраторов Google Workspace Как управлять доступом сторонних и внутренних приложений к данным Google Workspace.
disallowed_useragent
Конечная точка авторизации отображается во встроенном агенте пользователя, запрещенном правилами OAuth 2.0 Google.
Разработчики iOS и macOS могут столкнуться с этой ошибкой при открытии запросов на авторизацию в WKWebView.
Вместо этого разработчикам следует использовать библиотеки iOS, такие как вход с аккаунтом Google для iOS или AppAuth для iOS от OpenID Foundation.
Веб-разработчики могут столкнуться с этой ошибкой, когда приложение для iOS или macOS открывает общую веб-ссылку во встроенном агенте пользователя и пользователь переходит к конечной точке авторизации OAuth 2.0 Google с вашего сайта. Разработчикам следует разрешить открывать обычные ссылки в обработчике ссылок по умолчанию, который используется в операционной системе. К нему относятся обработчики универсальных ссылок и браузер по умолчанию. Также поддерживается библиотека SFSafariViewController.
org_internal
Идентификатор клиента OAuth в запросе относится к проекту, который ограничивает доступ к аккаунтам Google в определенной организации Google Cloud. Подробнее об этом параметре конфигурации см. в разделе Тип пользователя справочной статьи Настройка окна запроса доступа OAuth.
invalid_client
Неправильный секретный код клиента OAuth. Проверьте конфигурацию клиента OAuth, в том числе идентификатор клиента и секретный код, которые использовались для этого запроса.
deleted_client
Клиент OAuth, используемый для выполнения запроса, был удален. Удаление может выполняться вручную или автоматически, если клиенты не используются . Удаленных клиентов можно восстановить в течение 30 дней после удаления. Подробнее…
invalid_grant
При обновлении токена доступа или использовании инкрементной авторизации срок действия токена мог истечь или он был аннулирован. Повторно выполните аутентификацию пользователя и запросите согласие пользователя на получение новых токенов. Если вы продолжаете видеть эту ошибку, убедитесь, что ваше приложение настроено правильно и что вы используете правильные токены и параметры в своем запросе. В противном случае аккаунт пользователя может быть удален или отключен.
redirect_uri_mismatch
Параметр redirect_uri, переданный в запросе на авторизацию, не соответствует авторизованному URI переадресации для идентификатора клиента OAuth. Проверьте авторизованные URI перенаправления на странице клиентов в консоли Google Cloud.
Параметр redirect_uri может относиться к внеполосному потоку OAuth, который устарел и больше не поддерживается. Чтобы обновить интеграцию, следуйте инструкциям в руководстве по переносу.
invalid_request
В вашем запросе что-то было не так. Это может быть связано со следующими причинами:
- Неправильный формат запроса
- В запросе отсутствовали обязательные параметры.
- В запросе используется способ авторизации, который не поддерживается Google. Проверьте, что при интеграции OAuth используется рекомендуемый метод
Шаг 4. Обработайте ответ сервера OAuth 2.0
Сервер OAuth 2.0 отвечает на запрос доступа, отправленный приложением, используя URL, указанный в запросе.
Если пользователь одобрит запрос на доступ, ответ будет содержать код авторизации. Если пользователь не одобрит запрос, в ответе будет сообщение об ошибке. Код авторизации или сообщение об ошибке, возвращаемые на веб-сервер, появляются в строке запроса, как показано в следующих примерах:
Ответ с ошибкой:
https://oauth2.example.com/auth?error=access_denied
Ответ с кодом авторизации:
https://oauth2.example.com/auth?code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7
Пример ответа сервера OAuth 2.0
Вы можете протестировать этот процесс, нажав на следующий пример URL, который запрашивает доступ только для чтения к метаданным файлов на вашем Google Диске и доступ только для чтения к мероприятиям Google Календаря:
https://accounts.google.com/o/oauth2/v2/auth? scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly& access_type=offline& include_granted_scopes=true& response_type=code& state=state_parameter_passthrough_value& redirect_uri=https%3A//developers.google.com/oauthplayground& client_id=client_id
После завершения процесса OAuth 2.0 браузер перенаправит вас на страницу OAuth 2.0 Playground – инструмента для тестирования процессов OAuth. Вы увидите, что код авторизации автоматически добавлен в OAuth 2.0 Playground.
Шаг 5. Обменяйте код авторизации на токены обновления и доступа
Получив код авторизации, веб-сервер может обменять его на токен доступа.
PHP
Чтобы обменять код авторизации на токен доступа, используйте метод fetchAccessTokenWithAuthCode:
$access_token = $client->fetchAccessTokenWithAuthCode($_GET['code']);Python
На странице обратного вызова используйте библиотеку google-auth, чтобы проверить ответ сервера авторизации. Затем используйте метод flow.fetch_token, чтобы обменять код авторизации в этом ответе на токен доступа:
state = flask.session['state'] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( 'client_secret.json', scopes=['https://www.googleapis.com/auth/drive.metadata.readonly'], state=state) flow.redirect_uri = flask.url_for('oauth2callback', _external=True) authorization_response = flask.request.url flow.fetch_token(authorization_response=authorization_response) # Store the credentials in browser session storage, but for security: client_id, client_secret, # and token_uri are instead stored only on the backend server. credentials = flow.credentials flask.session['credentials'] = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'granted_scopes': credentials.granted_scopes}
Ruby
На странице обратного вызова используйте библиотеку googleauth, чтобы проверить ответ сервера авторизации. Используйте метод authorizer.handle_auth_callback_deferred, чтобы сохранить код авторизации и перенаправить пользователя на URL, с которого был отправлен запрос на авторизацию. Это позволяет отложить обмен кодом, временно сохранив результаты в сеансе пользователя.
target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url
Node.js
Чтобы обменять код авторизации на токен доступа, используйте метод getToken:
const url = require('url'); // Receive the callback from Google's OAuth 2.0 server. app.get('/oauth2callback', async (req, res) => { let q = url.parse(req.url, true).query; if (q.error) { // An error response e.g. error=access_denied console.log('Error:' + q.error); } else if (q.state !== req.session.state) { //check state value console.log('State mismatch. Possible CSRF attack'); res.end('State mismatch. Possible CSRF attack'); } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); });
HTTP/REST
Чтобы обменять код авторизации на токен доступа, вызовите конечную точку https://oauth2.googleapis.com/token и задайте следующие параметры:
| Поля | |
|---|---|
client_id |
Идентификатор клиента, полученный на странице клиентов в Cloud Console. |
client_secret |
Необязательно
Секретный код клиента, полученный на странице клиентов в Cloud Console. |
code |
Код авторизации, возвращенный в ответ на первоначальный запрос. |
grant_type |
Согласно спецификации OAuth 2.0, значение этого поля должно быть равно authorization_code. |
redirect_uri |
Один из URI переадресации, указанных для вашего проекта на странице клиентов в Cloud Console для client_id. |
Использовать DPoP необязательно, но рекомендуется для повышения безопасности. Безопасность DPoP зависит от того, ограничен ли закрытый ключ одним устройством. Мы рекомендуем хранить его так, чтобы его нельзя было скопировать с устройства, например с помощью TPM, Secure Enclave или других аппаратных хранилищ ключей. Чтобы использовать DPoP, ваше приложение должно генерировать новый уникальный JWT-токен подтверждения DPoP для каждого запроса к конечной точке токена и добавлять его в качестве заголовка HTTP-запроса.
| Заголовок | Обязательно | Описание |
|---|---|---|
DPoP |
Необязательно | Доказательство DPoP – это токен JWT, который подтверждает владение закрытым ключом. Это заголовок, а не параметр. Если он указан, возвращаемые токены привязываются к этому ключу. Для каждого запроса необходимо генерировать новое уникальное подтверждение, которое должно включать утверждения htm (метод HTTP) и htu (URI HTTP), соответствующие запросу. |
Ниже приведен пример запроса.
POST /token HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded DPoP: eyJ0eXAiOiJkcG9wK2p3dCIsImFsZyI6IkVTMjU2IiwiandrIjp7Imt0eSI6Ik\ VDIiwieCI6Imw4dEZyaHgtMzR0VjNoUklDUkRZOXpDa0RscEJoRjQyVVFVZldWQVdCR\ nMiLCJ5IjoiOVZFNGpmX09rX282NHpiVFRsY3VOSmFqSG10NnY5VERWclUwQ2R2R1JE\ QSIsImNydiI6IlAtMjU2In19.eyJqdGkiOiItQndDM0VTYzZhY2MybFRjIiwiaHRtIj\ oiUE9TVCIsImh0dSI6Imh0dHBzOi8vc2VydmVyLmV4YW1wbGUuY29tL3Rva2VuIiwia\ WF0IjoxNTYyMjYyNjE2fQ.2-GxA6T8lP4vfrg8v-FdWP0A0zdrj8igiMLvqRMUvwnQg\ 4PtFLbdLXiOSsX0x7NVY-FNyJK70nfbV37xRZT3Lg code=4/P7q7W91a-oMsCeLvIaQm6bTrgtp7& client_id=your_client_id& redirect_uri=https%3A//developers.google.com/oauthplayground& grant_type=authorization_code
Как создать подтверждение DPoP
Ниже описано, как создать доказательство DPoP с помощью OpenSSL из командной строки.
- Создайте пару ключей EC P-256:
openssl ecparam -name prime256v1 -genkey -noout -out dpop_private.pem openssl ec -in dpop_private.pem -pubout -out dpop_public.pem
- Создайте заголовок DPoP:
Заголовок должен содержать утверждения
typ,algиjwk(открытый ключ). Значенияxиy– это закодированные в Base64Url координаты вашего открытого ключа EC. Закодируйте этот JSON с помощью Base64Url:{ "typ":"dpop+jwt", "alg":"ES256", "jwk": { "kty":"EC", "x":"YOUR_PUBLIC_KEY_X", "y":"YOUR_PUBLIC_KEY_Y", "crv":"P-256" } } - Создайте полезную нагрузку DPoP:
Полезная нагрузка должна включать
jti(уникальный идентификатор запроса),htm(метод HTTP, напримерPOST),htu(URI HTTP, напримерhttps://oauth2.googleapis.com/token) иiat(время выпуска). Если вы получили одноразовый код от сервера в заголовкеDPoP-Nonceв ответе на предыдущий запрос, вы должны включить это значение одноразового кода в утверждениеnonce. Утверждение nonce необязательно для обмена кодами авторизации и используется, только если ранее был получен заголовок DPoP-Nonce. Закодируйте этот JSON-код с помощью Base64Url:{ "jti":"JTI_VALUE", "htm":"POST", "htu":"https://oauth2.googleapis.com/token", "iat":YOUR_JWT_ISSUED_TIME, "nonce":"SERVER_PROVIDED_NONCE" }Значение
jtiзависит от типа биржи:- При обмене кода авторизации параметр
jtiдолжен представлять собой хеш SHA256 кода авторизации, закодированный в Base64Url:"jti":"BASE64URL(SHA256(AUTHORIZATION_CODE))". - Для обмена токенами обновления параметр
jtiдолжен быть уникальным идентификатором для каждого запроса:"jti":"YOUR_UNIQUE_PER_REQUEST_IDENTIFIER".
- При обмене кода авторизации параметр
- Подпишите подтверждение:
Объедините закодированный заголовок и полезную нагрузку, разделив их точкой (
.), а затем подпишите результат закрытым ключом, используя ES256. Обратите внимание, что для JWS требуется, чтобы подпись была в необработанном форматеR | S(64 байта для P-256). Если вы используете OpenSSL напрямую, вам нужно преобразовать подпись, закодированную по умолчанию в формате ASN.1 DER, в этот необработанный формат.
Успешный обмен подтверждается ответом 200 OK, содержащим токены. Если при обмене используется действительное доказательство DPoP, возвращаемый Google токен обновления будет связан с вашим ключом, но токены доступа не будут связаны с DPoP. Токены доступа сохранят token_type Bearer вместо DPoP.
Кроме того, Google возвращает в ответе HTTP-заголовок DPoP-Nonce.
Клиент должен кешировать этот nonce и включать его в утверждение nonce подтверждения DPoP в последующих запросах (например, при обмене токена обновления на новый токен доступа или при вызове API, защищенных DPoP). Используя этот одноразовый код, вы можете избежать дополнительной ошибки (use_dpop_nonce) при следующем запросе.
Доказательства DPoP должны быть включены в запросы на обмен токенов, созданные с помощью токенов обновления, связанных с DPoP.
Неудачный обмен происходит, если заголовок DPoP отсутствует, когда он должен быть, или недействителен, а также если доказательство использует ключ, отличный от того, который связан с токеном. В таких случаях сервер возвращает ошибку 400 Bad Request.
Если в подтверждении DPoP есть несовпадающие утверждения htm или htu, просроченное утверждение iat, повторно использованное утверждение jti или недействительная подпись, Google возвращает код ошибки invalid_dpop_proof.
Если требуется одноразовый код DPoP, например при обмене токена обновления, а в подтверждении DPoP отсутствует утверждение nonce или значение одноразового кода неприемлемо для сервера (например, оно устарело, уже использовалось или неправильное), Google возвращает код ошибки use_dpop_nonce вместе с HTTP-заголовком DPoP-Nonce, содержащим новый одноразовый код, который можно использовать в следующем запросе.
В других случаях может возвращаться значение invalid_grant.
В ответ на этот запрос Google возвращает объект JSON, содержащий токен доступа с ограниченным сроком действия и токен обновления.
Обратите внимание, что токен обновления возвращается, только если в первоначальном запросе к серверу авторизации Google параметр access_type имеет значение offline.
Ответ содержит следующие поля:
| Поля | |
|---|---|
access_token |
Токен, который приложение отправляет для авторизации запроса к API Google. |
expires_in |
Оставшееся время действия токена доступа в секундах. |
refresh_token |
Токен, который можно использовать для получения нового токена доступа. Токены обновления действительны до тех пор, пока пользователь не отзовет доступ или пока не истечет срок действия токена. Если использовался протокол DPoP, токен обновления будет связан с закрытым ключом, который использовался для подписи доказательства DPoP.
Это поле будет присутствовать в ответе, только если в исходном запросе к серверу авторизации Google вы задали для параметра access_type значение offline.
|
refresh_token_expires_in |
Оставшийся срок действия токена обновления в секундах. Это значение задается, только если пользователь предоставляет доступ на определенное время. |
scope |
Области действия доступа, предоставленные access_token, в виде списка строк, разделенных пробелами, с учетом регистра. |
token_type |
Тип возвращаемого токена. Это значение всегда равно Bearer, даже если используется DPoP. |
Ниже приведен пример заголовков и тела успешного ответа при использовании DPoP:
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DPoP-Nonce: AN3XwJjZsjnb0ZuWkRlek8QU7wY-Zhf-5IP6tO0tORz0KgtDT1Bo8FX-w4nz3r5lnepI
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3920,
"token_type": "Bearer",
"scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
"refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI"
}Ошибки
При обмене кода авторизации на токен доступа вместо ожидаемого ответа может появиться следующая ошибка: В этом разделе перечислены распространенные коды ошибок и предлагаемые решения.
invalid_grant
Указан недействительный код авторизации или код в неверном формате. Запросите новый код, перезапустив процесс OAuth, чтобы снова запросить согласие пользователя.
Шаг 6. Проверьте, какие области действия предоставили пользователи
Если вы запрашиваете несколько разрешений (областей действия), пользователи могут не предоставить вашему приложению доступ ко всем из них. Приложение должно проверять, какие области доступа были предоставлены, и корректно обрабатывать ситуации, когда в некоторых разрешениях отказано. Обычно в таких случаях отключаются функции, которые зависят от этих разрешений.
Однако из этого правила бывают исключения. Приложения Google Workspace Enterprise с делегированием доступа к данным в домене или приложения, помеченные как надежные, обходят окно запроса доступа с детализированными разрешениями. Для таких приложений пользователи не увидят окно запроса доступа с детализированными разрешениями. Вместо этого приложение получит либо все запрошенные области действия, либо ни одной.
Подробную информацию можно найти в статье Как работать с детализированными разрешениями.
PHP
Чтобы проверить, какие области действия разрешил пользователь, используйте метод getGrantedScope():
// Space-separated string of granted scopes if it exists, otherwise null. $granted_scopes = $client->getOAuth2Service()->getGrantedScope(); // Determine which scopes user granted and build a dictionary $granted_scopes_dict = [ 'Drive' => str_contains($granted_scopes, Google\Service\Drive::DRIVE_METADATA_READONLY), 'Calendar' => str_contains($granted_scopes, Google\Service\Calendar::CALENDAR_READONLY) ];
Python
Возвращенный объект credentials содержит свойство granted_scopes, которое представляет собой список областей, доступ к которым пользователь предоставил вашему приложению.
credentials = flow.credentials flask.session['credentials'] = { 'token': credentials.token, 'refresh_token': credentials.refresh_token, 'granted_scopes': credentials.granted_scopes}
Следующая функция проверяет, какие области действия пользователь предоставил приложению.
def check_granted_scopes(credentials): features = {} if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['granted_scopes']: features['drive'] = True else: features['drive'] = False if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['granted_scopes']: features['calendar'] = True else: features['calendar'] = False return features
Ruby
Если вы запрашиваете несколько областей одновременно, проверьте, какие из них были предоставлены, с помощью свойства scope объекта credentials.
# User authorized the request. Now, check which scopes were granted. if credentials.scope.include?(Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY) # User authorized read-only Drive activity permission. # Calling the APIs, etc else # User didn't authorize read-only Drive activity permission. # Update UX and application accordingly end # Check if user authorized Calendar read permission. if credentials.scope.include?(Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY) # User authorized Calendar read permission. # Calling the APIs, etc. else # User didn't authorize Calendar read permission. # Update UX and application accordingly end
Node.js
При запросе нескольких областей действия одновременно проверьте, какие из них были предоставлены, с помощью свойства scope объекта tokens.
// User authorized the request. Now, check which scopes were granted. if (tokens.scope.includes('https://www.googleapis.com/auth/drive.metadata.readonly')) { // User authorized read-only Drive activity permission. // Calling the APIs, etc. } else { // User didn't authorize read-only Drive activity permission. // Update UX and application accordingly } // Check if user authorized Calendar read permission. if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly')) { // User authorized Calendar read permission. // Calling the APIs, etc. } else { // User didn't authorize Calendar read permission. // Update UX and application accordingly }
HTTP/REST
Чтобы проверить, предоставил ли пользователь приложению доступ к определенной области действия, изучите поле scope в ответе с токеном доступа. Области действия доступа, предоставленные токеном доступа, выраженные в виде списка строк с учетом регистра, разделенных пробелами.
Например, в следующем примере ответа на запрос токена доступа указано, что пользователь предоставил вашему приложению доступ к разрешениям на чтение активности на Диске и мероприятий в Календаре:
{ "access_token": "1/fFAGRNJru1FTz70BzhT3Zg", "expires_in": 3920, "token_type": "Bearer", "scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly", "refresh_token": "1//xEoDL4iW3cxlI7yDbSRFYNG01kVKM2C-259HOF2aQbI" }
Как вызывать API Google
PHP
Чтобы вызвать API Google с помощью токена доступа, выполните следующие действия:
- Если вам нужно применить токен доступа к новому объекту
Google\Client, например если вы сохранили токен доступа в сеансе пользователя, используйте методsetAccessToken:$client->setAccessToken($access_token); - Создайте объект сервиса для API, который вы хотите вызвать. Чтобы создать объект сервиса, передайте авторизованный объект
Google\Clientконструктору нужного API. Например, чтобы вызвать Drive API, выполните следующие действия:$drive = new Google\Service\Drive($client); - Отправьте запросы к сервису API, используя
интерфейс, предоставляемый объектом сервиса.
Например, чтобы получить список файлов на Google Диске пользователя, прошедшего аутентификацию:
$files = $drive->files->listFiles(array());
Python
Получив токен доступа, ваше приложение может использовать его для авторизации запросов к API от имени определенного аккаунта пользователя или сервисного аккаунта. Используйте учетные данные авторизации пользователя, чтобы создать служебный объект для API, который вы хотите вызвать, а затем используйте этот объект для выполнения авторизованных запросов к API.
- Создайте объект сервиса для API, который вы хотите вызвать. Чтобы создать объект сервиса, вызовите метод
buildбиблиотекиgoogleapiclient.discovery, указав название и версию API, а также учетные данные пользователя: Например, чтобы вызвать версию 3 Drive API, сделайте следующее:from googleapiclient.discovery import build drive = build('drive', 'v2', credentials=credentials)
- Отправьте запросы к сервису API, используя интерфейс, предоставляемый объектом сервиса.
Например, чтобы получить список файлов на Google Диске пользователя, прошедшего аутентификацию, выполните следующий запрос:
files = drive.files().list().execute()
Ruby
Получив токен доступа, ваше приложение может использовать его для отправки запросов к API от имени определенного аккаунта пользователя или сервисного аккаунта. Используйте учетные данные авторизации пользователя, чтобы создать служебный объект для API, который вы хотите вызвать, а затем используйте этот объект для выполнения авторизованных запросов к API.
- Создайте объект сервиса для API, который вы хотите вызвать.
Например, чтобы вызвать версию 3 Drive API:
drive = Google::Apis::DriveV3::DriveService.new
- Укажите учетные данные в сервисе:
drive.authorization = credentials
- Отправляйте запросы к сервису API, используя интерфейс, предоставленный объектом сервиса.
Например, чтобы получить список файлов на Google Диске аутентифицированного пользователя, выполните следующие действия:
files = drive.list_files
Кроме того, авторизацию можно выполнять для каждого метода отдельно, передавая ему параметр options:
files = drive.list_files(options: { authorization: credentials })
Node.js
Получив токен доступа и задав его для объекта OAuth2, используйте этот объект для вызова API Google. Приложение может использовать этот токен для авторизации запросов к API от имени определенного аккаунта пользователя или сервисного аккаунта. Создайте объект сервиса для API, который вы хотите вызвать.
Например, в следующем коде используется Google Drive API, чтобы получить список названий файлов на Диске пользователя.
const { google } = require('googleapis'); // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { if (err1) return console.log('The API returned an error: ' + err1); const files = res1.data.files; if (files.length) { console.log('Files:'); files.map((file) => { console.log(`${file.name} (${file.id})`); }); } else { console.log('No files found.'); } });
HTTP/REST
Получив токен доступа, приложение может использовать его для вызова Google API от имени определенного аккаунта пользователя, если предоставлены необходимые для API области доступа. Для этого включите токен доступа в запрос к API, добавив параметр запроса access_token или значение Bearer заголовка HTTP Authorization. По возможности используйте заголовок HTTP, поскольку строки запросов обычно видны в журналах сервера. В большинстве случаев для настройки вызовов API Google можно использовать клиентскую библиотеку (например, при вызове Drive Files API).
Вы можете попробовать все API Google и посмотреть их области действия в OAuth 2.0 Playground.
Примеры запросов HTTP GET
Вызов конечной точки
drive.files (Drive Files API) с использованием HTTP-заголовка Authorization: Bearer может выглядеть следующим образом: Обратите внимание, что вам нужно указать собственный токен доступа:
GET /drive/v2/files HTTP/1.1 Host: www.googleapis.com Authorization: Bearer access_token
Вот пример вызова того же API для аутентифицированного пользователя с помощью параметра строки запроса access_token:
GET https://www.googleapis.com/drive/v2/files?access_token=access_token
Примеры использования оператора curl
Вы можете протестировать эти команды с помощью приложения командной строки curl. Вот пример с использованием заголовка HTTP (рекомендуемый вариант):
curl -H "Authorization: Bearer access_token" https://www.googleapis.com/drive/v2/files
Или параметр строки запроса:
curl https://www.googleapis.com/drive/v2/files?access_token=access_token
Полный пример
В следующем примере выводится список файлов в формате JSON, хранящихся на Google Диске пользователя, после того как пользователь выполнит аутентификацию и предоставит приложению доступ к метаданным Диска.
PHP
Чтобы запустить этот пример:
- В API Console добавьте URL локального компьютера в список URL переадресации. Например, добавьте
http://localhost:8080. - Создайте новый каталог и перейдите в него. Пример:
mkdir ~/php-oauth2-example cd ~/php-oauth2-example
- Установите клиентскую библиотеку API Google для PHP с помощью Composer:
composer require google/apiclient:^2.15.0
- Создайте файлы
index.phpиoauth2callback.phpсо следующим содержанием: - Запустите пример с помощью встроенного тестового веб-сервера PHP:
php -S localhost:8080 ~/php-oauth2-example
index.php
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); $client = new Google\Client(); $client->setAuthConfig('client_secret.json'); // User granted permission as an access token is in the session. if (isset($_SESSION['access_token']) && $_SESSION['access_token']) { $client->setAccessToken($_SESSION['access_token']); // Check if user granted Drive permission if ($_SESSION['granted_scopes_dict']['Drive']) { echo "Drive feature is enabled."; echo "</br>"; $drive = new Drive($client); $files = array(); $response = $drive->files->listFiles(array()); foreach ($response->files as $file) { echo "File: " . $file->name . " (" . $file->id . ")"; echo "</br>"; } } else { echo "Drive feature is NOT enabled."; echo "</br>"; } // Check if user granted Calendar permission if ($_SESSION['granted_scopes_dict']['Calendar']) { echo "Calendar feature is enabled."; echo "</br>"; } else { echo "Calendar feature is NOT enabled."; echo "</br>"; } } else { // Redirect users to outh2call.php which redirects users to Google OAuth 2.0 $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/oauth2callback.php'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); } ?>
oauth2callback.php
<?php require_once __DIR__.'/vendor/autoload.php'; session_start(); $client = new Google\Client(); // Required, call the setAuthConfig function to load authorization credentials from // client_secret.json file. $client->setAuthConfigFile('client_secret.json'); $client->setRedirectUri('http://' . $_SERVER['HTTP_HOST']. $_SERVER['PHP_SELF']); // Required, to set the scope value, call the addScope function. $client->addScope([Google\Service\Drive::DRIVE_METADATA_READONLY, Google\Service\Calendar::CALENDAR_READONLY]); // Enable incremental authorization. Recommended as a best practice. $client->setIncludeGrantedScopes(true); // Recommended, offline access will give you both an access and refresh token so that // your app can refresh the access token without user interaction. $client->setAccessType("offline"); // Generate a URL for authorization as it doesn't contain code and error if (!isset($_GET['code']) && !isset($_GET['error'])) { // Generate and set state value $state = bin2hex(random_bytes(16)); $client->setState($state); $_SESSION['state'] = $state; // Generate a url that asks permissions. $auth_url = $client->createAuthUrl(); header('Location: ' . filter_var($auth_url, FILTER_SANITIZE_URL)); } // User authorized the request and authorization code is returned to exchange access and // refresh tokens. if (isset($_GET['code'])) { // Check the state value if (!isset($_GET['state']) || $_GET['state'] !== $_SESSION['state']) { die('State mismatch. Possible CSRF attack.'); } // Get access and refresh tokens (if access_type is offline) $token = $client->fetchAccessTokenWithAuthCode($_GET['code']); /** Save access and refresh token to the session variables. * ACTION ITEM: In a production app, you likely want to save the * refresh token in a secure persistent storage instead. */ $_SESSION['access_token'] = $token; $_SESSION['refresh_token'] = $client->getRefreshToken(); // Space-separated string of granted scopes if it exists, otherwise null. $granted_scopes = $client->getOAuth2Service()->getGrantedScope(); // Determine which scopes user granted and build a dictionary $granted_scopes_dict = [ 'Drive' => str_contains($granted_scopes, Google\Service\Drive::DRIVE_METADATA_READONLY), 'Calendar' => str_contains($granted_scopes, Google\Service\Calendar::CALENDAR_READONLY) ]; $_SESSION['granted_scopes_dict'] = $granted_scopes_dict; $redirect_uri = 'http://' . $_SERVER['HTTP_HOST'] . '/'; header('Location: ' . filter_var($redirect_uri, FILTER_SANITIZE_URL)); } // An error response e.g. error=access_denied if (isset($_GET['error'])) { echo "Error: ". $_GET['error']; } ?>
Python
В этом примере используется фреймворк Flask. Он запускает веб-приложение по адресу http://localhost:8080, которое позволяет протестировать процесс OAuth 2.0. Если вы перейдете по этому URL, то увидите пять ссылок:
- Call Drive API (Вызвать Drive API). Ссылка на страницу, которая пытается выполнить пример запроса к API, если пользователь предоставил разрешение. При необходимости запускается процесс авторизации. Если все пройдет успешно, на странице появится ответ API.
- Страница для тестирования вызовов Calendar API. Эта ссылка ведет на страницу, которая пытается выполнить пример запроса Calendar API, если пользователи предоставили разрешение. При необходимости запускается процесс авторизации. Если запрос выполнен успешно, на странице появится ответ API.
- Проверить процесс аутентификации. Эта ссылка ведет на страницу, на которой пользователь проходит процесс авторизации. Приложение запрашивает разрешение на отправку авторизованных запросов к API от имени пользователя.
- Отозвать текущие учетные данные. Эта ссылка ведет на страницу, на которой можно отозвать разрешения, которые пользователь уже предоставил приложению.
- Clear Flask session credentials (Очистить учетные данные сеанса Flask). Эта ссылка позволяет удалить учетные данные, которые хранятся в сеансе Flask. Это позволит вам увидеть, что произойдет, если пользователь, который уже предоставил разрешение вашему приложению, попытается выполнить запрос к API в новом сеансе. Кроме того, вы можете посмотреть ответ API, который получит ваше приложение, если пользователь отзовет разрешения, предоставленные вашему приложению, а оно все равно попытается авторизовать запрос с помощью отозванного токена доступа.
# -*- coding: utf-8 -*- import os import flask import json import requests import google.oauth2.credentials import google_auth_oauthlib.flow import googleapiclient.discovery # This variable specifies the name of a file that contains the OAuth 2.0 # information for this application, including its client_id and client_secret. CLIENT_SECRETS_FILE = "client_secret.json" # The OAuth 2.0 access scope allows for access to the # authenticated user's account and requires requests to use an SSL connection. SCOPES = ['https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/calendar.readonly'] API_SERVICE_NAME = 'drive' API_VERSION = 'v2' app = flask.Flask(__name__) # Note: A secret key is included in the sample so that it works. # If you use this code in your application, replace this with a truly secret # key. See https://flask.palletsprojects.com/quickstart/#sessions. app.secret_key = 'REPLACE ME - this value is here as a placeholder.' @app.route('/') def index(): return print_index_table() @app.route('/drive') def drive_api_request(): if 'credentials' not in flask.session: return flask.redirect('authorize') features = flask.session['features'] if features['drive']: # Load client secrets from the server-side file. with open(CLIENT_SECRETS_FILE, 'r') as f: client_config = json.load(f)['web'] # Load user-specific credentials from browser session storage. session_credentials = flask.session['credentials'] # Reconstruct the credentials object. credentials = google.oauth2.credentials.Credentials( refresh_token=session_credentials.get('refresh_token'), scopes=session_credentials.get('granted_scopes'), token=session_credentials.get('token'), client_id=client_config.get('client_id'), client_secret=client_config.get('client_secret'), token_uri=client_config.get('token_uri')) drive = googleapiclient.discovery.build( API_SERVICE_NAME, API_VERSION, credentials=credentials) files = drive.files().list().execute() # Save credentials back to session in case access token was refreshed. flask.session['credentials'] = credentials_to_dict(credentials) return flask.jsonify(**files) else: # User didn't authorize read-only Drive activity permission. return '<p>Drive feature is not enabled.</p>' @app.route('/calendar') def calendar_api_request(): if 'credentials' not in flask.session: return flask.redirect('authorize') features = flask.session['features'] if features['calendar']: # User authorized Calendar read permission. # Calling the APIs, etc. return ('<p>User granted the Google Calendar read permission. '+ 'This sample code does not include code to call Calendar</p>') else: # User didn't authorize Calendar read permission. # Update UX and application accordingly return '<p>Calendar feature is not enabled.</p>' @app.route('/authorize') def authorize(): # Create flow instance to manage the OAuth 2.0 Authorization Grant Flow steps. flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES) # The URI created here must exactly match one of the authorized redirect URIs # for the OAuth 2.0 client, which you configured in the API Console. If this # value doesn't match an authorized URI, you will get a 'redirect_uri_mismatch' # error. flow.redirect_uri = flask.url_for('oauth2callback', _external=True) authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true') # Store the state so the callback can verify the auth server response. flask.session['state'] = state return flask.redirect(authorization_url) @app.route('/oauth2callback') def oauth2callback(): # Specify the state when creating the flow in the callback so that it can # verified in the authorization server response. state = flask.session['state'] flow = google_auth_oauthlib.flow.Flow.from_client_secrets_file( CLIENT_SECRETS_FILE, scopes=SCOPES, state=state) flow.redirect_uri = flask.url_for('oauth2callback', _external=True) # Use the authorization server's response to fetch the OAuth 2.0 tokens. authorization_response = flask.request.url flow.fetch_token(authorization_response=authorization_response) # Store credentials in the session. # ACTION ITEM: In a production app, you likely want to save these # credentials in a persistent database instead. credentials = flow.credentials credentials = credentials_to_dict(credentials) flask.session['credentials'] = credentials # Check which scopes user granted features = check_granted_scopes(credentials) flask.session['features'] = features return flask.redirect('/') @app.route('/revoke') def revoke(): if 'credentials' not in flask.session: return ('You need to <a href="/authorize">authorize</a> before ' + 'testing the code to revoke credentials.') # Load client secrets from the server-side file. with open(CLIENT_SECRETS_FILE, 'r') as f: client_config = json.load(f)['web'] # Load user-specific credentials from the session. session_credentials = flask.session['credentials'] # Reconstruct the credentials object. credentials = google.oauth2.credentials.Credentials( refresh_token=session_credentials.get('refresh_token'), scopes=session_credentials.get('granted_scopes'), token=session_credentials.get('token'), client_id=client_config.get('client_id'), client_secret=client_config.get('client_secret'), token_uri=client_config.get('token_uri')) revoke = requests.post('https://oauth2.googleapis.com/revoke', params={'token': credentials.token}, headers = {'content-type': 'application/x-www-form-urlencoded'}) status_code = getattr(revoke, 'status_code') if status_code == 200: # Clear the user's session credentials after successful revocation if 'credentials' in flask.session: del flask.session['credentials'] del flask.session['features'] return('Credentials successfully revoked.' + print_index_table()) else: return('An error occurred.' + print_index_table()) @app.route('/clear') def clear_credentials(): if 'credentials' in flask.session: del flask.session['credentials'] return ('Credentials have been cleared.<br><br>' + print_index_table()) def credentials_to_dict(credentials): return {'token': credentials.token, 'refresh_token': credentials.refresh_token, 'granted_scopes': credentials.granted_scopes} def check_granted_scopes(credentials): features = {} if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['granted_scopes']: features['drive'] = True else: features['drive'] = False if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['granted_scopes']: features['calendar'] = True else: features['calendar'] = False return features def print_index_table(): return ('<table>' + '<tr><td><a href="/authorize">Test the auth flow directly</a></td>' + '<td>Go directly to the authorization flow. If there are stored ' + ' credentials, you still might not be prompted to reauthorize ' + ' the application.</td></tr>' + '<tr><td><a href="/drive">Call Drive API directly</a></td>' + '<td> Use stored credentials to call the API, you still might not be prompted to reauthorize ' + ' the application.</td></tr>' + '<tr><td><a href="/calendar">Call Calendar API directly</a></td>' + '<td> Use stored credentials to call the API, you still might not be prompted to reauthorize ' + ' the application.</td></tr>' + '<tr><td><a href="/revoke">Revoke current credentials</a></td>' + '<td>Revoke the access token associated with the current user ' + ' session. After revoking credentials, if you go to the test ' + ' page, you should see an <code>invalid_grant</code> error.' + '</td></tr>' + '<tr><td><a href="/clear">Clear Flask session credentials</a></td>' + '<td>Clear the access token currently stored in the user session. ' + ' After clearing the token, if you <a href="/authorize">authorize</a> ' + ' again, you should go back to the auth flow.' + '</td></tr></table>') if __name__ == '__main__': # When running locally, disable OAuthlib's HTTPs verification. # ACTION ITEM for developers: # When running in production *do not* leave this option enabled. os.environ['OAUTHLIB_INSECURE_TRANSPORT'] = '1' # This disables the requested scopes and granted scopes check. # If users only grant partial request, the warning would not be thrown. os.environ['OAUTHLIB_RELAX_TOKEN_SCOPE'] = '1' # Specify a hostname and port that are set as a valid redirect URI # for your API project in the Google API Console. app.run('localhost', 8080, debug=True)
Ruby
В этом примере используется фреймворк Sinatra.
require 'googleauth' require 'googleauth/web_user_authorizer' require 'googleauth/stores/redis_token_store' require 'google/apis/drive_v3' require 'google/apis/calendar_v3' require 'sinatra' configure do enable :sessions # Required, call the from_file method to retrieve the client ID from a # client_secret.json file. set :client_id, Google::Auth::ClientId.from_file('/path/to/client_secret.json') # Required, scope value # Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar. scope = ['Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY', 'Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY'] # Required, Authorizers require a storage instance to manage long term persistence of # access and refresh tokens. set :token_store, Google::Auth::Stores::RedisTokenStore.new(redis: Redis.new) # Required, indicate where the API server will redirect the user after the user completes # the authorization flow. The redirect URI is required. The value must exactly # match one of the authorized redirect URIs for the OAuth 2.0 client, which you # configured in the API Console. If this value doesn't match an authorized URI, # you will get a 'redirect_uri_mismatch' error. set :callback_uri, '/oauth2callback' # To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI # from the client_secret.json file. To get these credentials for your application, visit # https://console.cloud.google.com/apis/credentials. set :authorizer, Google::Auth::WebUserAuthorizer.new(settings.client_id, settings.scope, settings.token_store, callback_uri: settings.callback_uri) end get '/' do # NOTE: Assumes the user is already authenticated to the app user_id = request.session['user_id'] # Fetch stored credentials for the user from the given request session. # nil if none present credentials = settings.authorizer.get_credentials(user_id, request) if credentials.nil? # Generate a url that asks the user to authorize requested scope(s). # Then, redirect user to the url. redirect settings.authorizer.get_authorization_url(request: request) end # User authorized the request. Now, check which scopes were granted. if credentials.scope.include?(Google::Apis::DriveV3::AUTH_DRIVE_METADATA_READONLY) # User authorized read-only Drive activity permission. # Example of using Google Drive API to list filenames in user's Drive. drive = Google::Apis::DriveV3::DriveService.new files = drive.list_files(options: { authorization: credentials }) "<pre>#{JSON.pretty_generate(files.to_h)}</pre>" else # User didn't authorize read-only Drive activity permission. # Update UX and application accordingly end # Check if user authorized Calendar read permission. if credentials.scope.include?(Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY) # User authorized Calendar read permission. # Calling the APIs, etc. else # User didn't authorize Calendar read permission. # Update UX and application accordingly end end # Receive the callback from Google's OAuth 2.0 server. get '/oauth2callback' do # Handle the result of the oauth callback. Defers the exchange of the code by # temporarily stashing the results in the user's session. target_url = Google::Auth::WebUserAuthorizer.handle_auth_callback_deferred(request) redirect target_url end
Node.js
Чтобы запустить этот пример:
-
В API Console добавьте URL локального компьютера в список URL переадресации. Например, добавьте
http://localhost. - Убедитесь, что у вас установлена поддерживаемая версия Node.js с долгосрочной поддержкой (LTS) или текущая версия.
-
Создайте новый каталог и перейдите в него. Пример:
mkdir ~/nodejs-oauth2-example cd ~/nodejs-oauth2-example
-
Установите клиентскую библиотеку Google API для Node.js с помощью npm:
npm install googleapis
-
Создайте файлы
main.jsсо следующим содержимым. -
Запустите пример:
node .\main.js
main.js
const http = require('http'); const https = require('https'); const url = require('url'); const { google } = require('googleapis'); const crypto = require('crypto'); const express = require('express'); const session = require('express-session'); /** * To use OAuth2 authentication, we need access to a CLIENT_ID, CLIENT_SECRET, AND REDIRECT_URI. * To get these credentials for your application, visit * https://console.cloud.google.com/apis/credentials. */ const oauth2Client = new google.auth.OAuth2( YOUR_CLIENT_ID, YOUR_CLIENT_SECRET, YOUR_REDIRECT_URL ); // Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar. const scopes = [ 'https://www.googleapis.com/auth/drive.metadata.readonly', 'https://www.googleapis.com/auth/calendar.readonly' ]; /* Global variable that stores user credential in this code example. * ACTION ITEM for developers: * Store user's refresh token in your data store if * incorporating this code into your real app. * For more information on handling refresh tokens, * see https://github.com/googleapis/google-api-nodejs-client#handling-refresh-tokens */ let userCredential = null; async function main() { const app = express(); app.use(session({ secret: 'your_secure_secret_key', // Replace with a strong secret resave: false, saveUninitialized: false, })); // Example on redirecting user to Google's OAuth 2.0 server. app.get('/', async (req, res) => { // Generate a secure random state value. const state = crypto.randomBytes(32).toString('hex'); // Store state in the session req.session.state = state; // Generate a url that asks permissions for the Drive activity and Google Calendar scope const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true, // Include the state parameter to reduce the risk of CSRF attacks. state: state }); res.redirect(authorizationUrl); }); // Receive the callback from Google's OAuth 2.0 server. app.get('/oauth2callback', async (req, res) => { // Handle the OAuth 2.0 server response let q = url.parse(req.url, true).query; if (q.error) { // An error response e.g. error=access_denied console.log('Error:' + q.error); } else if (q.state !== req.session.state) { //check state value console.log('State mismatch. Possible CSRF attack'); res.end('State mismatch. Possible CSRF attack'); } else { // Get access and refresh tokens (if access_type is offline) let { tokens } = await oauth2Client.getToken(q.code); oauth2Client.setCredentials(tokens); /** Save credential to the global variable in case access token was refreshed. * ACTION ITEM: In a production app, you likely want to save the refresh token * in a secure persistent database instead. */ userCredential = tokens; // User authorized the request. Now, check which scopes were granted. if (tokens.scope.includes('https://www.googleapis.com/auth/drive.metadata.readonly')) { // User authorized read-only Drive activity permission. // Example of using Google Drive API to list filenames in user's Drive. const drive = google.drive('v3'); drive.files.list({ auth: oauth2Client, pageSize: 10, fields: 'nextPageToken, files(id, name)', }, (err1, res1) => { if (err1) return console.log('The API returned an error: ' + err1); const files = res1.data.files; if (files.length) { console.log('Files:'); files.map((file) => { console.log(`${file.name} (${file.id})`); }); } else { console.log('No files found.'); } }); } else { // User didn't authorize read-only Drive activity permission. // Update UX and application accordingly } // Check if user authorized Calendar read permission. if (tokens.scope.includes('https://www.googleapis.com/auth/calendar.readonly')) { // User authorized Calendar read permission. // Calling the APIs, etc. } else { // User didn't authorize Calendar read permission. // Update UX and application accordingly } } }); // Example on revoking a token app.get('/revoke', async (req, res) => { // Build the string for the POST request let postData = "token=" + userCredential.access_token; // Options for POST request to Google's OAuth 2.0 server to revoke a token let postOptions = { host: 'oauth2.googleapis.com', port: '443', path: '/revoke', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; // Set up the request const postReq = https.request(postOptions, function (res) { res.setEncoding('utf8'); res.on('data', d => { console.log('Response: ' + d); }); }); postReq.on('error', error => { console.log(error) }); // Post the request with data postReq.write(postData); postReq.end(); }); const server = http.createServer(app); server.listen(8080); } main().catch(console.error);
HTTP/REST
В этом примере на Python используется фреймворк Flask и библиотека Requests, чтобы продемонстрировать веб-процесс OAuth 2.0. Для этого процесса мы рекомендуем использовать клиентскую библиотеку API Google для Python. (В примере на вкладке Python используется клиентская библиотека.)
import json import flask import requests app = flask.Flask(__name__) # To get these credentials (CLIENT_ID CLIENT_SECRET) and for your application, visit # https://console.cloud.google.com/apis/credentials. CLIENT_ID = '123456789.apps.googleusercontent.com' CLIENT_SECRET = 'abc123' # Read from a file or environmental variable in a real app # Access scopes for two non-Sign-In scopes: Read-only Drive activity and Google Calendar. SCOPE = 'https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly' # Indicate where the API server will redirect the user after the user completes # the authorization flow. The redirect URI is required. The value must exactly # match one of the authorized redirect URIs for the OAuth 2.0 client, which you # configured in the API Console. If this value doesn't match an authorized URI, # you will get a 'redirect_uri_mismatch' error. REDIRECT_URI = 'http://example.com/oauth2callback' @app.route('/') def index(): if 'credentials' not in flask.session: return flask.redirect(flask.url_for('oauth2callback')) credentials = json.loads(flask.session['credentials']) if credentials['expires_in'] <= 0: return flask.redirect(flask.url_for('oauth2callback')) else: # User authorized the request. Now, check which scopes were granted. if 'https://www.googleapis.com/auth/drive.metadata.readonly' in credentials['scope']: # User authorized read-only Drive activity permission. # Example of using Google Drive API to list filenames in user's Drive. headers = {'Authorization': 'Bearer {}'.format(credentials['access_token'])} req_uri = 'https://www.googleapis.com/drive/v2/files' r = requests.get(req_uri, headers=headers).text else: # User didn't authorize read-only Drive activity permission. # Update UX and application accordingly r = 'User did not authorize Drive permission.' # Check if user authorized Calendar read permission. if 'https://www.googleapis.com/auth/calendar.readonly' in credentials['scope']: # User authorized Calendar read permission. # Calling the APIs, etc. r += 'User authorized Calendar permission.' else: # User didn't authorize Calendar read permission. # Update UX and application accordingly r += 'User did not authorize Calendar permission.' return r @app.route('/oauth2callback') def oauth2callback(): if 'code' not in flask.request.args: state = str(uuid.uuid4()) flask.session['state'] = state # Generate a url that asks permissions for the Drive activity # and Google Calendar scope. Then, redirect user to the url. auth_uri = ('https://accounts.google.com/o/oauth2/v2/auth?response_type=code' '&client_id={}&redirect_uri={}&scope={}&state={}').format(CLIENT_ID, REDIRECT_URI, SCOPE, state) return flask.redirect(auth_uri) else: if 'state' not in flask.request.args or flask.request.args['state'] != flask.session['state']: return 'State mismatch. Possible CSRF attack.', 400 auth_code = flask.request.args.get('code') data = {'code': auth_code, 'client_id': CLIENT_ID, 'client_secret': CLIENT_SECRET, 'redirect_uri': REDIRECT_URI, 'grant_type': 'authorization_code'} # Exchange authorization code for access and refresh tokens (if access_type is offline) r = requests.post('https://oauth2.googleapis.com/token', data=data) flask.session['credentials'] = r.text return flask.redirect(flask.url_for('index')) if __name__ == '__main__': import uuid app.secret_key = str(uuid.uuid4()) app.debug = False app.run()
Правила проверки URI переадресации
Чтобы помочь разработчикам защитить свои приложения, Google применяет к URI перенаправления следующие правила проверки: Ваши URI перенаправления должны соответствовать этим правилам. Определения домена, хоста, пути, запроса, схемы и информации о пользователе, используемые в этих правилах, приведены в разделе 3 RFC 3986.
| Правила проверки данных | |
|---|---|
| Схема |
URI перенаправления должны использовать схему HTTPS, а не обычный HTTP. URI localhost (включая URI IP-адресов localhost) не подпадают под это правило. |
| Хост |
В качестве хостов нельзя использовать IP-адреса. Это правило не распространяется на IP-адреса localhost. |
| Домен |
“googleusercontent.com”.goo.gl), если только домен не принадлежит приложению. Кроме того, если приложение, которому принадлежит домен сокращателя, перенаправляет на этот домен, URI перенаправления должен содержать “/google-callback/” в пути или заканчиваться на “/google-callback”. |
| Userinfo |
URI переадресации не могут содержать подкомпонент userinfo. |
| Путь |
URI переадресации не могут содержать переход к родительскому каталогу (также называемый возвратом к каталогу), который представлен символами |
| Запрос |
URI перенаправления не могут содержать открытые перенаправления. |
| Фрагмент |
URI переадресации не могут содержать компонент фрагмента. |
| Персонажи |
URI перенаправления не могут содержать некоторые символы, в том числе:
|
Дополнительная авторизация
В протоколе OAuth 2.0 приложение запрашивает авторизацию для доступа к ресурсам, которые идентифицируются областями действия. Рекомендуется запрашивать авторизацию для ресурсов в тот момент, когда она вам нужна. Чтобы реализовать этот метод, сервер авторизации Google поддерживает поэтапную авторизацию. Эта функция позволяет запрашивать области действия по мере необходимости и, если пользователь предоставляет разрешение для новой области действия, возвращает код авторизации, который можно обменять на токен, содержащий все области действия, для которых пользователь предоставил проекту разрешение.
Например, приложению, которое позволяет слушать музыкальные треки и создавать миксы, при входе может потребоваться очень мало ресурсов, возможно, только имя пользователя. Однако для сохранения готового микса потребуется доступ к Google Диску. Большинство пользователей сочтут естественным, если запрос на доступ к Google Диску появится только тогда, когда приложению действительно понадобится этот доступ.
В этом случае при входе приложение может запросить области openid и profile, а затем, при первом запросе на сохранение микса, – область https://www.googleapis.com/auth/drive.file.
Чтобы реализовать поэтапную авторизацию, выполните обычный процесс запроса токена доступа, но убедитесь, что запрос на авторизацию включает ранее предоставленные области действия. Это позволяет избежать необходимости управлять несколькими токенами доступа.
К токену доступа, полученному в результате пошаговой авторизации, применяются следующие правила:
- Токен можно использовать для доступа к ресурсам, соответствующим любому из областей действия, включенных в новую комбинированную авторизацию.
- Когда вы используете токен обновления для комбинированной авторизации, чтобы получить токен доступа, токен доступа представляет комбинированную авторизацию и может использоваться для любого из значений
scope, включенных в ответ. - Объединенная авторизация включает все области действия, которые пользователь предоставил проекту API, даже если запросы на предоставление были отправлены от разных клиентов. Например, если пользователь предоставил доступ к одной области действия через клиент приложения для ПК, а затем предоставил доступ к другой области действия через мобильный клиент, то комбинированная авторизация будет включать обе области действия.
- Если вы отзовете токен, представляющий комбинированное разрешение, доступ ко всем областям действия этого разрешения от имени связанного пользователя будет отозван одновременно.
В примерах кода для разных языков программирования, приведенных в разделе Шаг 1. Настройте авторизацию и перенаправление на сервер OAuth 2.0 Google, используется пошаговая авторизация. В примерах кода ниже также показано, как использовать пошаговую авторизацию.
PHP
$client->setIncludeGrantedScopes(true);Python
В Python задайте для аргумента ключевого слова include_granted_scopes значение true, чтобы запрос на авторизацию включал ранее предоставленные области действия. Вполне возможно, что include_granted_scopes будет не единственным аргументом ключевого слова, который вы зададите, как показано в примере ниже.
authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true')
Ruby
auth_client.update!( :additional_parameters => {"include_granted_scopes" => "true"} )
Node.js
const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true });
HTTP/REST
GET https://accounts.google.com/o/oauth2/v2/auth? client_id=your_client_id& response_type=code& state=state_parameter_passthrough_value& scope=https%3A//www.googleapis.com/auth/drive.metadata.readonly%20https%3A//www.googleapis.com/auth/calendar.readonly& redirect_uri=https%3A//developers.google.com/oauthplayground& prompt=consent& include_granted_scopes=true
Обновление токена доступа (офлайн-режим)
Срок действия токенов доступа периодически истекает, и они становятся недействительными учетными данными для связанного запроса к API. Вы можете обновить токен доступа, не запрашивая разрешения пользователя (в том числе когда пользователь отсутствует), если вы запросили офлайн-доступ к областям действия, связанным с токеном.
- Если вы используете клиентскую библиотеку API Google, объект API-клиента обновляет токен доступа по мере необходимости, если вы настроили этот объект для офлайн-режима.
- Если вы не используете клиентскую библиотеку, то при перенаправлении пользователя на сервер OAuth 2.0 Google вам нужно задать для параметра HTTP-запроса
access_typeзначениеoffline. В этом случае сервер авторизации Google возвращает токен обновления, когда вы обмениваете код авторизации на токен доступа. Если срок действия токена доступа истечет, вы сможете использовать токен обновления, чтобы получить новый токен доступа.
Запрос офлайн-доступа необходим для любого приложения, которому требуется доступ к API Google, когда пользователь отсутствует. Например, приложению, которое выполняет резервное копирование или выполняет действия в заданное время, необходимо иметь возможность обновить токен доступа, когда пользователь отсутствует. Стиль доступа по умолчанию называется online.
Веб-приложения на стороне сервера, установленные приложения и устройства получают токены обновления в процессе авторизации. Токены обновления обычно не используются в клиентских веб-приложениях (JavaScript).
PHP
Если вашему приложению нужен офлайн-доступ к Google API, задайте для типа доступа API-клиента значение offline:
$client->setAccessType("offline");После того как пользователь предоставит офлайн-доступ к запрошенным областям действия, вы сможете продолжать использовать клиент API для доступа к API Google от имени пользователя, когда он не в сети. Объект клиента будет обновлять токен доступа по мере необходимости.
Python
В Python задайте для аргумента ключевого слова access_type значение offline, чтобы вы могли обновлять токен доступа, не запрашивая разрешение у пользователя повторно. Вполне возможно, что access_type будет не единственным аргументом ключевого слова, который вы зададите, как показано в примере ниже.
authorization_url, state = flow.authorization_url( # Enable offline access so that you can refresh an access token without # re-prompting the user for permission. Recommended for web server apps. access_type='offline', # Enable incremental authorization. Recommended as a best practice. include_granted_scopes='true')
После того как пользователь предоставит офлайн-доступ к запрошенным областям действия, вы сможете использовать API-клиент для доступа к API Google от имени пользователя, когда он не в сети. Объект клиента будет обновлять маркер доступа по мере необходимости.
Ruby
Если вашему приложению нужен офлайн-доступ к Google API, задайте для типа доступа API-клиента значение offline:
auth_client.update!( :additional_parameters => {"access_type" => "offline"} )
После того как пользователь предоставит офлайн-доступ к запрошенным областям действия, вы сможете продолжать использовать клиент API для доступа к API Google от имени пользователя, когда он не в сети. Объект клиента будет обновлять токен доступа по мере необходимости.
Node.js
Если вашему приложению нужен офлайн-доступ к Google API, задайте для типа доступа API-клиента значение offline:
const authorizationUrl = oauth2Client.generateAuthUrl({ // 'online' (default) or 'offline' (gets refresh_token) access_type: 'offline', /** Pass in the scopes array defined above. * Alternatively, if only one scope is needed, you can pass a scope URL as a string */ scope: scopes, // Enable incremental authorization. Recommended as a best practice. include_granted_scopes: true });
После того как пользователь предоставит офлайн-доступ к запрошенным областям действия, вы сможете использовать API-клиент для доступа к API Google от имени пользователя, когда он не в сети. Объект клиента будет обновлять токен доступа по мере необходимости.
Срок действия токенов доступа истекает. Эта библиотека автоматически использует токен обновления, чтобы получить новый токен доступа, если срок действия текущего истекает. Чтобы всегда хранить самые новые токены, используйте событие tokens:
oauth2Client.on('tokens', (tokens) => { if (tokens.refresh_token) { // store the refresh_token in your secure persistent database console.log(tokens.refresh_token); } console.log(tokens.access_token); });
Это событие происходит только при первой авторизации. Чтобы получить токен обновления, при вызове метода generateAuthUrl необходимо задать для параметра access_type значение offline. Если вы уже предоставили приложению необходимые разрешения, но не задали подходящие ограничения для получения токена обновления, вам нужно будет повторно авторизовать приложение, чтобы получить новый токен обновления.
Чтобы задать refresh_token позже, используйте метод setCredentials:
oauth2Client.setCredentials({ refresh_token: `STORED_REFRESH_TOKEN` });
После того как клиент получит токен обновления, токены доступа будут запрашиваться и обновляться автоматически при следующем вызове API.
HTTP/REST
Чтобы обновить токен доступа, приложение отправляет HTTPS-запрос POST на сервер авторизации Google (https://oauth2.googleapis.com/token), который включает в тело запроса следующие параметры:
| Название | Значение |
|---|---|
client_id |
Идентификатор клиента, полученный из API Console. |
client_secret |
Необязательно
Секретный код клиента, полученный из API Console. |
grant_type |
Согласно спецификации OAuth 2.0, значение этого поля должно быть refresh_token. |
refresh_token |
Маркер обновления, возвращенный при обмене кода авторизации. |
Использовать DPoP необязательно, но рекомендуется для повышения безопасности. Чтобы использовать DPoP с токеном обновления, ваше приложение должно генерировать новый уникальный JWT-токен подтверждения DPoP для каждого запроса к конечной точке токена. Доказательство должно быть подписано тем же закрытым ключом, который использовался при первоначальном обмене кода авторизации. Приложение добавляет подтверждение в качестве заголовка HTTP-запроса.
| Заголовок | Обязательно | Описание |
|---|---|---|
DPoP |
Необязательно | Доказательство DPoP – это токен JWT, который подтверждает владение закрытым ключом. Это заголовок, а не параметр. Если он указан, возвращаемые токены привязываются к этому ключу. Для каждого запроса необходимо создать новое уникальное доказательство, которое должно включать утверждения htm (метод HTTP) и htu (URI HTTP), соответствующие запросу. |
Ниже приведен пример запроса.
POST /token HTTP/1.1 Host: oauth2.googleapis.com Content-Type: application/x-www-form-urlencoded DPoP: DPOP_PROOF_JWT client_id=your_client_id& refresh_token=refresh_token& grant_type=refresh_token
Чтобы использовать DPoP с токеном обновления, необходимо сгенерировать новый уникальный JWT-доказательство DPoP для запроса. Пошаговые инструкции по созданию пары ключей и JWT приведены в разделе Как создать подтверждение DPoP.
Успешный обмен обозначается ответом 200 OK, содержащим новый токен доступа. Если используется DPoP, значение параметра token_type – Bearer. Успешный ответ подтверждает, что доказательство DPoP для токена обновления было принято. Google также может вернуть в ответе новый HTTP-заголовок DPoP-Nonce. В этом случае ваш клиент должен кешировать этот одноразовый код и включать его в утверждение nonce подтверждения DPoP в последующих запросах.
Если заголовок DPoP отсутствует, недействителен или в нем используется неправильный ключ, обмен не выполняется. Подробную информацию о кодах ошибок DPoP и обработке одноразовых кодов можно найти в разделе Ошибка обмена.
Ниже приведен пример заголовков и тела успешного ответа при использовании DPoP.
HTTP/1.1 200 OK
Content-Type: application/json; charset=utf-8
DPoP-Nonce: AN3XwJjZsjnb0ZuWkRlek8QU7wY-Zhf-5IP6tO0tORz0KgtDT1Bo8FX-w4nz3r5lnepI
{
"access_token": "1/fFAGRNJru1FTz70BzhT3Zg",
"expires_in": 3920,
"scope": "https://www.googleapis.com/auth/drive.metadata.readonly https://www.googleapis.com/auth/calendar.readonly",
"token_type": "Bearer"
}Обратите внимание, что количество токенов обновления ограничено. Ограничения действуют для каждой комбинации клиента и пользователя, а также для каждого пользователя во всех клиентах. Токены обновления следует сохранять в долгосрочном хранилище и использовать, пока они действительны. Если ваше приложение запрашивает слишком много токенов обновления, оно может столкнуться с этими ограничениями, и тогда более старые токены обновления перестанут работать.
Отзыв токена
В некоторых случаях пользователь может захотеть отозвать доступ, предоставленный приложению. Пользователь может отозвать доступ, перейдя в настройки аккаунта. Подробную информацию вы найдете в разделе Как закрыть доступ к аккаунту для стороннего сайта или приложения.
Приложение также может программно отозвать предоставленный ему доступ. Программный отзыв важен в тех случаях, когда пользователь отменяет подписку, удаляет приложение или когда ресурсы API, необходимые для приложения, значительно изменились. Другими словами, в процессе удаления может выполняться запрос к API, чтобы убедиться, что разрешения, ранее предоставленные приложению, удалены.
PHP
Чтобы отозвать токен программно, вызовите revokeToken():
$client->revokeToken();Python
Чтобы отозвать токен программно, отправьте запрос на адрес https://oauth2.googleapis.com/revoke, указав токен в качестве параметра и задав заголовок Content-Type:
requests.post('https://oauth2.googleapis.com/revoke', params={'token': credentials.token}, headers = {'content-type': 'application/x-www-form-urlencoded'})
Ruby
Чтобы отозвать токен программно, отправьте HTTP-запрос конечной точке oauth2.revoke:
uri = URI('https://oauth2.googleapis.com/revoke') response = Net::HTTP.post_form(uri, 'token' => auth_client.access_token)
Это может быть токен доступа или токен обновления. Если токен является токеном доступа и у него есть соответствующий токен обновления, токен обновления также будет отозван.
Если отзыв сертификата будет успешно обработан, код статуса ответа будет 200. В случае ошибки возвращается код статуса 400 и код ошибки.
Node.js
Чтобы отозвать токен программным способом, отправьте запрос HTTPS POST к конечной точке /revoke:
const https = require('https'); // Build the string for the POST request let postData = "token=" + userCredential.access_token; // Options for POST request to Google's OAuth 2.0 server to revoke a token let postOptions = { host: 'oauth2.googleapis.com', port: '443', path: '/revoke', method: 'POST', headers: { 'Content-Type': 'application/x-www-form-urlencoded', 'Content-Length': Buffer.byteLength(postData) } }; // Set up the request const postReq = https.request(postOptions, function (res) { res.setEncoding('utf8'); res.on('data', d => { console.log('Response: ' + d); }); }); postReq.on('error', error => { console.log(error) }); // Post the request with data postReq.write(postData); postReq.end();
Параметр token может быть токеном доступа или токеном обновления. Если токен является токеном доступа и у него есть соответствующий токен обновления, токен обновления также будет отозван.
Если отзыв сертификата будет успешно обработан, код статуса ответа будет 200. В случае ошибки возвращается код статуса 400 и код ошибки.
HTTP/REST
Чтобы отозвать токен программным способом, ваше приложение должно отправить запрос на адрес https://oauth2.googleapis.com/revoke и включить токен в качестве параметра:
curl -d -X -POST --header "Content-type:application/x-www-form-urlencoded" \
https://oauth2.googleapis.com/revoke?token={token}Это может быть токен доступа или токен обновления. Если токен является токеном доступа и у него есть соответствующий токен обновления, токен обновления также будет отозван.
Если отзыв будет успешно обработан, код статуса HTTP ответа будет 200. В случае ошибки возвращается код статуса HTTP 400 и код ошибки.
Доступ на основе времени
Временный доступ позволяет пользователю предоставить вашему приложению доступ к своим данным на ограниченный период времени для выполнения действия. В некоторых продуктах Google при запросе разрешений пользователи могут предоставить доступ на ограниченное время. Пример – Data Portability API, который позволяет выполнить однократный перенос данных.
Если пользователь предоставит приложению доступ на определенный срок, токен обновления будет действителен в течение этого срока. Обратите внимание, что токены обновления могут быть аннулированы раньше при определенных обстоятельствах. Подробнее о таких случаях… В таких случаях поле refresh_token_expires_in, возвращаемое в ответе на обмен кодами авторизации, представляет собой время, оставшееся до истечения срока действия токена обновления.
Как реализовать защиту связанных аккаунтов
Чтобы дополнительно защитить аккаунты пользователей, реализуйте защиту связанных аккаунтов с помощью сервиса защиты связанных аккаунтов Google. Этот сервис позволяет подписаться на уведомления о событиях безопасности, которые предоставляют вашему приложению информацию о важных изменениях в аккаунте пользователя. Затем вы можете использовать эту информацию, чтобы предпринять действия в зависимости от того, как вы решите реагировать на события.
Вот несколько примеров типов событий, которые сервис защиты связанных аккаунтов Google отправляет в ваше приложение:
-
https://schemas.openid.net/secevent/risc/event-type/sessions-revoked -
https://schemas.openid.net/secevent/oauth/event-type/token-revoked -
https://schemas.openid.net/secevent/risc/event-type/account-disabled
Дополнительную информацию о том, как реализовать защиту связанных аккаунтов, и полный список доступных событий можно найти на странице Защита аккаунтов пользователей с помощью защиты связанных аккаунтов .