OAuth 2.0

Este documento descreve o OAuth 2.0, quando usá-lo, como adquirir IDs de cliente e como usá-lo com a biblioteca de cliente de APIs do Google para .NET.

Protocolo OAuth 2.0

OAuth 2.0 é o protocolo de autorização usado pelas APIs do Google. Confira os links a seguir para se familiarizar com o protocolo:

Como adquirir IDs e secrets do cliente

É possível acessar IDs e chaves secretas do cliente no Console de APIs do Google. Há diferentes tipos de IDs do cliente. Portanto, escolha o tipo correto para seu aplicativo:

Em cada um dos snippets de código abaixo (exceto o da conta de serviço), é preciso fazer o download da chave secreta do cliente e armazená-la como client_secrets.json no projeto.

Credenciais

Credenciais do usuário

UserCredential é uma classe auxiliar segura para linhas de execução que usa um token de acesso para acessar recursos protegidos. Um token de acesso normalmente expira após uma hora. Depois disso, você receberá um erro se tentar usá-lo.

UserCredential e AuthorizationCodeFlow atualizam automaticamente o token, o que significa apenas receber um novo token de acesso. Isso é feito usando um token de atualização de longa duração, que você recebe com o token de acesso se usar o parâmetro access_type=offline durante o fluxo do código de autorização.

Na maioria dos aplicativos, é aconselhável armazenar o token de acesso da credencial e o token de atualização no armazenamento permanente. Caso contrário, você precisará apresentar ao usuário final uma página de autorização no navegador a cada hora, porque o token de acesso expira uma hora depois de ser recebido.

Para garantir que os tokens de acesso e atualização sejam mantidos, você pode fornecer sua própria implementação de IDataStore ou usar uma das seguintes implementações oferecidas pela biblioteca:

  • FileDataStore para .NET garante que a credencial seja persistente em um arquivo.

ServiceAccountCredential

ServiceAccountCredential é semelhante a UserCredential, mas tem uma finalidade diferente. O Google OAuth 2.0 oferece suporte a interações de servidor para servidor, como aquelas entre um aplicativo da Web e o Google Cloud Storage. O aplicativo solicitante precisa provar a própria identidade para conseguir acesso a uma API, e um usuário final não precisa estar envolvido. ServiceAccountCredential armazena uma chave privada, que é usada para assinar uma solicitação e receber um novo token de acesso.

Tanto UserCredential quanto ServiceAccountCredential implementam IConfigurableHttpClientInitializer para que você possa registrar cada um deles como:

  • Um gerenciador de respostas falha. Portanto, ele atualizará o token se receber um código de status HTTP 401.
  • Um interceptador para interceptar o cabeçalho Authorization em todas as solicitações.

Apps instalados

Exemplo de código usando a API Books:

using System;
using System.IO;
using System.Threading;
using System.Threading.Tasks;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Books.v1;
using Google.Apis.Books.v1.Data;
using Google.Apis.Services;
using Google.Apis.Util.Store;

namespace Books.ListMyLibrary
{
    /// <summary>
    /// Sample which demonstrates how to use the Books API.
    /// https://developers.google.com/books/docs/v1/getting_started
    /// <summary>
    internal class Program
    {
        [STAThread]
        static void Main(string[] args)
        {
            Console.WriteLine("Books API Sample: List MyLibrary");
            Console.WriteLine("================================");
            try
            {
                new Program().Run().Wait();
            }
            catch (AggregateException ex)
            {
                foreach (var e in ex.InnerExceptions)
                {
                    Console.WriteLine("ERROR: " + e.Message);
                }
            }
            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }

        private async Task Run()
        {
            UserCredential credential;
            using (var stream = new FileStream("client_secrets.json", FileMode.Open, FileAccess.Read))
            {
                credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
                    GoogleClientSecrets.Load(stream).Secrets,
                    new[] { BooksService.Scope.Books },
                    "user", CancellationToken.None, new FileDataStore("Books.ListMyLibrary"));
            }

            // Create the service.
            var service = new BooksService(new BaseClientService.Initializer()
                {
                    HttpClientInitializer = credential,
                    ApplicationName = "Books API Sample",
                });

            var bookshelves = await service.Mylibrary.Bookshelves.List().ExecuteAsync();
            ...
        }
    }
}
  
  • Neste exemplo de código, uma nova instância de UserCredential é criada chamando o método GoogleWebAuthorizationBroker.AuthorizeAsync. Esse método estático obtém o seguinte:

    • A chave secreta do cliente (ou um fluxo para a chave secreta do cliente).
    • Os escopos necessários.
    • É o identificador do usuário.
    • O token de cancelamento para uma operação.
    • Um repositório de dados opcional. Se o armazenamento de dados não for especificado, o padrão será um FileDataStore com uma pasta Google.Apis.Auth padrão. A pasta será criada em Environment.SpecialFolder.ApplicationData.
  • O UserCredential retornado por esse método é definido como um HttpClientInitializer no BooksService (usando o inicializador). Conforme explicado acima, UserCredential implementa um inicializador de cliente HTTP.

  • No exemplo de código acima, as informações da chave secreta do cliente são carregadas de um arquivo, mas também é possível fazer o seguinte:

    credential = await GoogleWebAuthorizationBroker.AuthorizeAsync(
        new ClientSecrets
        {
            ClientId = "PUT_CLIENT_ID_HERE",
            ClientSecret = "PUT_CLIENT_SECRETS_HERE"
        },
        new[] { BooksService.Scope.Books },
        "user",
        CancellationToken.None,
        new FileDataStore("Books.ListMyLibrary"));
          

