Трехсторонний OAuth и поток одноразового кода с использованием google-auth-library-ruby с google-api-ruby-client
Краткий обзор: у меня есть приложение Ruby, которое работает каждую ночь и что-то делает с календарем пользователя Google. Пользователь уже предоставил доступ через отдельное приложение для реагирования. Мне не удается получить доступ к календарю пользователя для приложения ruby с кодом авторизации из приложения React.
Подробности: у меня есть интерфейс React, который может входить в систему с помощью gapi, а затем подписывать пользователя в Firebase. Вот как я настраиваю объект gapi:
this.auth2 = await loadAuth2WithProps({
apiKey: config.apiKey, // from firebase
clientId: config.clientId, // from gcp
// ....
access_type: "offline", // so we get get authorization code
})
Вот авторизация:
doSignInWithGoogle = async () => {
const googleUser = await this.auth2.signIn();
const token = googleUser.getAuthResponse().id_token;
const credential = app.auth.GoogleAuthProvider.credential(token);
return this.auth.signInWithCredential(credential);
};
Следующий шаг пользователя - предоставить приложению автономный доступ к своему календарю:
doConnectGoogleCalendar = async () => {
const params = {scope:scopes};
const result = await this.auth2.grantOfflineAccess(params);
console.log(result.code); // logs: "4/ygFsjdK....."
};
На этом этапе клиентская часть имеет код авторизации, который может быть передан в серверное приложение для обмена на токены доступа и обновления. Мне не удалось найти хороший способ использовать предоставленный пользователем код аутентификации для вызова доступных областей. Вот как я настроил клиент oauth:
auth_client = Google::APIClient::ClientSecrets.load(
File.join(Rails.root,'config','client_secrets.json') // downloaded from GCP
).to_authorization
^ Я использую те же учетные данные GCP на бэкэнде, что и для внешнего интерфейса. Это тип учетных данных "Идентификатор клиента OAuth 2.0". Я не уверен, хорошая ли это практика или нет. Кроме того, нужно ли мне определять ту же конфигурацию, что и во внешнем интерфейсе (например, access_type и scope)?.
Затем я делаю то, что написано в документации, чтобы получить доступ и обновить токены(щелкните Ruby):
auth_client.code = authorization_code_from_frontend
auth_client.fetch_access_token!
---------
Signet::AuthorizationError (Authorization failed. Server message:)
{
"error": "invalid_grant",
"error_description": "Bad Request"
}
Есть ли что-то, чего мне не хватает при настройке отдельного серверного приложения, которое может обрабатывать автономный доступ к предоставленной пользователем области? Об этих библиотеках так много разной информации, но я не смог выделить что-то, что работает.
ОБНОВЛЕНИЕ Я нашел эту страницу, описывающую "поток одноразового кода", который я нигде больше не нашел, это все документы, через которые я прошел. Он отвечает на один из моих незначительных вопросов выше: да, вы можете использовать те же секреты клиента, что и веб-приложение для бэкэнда.(см. полный пример внизу, где они это делают). Я изучу это подробнее и посмотрю, можно ли решить мою большую проблему. Также собираюсь обновить заголовок, чтобы включить поток одноразового кода.
1 ответ
После тщательного изучения примеров кода и исходного кода я получил чистое рабочее решение. Как только я нашел страницу в моем "обновлении", я понял, чтоClientSecrets
То, как я делал что-то, было устаревшим в пользу проекта https://github.com/googleapis/google-auth-library-ruby. Я рад, что нашел его, потому что он кажется более полным решением, поскольку он берет на себя все управление токенами за вас. Вот код для настройки всего:
def authorizer
client_secrets_path = File.join(Rails.root,'config','client_secrets.json')
client_id = Google::Auth::ClientId.from_file(client_secrets_path)
scope = [Google::Apis::CalendarV3::AUTH_CALENDAR_READONLY]
redis = Redis.new(url: Rails.application.secrets.redis_url)
token_store = Google::Auth::Stores::RedisTokenStore.new(redis: redis)
Google::Auth::WebUserAuthorizer.new(client_id, scope, token_store, "postmessage")
end
а затем вот как я использую код авторизации:
def exchange_for_token(user_id,auth_code)
credentials_opts = {user_id:user_id,code:auth_code}
credentials = authorizer.get_and_store_credentials_from_code(credentials_opts)
end
после вызова этого метода библиотека сохранит обмененные токены в Redis (вы можете настроить, где хранить) для последующего использования следующим образом:
def run_job(user_id)
credentials = authorizer.get_credentials(user_id)
service = Google::Apis::CalendarV3::CalendarService.new
service.authorization = credentials
calendar_list = service.list_calendar_lists.items
# ... do more things ...
end
Информации так много, что трудно выделить, что применимо к каждому условию. Надеюсь, это поможет любому, кто застрянет в "потоке одноразового кода", чтобы не тратить дни на то, чтобы биться головой о стол.