Confira nossa amostra de Livros.

Aplicativos da Web (ASP.NET Core 3)

As APIs do Google são compatíveis com OAuth 2.0 para aplicativos de servidor da Web.

Google.Apis.Auth.AspNetCore3 é a biblioteca recomendada para a maioria dos cenários de OAuth 2.0 baseados no Google em aplicativos ASP.NET Core 3. Ela implementa um gerenciador de autenticação OpenIdConnect específico do Google. Ele oferece suporte a autenticação incremental e define um IGoogleAuthProvider injetável para fornecer credenciais do Google que podem ser usadas com as APIs do Google.

Esta seção descreve como configurar e usar Google.Apis.Auth.AspNetCore3. O código mostrado aqui é baseado em Google.Apis.Auth.AspNetCore3.IntegrationTests, que é um aplicativo padrão ASP.NET Core 3 totalmente funcional.

Se você quiser acompanhar esta documentação como um tutorial, precisará do seu próprio aplicativo ASP.NET Core 3 e concluir essas etapas como pré-requisito.

Pré-requisitos

  • Instale o pacote Google.Apis.Auth.AspNetCore3.
  • Estamos usando a API Google Drive, então você também precisará instalar o pacote Google.Apis.Drive.v3.
  • Crie um projeto do Google Cloud se você ainda não tiver um. Para isso, siga estas instruções. Esse é o projeto com que seu app vai ser identificado.
  • A API Google Drive precisa estar ativada. Para ativar as APIs, siga estas instruções.
  • Crie credenciais de autorização que identificarão seu app para o Google. Siga estas instruções para criar credenciais de autorização e fazer o download do arquivo client_secrets.json. Dois destaques:
    • O tipo das credenciais precisa ser aplicativo da Web.
    • Para executar esse app, o único URI de redirecionamento que você precisa adicionar é https://localhost:5001/signin-oidc.

Configure o aplicativo para usar Google.Apis.Auth.AspNetCore3

Google.Apis.Auth.AspNetCore3 está configurado na classe Startup ou em uma alternativa semelhante que você esteja usando. Os snippets a seguir são extraídos de Startup.cs no projeto Google.Apis.Auth.AspNetCore3.IntegrationTests.

  • Adicione o seguinte usando a diretiva para seu arquivo Startup.cs.
    using Google.Apis.Auth.AspNetCore3;
  • No método Startup.ConfigureServices, adicione o seguinte código, troque os marcadores de posição chave secreta do cliente e ID do cliente com os valores contidos no arquivo client_secrets.json. É possível carregar esses valores diretamente do arquivo JSON ou armazená-los de qualquer outra maneira segura. Confira o método ClientInfo.Load no projeto Google.Apis.Auth.AspNetCore3.IntegrationTests para um exemplo de como carregar esses valores diretamente do arquivo JSON.
    public void ConfigureServices(IServiceCollection services)
    {
        ...
    
        // This configures Google.Apis.Auth.AspNetCore3 for use in this app.
        services
            .AddAuthentication(o =>
            {
                // This forces challenge results to be handled by Google OpenID Handler, so there's no
                // need to add an AccountController that emits challenges for Login.
                o.DefaultChallengeScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // This forces forbid results to be handled by Google OpenID Handler, which checks if
                // extra scopes are required and does automatic incremental auth.
                o.DefaultForbidScheme = GoogleOpenIdConnectDefaults.AuthenticationScheme;
                // Default scheme that will handle everything else.
                // Once a user is authenticated, the OAuth2 token info is stored in cookies.
                o.DefaultScheme = CookieAuthenticationDefaults.AuthenticationScheme;
            })
            .AddCookie()
            .AddGoogleOpenIdConnect(options =>
            {
                options.ClientId = {YOUR_CLIENT_ID};
                options.ClientSecret = {YOUR_CLIENT_SECRET};
            });
    }
          
  • No método Startup.Configure, adicione ao pipeline os componentes de middleware de autenticação e autorização do ASP.NET Core 3, além de redirecionamentos HTTPS:
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
        app.UseHttpsRedirection();
        ...
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        ...
    }
          

Usar a credencial do usuário para acessar as APIs do Google em nome dele

Agora está tudo pronto para adicionar métodos de ação aos seus controladores que exigem uma credencial de usuário para acessar as APIs do Google em nome deles. O snippet a seguir mostra como listar os arquivos na conta do Google Drive do usuário autenticado. Observe duas coisas principalmente:

  • O usuário não só precisa ser autenticado, como também precisa ter concedido o escopo https://www.googleapis.com/auth/drive.readonly ao aplicativo, que você especifica por meio do atributo GoogleScopedAuthorize.
  • Estamos usando o mecanismo de injeção de dependência padrão do ASP.NET Core 3 para receber uma IGoogleAuthProvider que será usada para receber as credenciais do usuário.

O código:

  • Primeiro, adicione o seguinte usando diretivas ao seu controlador.
    using Google.Apis.Auth.AspNetCore3;
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Drive.v3;
    using Google.Apis.Services;
          
  • Adicione a ação do controlador da seguinte maneira (e a acompanhe com uma visualização simples que recebe um modelo IList<string>):
    /// <summary>
    /// Lists the authenticated user's Google Drive files.
    /// Specifying the <see cref="GoogleScopedAuthorizeAttribute"> will guarantee that the code
    /// executes only if the user is authenticated and has granted the scope specified in the attribute
    /// to this application.
    /// </summary>
    /// <param name="auth">The Google authorization provider.
    /// This can also be injected on the controller constructor.</param>
    [GoogleScopedAuthorize(DriveService.ScopeConstants.DriveReadonly)]
    public async Task<IActionResult> DriveFileList([FromServices] IGoogleAuthProvider auth)
    {
        GoogleCredential cred = await auth.GetCredentialAsync();
        var service = new DriveService(new BaseClientService.Initializer
        {
            HttpClientInitializer = cred
        });
        var files = await service.Files.List().ExecuteAsync();
        var fileNames = files.Files.Select(x => x.Name).ToList();
        return View(fileNames);
    }
          

E esses são os princípios básicos. Confira HomeController.cs do projeto Google.Apis.Auth.AspNetCore3.IntegrationTests para saber como:

  • Apenas autenticação de usuário, sem escopos específicos
  • Funcionalidade de logout
  • Autorização incremental por código. O snippet acima mostra autorização incremental por meio de atributos.
  • Examinar escopos concedidos no momento
  • Examinar os tokens de acesso e de atualização
  • Forçar a atualização do token de acesso. Observe que você não precisa fazer isso por conta própria, uma vez que o Google.Apis.Auth.AspNetCore3 detectará se o token de acesso expirou ou está próximo de expirar e o atualizará automaticamente.

Conta de serviço

As APIs do Google também são compatíveis com contas de serviço. Ao contrário do cenário em que um aplicativo cliente solicita acesso aos dados de um usuário final, as contas de serviço fornecem acesso aos dados do próprio aplicativo cliente.

O aplicativo cliente assina a solicitação de token de acesso usando uma chave privada salva no Console de APIs do Google. Depois de criar um novo ID do cliente, escolha um tipo de aplicativo "Conta de serviço" e faça o download da chave privada. Confira nossa amostra de conta de serviço usando a API Google Plus.

using System;
using System.Security.Cryptography.X509Certificates;

using Google.Apis.Auth.OAuth2;
using Google.Apis.Plus.v1;
using Google.Apis.Plus.v1.Data;
using Google.Apis.Services;

namespace Google.Apis.Samples.PlusServiceAccount
{
    /// <summary>
    /// This sample demonstrates the simplest use case for a Service Account service.
    /// The certificate needs to be downloaded from the Google API Console
    /// <see cref="https://console.cloud.google.com/">
    ///   "Create another client ID..." -> "Service Account" -> Download the certificate,
    ///   rename it as "key.p12" and add it to the project. Don't forget to change the Build action
    ///   to "Content" and the Copy to Output Directory to "Copy if newer".
    /// </summary>
    public class Program
    {
        // A known public activity.
        private static String ACTIVITY_ID = "z12gtjhq3qn2xxl2o224exwiqruvtda0i";

        public static void Main(string[] args)
        {
            Console.WriteLine("Plus API - Service Account");
            Console.WriteLine("==========================");

            String serviceAccountEmail = "SERVICE_ACCOUNT_EMAIL_HERE";

            var certificate = new X509Certificate2(@"key.p12", "notasecret", X509KeyStorageFlags.Exportable);

            ServiceAccountCredential credential = new ServiceAccountCredential(
               new ServiceAccountCredential.Initializer(serviceAccountEmail)
               {
                   Scopes = new[] { PlusService.Scope.PlusMe }
               }.FromCertificate(certificate));

            // Create the service.
            var service = new PlusService(new BaseClientService.Initializer()
            {
                HttpClientInitializer = credential,
                ApplicationName = "Plus API Sample",
            });

            Activity activity = service.Activities.Get(ACTIVITY_ID).Execute();
            Console.WriteLine("  Activity: " + activity.Object.Content);
            Console.WriteLine("  Video: " + activity.Object.Attachments[0].Url);

            Console.WriteLine("Press any key to continue...");
            Console.ReadKey();
        }
    }
}

O exemplo de código acima cria um ServiceAccountCredential. Os escopos necessários são definidos e há uma chamada para FromCertificate, que carrega a chave privada do X509Certificate2 especificado. Como em todos os outros exemplos de código, a credencial é definida como HttpClientInitializer.