OAuth 2.0

En este documento, se describe OAuth 2.0, cuándo usarlo, cómo obtener los ID de cliente y cómo usarlo con la biblioteca cliente de la API de Google para .NET.

Protocolo OAuth 2.0

OAuth 2.0 es el protocolo de autorización que usan las API de Google. Para conocer el protocolo, lee los siguientes vínculos:

Adquirir ID de cliente y secretos

Puedes obtener ID de cliente y secretos en la Consola de API de Google. Hay diferentes tipos de ID de cliente, por lo que debes asegurarte de obtener el tipo correcto para tu aplicación:

En cada uno de los siguientes fragmentos de código (excepto en el de la cuenta de servicio), debes descargar el secreto del cliente y almacenarlo como client_secrets.json en tu proyecto.

Credenciales

Credenciales de usuario

UserCredential es una clase de ayuda segura para subprocesos a fin de usar un token de acceso a fin de acceder a recursos protegidos. Por lo general, un token de acceso vence después de 1 hora. Luego de este período, recibirás un error si intentas usarlo.

UserCredential y AuthorizationCodeFlow se encargan de actualizar el token automáticamente, lo que simplemente significa obtener un nuevo token de acceso. Para ello, se usa un token de actualización de larga duración, que recibirás junto con el token de acceso si usas el parámetro access_type=offline durante el flujo de código de autorización.

En la mayoría de las aplicaciones, se recomienda almacenar el token de acceso y el token de actualización en el almacenamiento persistente. De lo contrario, deberás presentarle al usuario final una página de autorización en el navegador cada hora, ya que el token de acceso vence una hora después de haberlo recibido.

Para asegurarte de que los tokens de acceso y actualización se conserven, puedes proporcionar tu propia implementación de IDataStore o puedes usar una de las siguientes implementaciones que proporciona la biblioteca:

  • FileDataStore para .NET garantiza que la credencial sea persistente en un archivo.

ServiceAccountCredential

ServiceAccountCredential es similar a UserCredential, pero tiene un propósito diferente. Google OAuth 2.0 admite interacciones de servidor a servidor, como aquellas entre una aplicación web y Google Cloud Storage. La aplicación solicitante debe demostrar su propia identidad para obtener acceso a una API, y un usuario final no tiene que estar involucrado. ServiceAccountCredential almacena una clave privada, que se usa para firmar una solicitud a fin de obtener un nuevo token de acceso.

UserCredential y ServiceAccountCredential implementan IConfigurableHttpClientInitializer, por lo que puedes registrar cada uno de ellos de la siguiente manera:

  • Un controlador de respuestas incorrecto, por lo que actualizará el token si recibe un código de estado HTTP 401.
  • Un interceptor para interceptar el encabezado Authorization en cada solicitud.

Aplicaciones instaladas

Código de muestra con la API de Libros:

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();
            ...
        }
    }
}
  
  • En este código de muestra, se crea una nueva instancia de UserCredential mediante una llamada al método GoogleWebAuthorizationBroker.AuthorizeAsync. Este método estático obtiene lo siguiente:

    • El secreto del cliente (o una transmisión al secreto del cliente)
    • Los alcances obligatorios
    • El identificador del usuario.
    • El token de cancelación para cancelar una operación.
    • Un almacén de datos opcional. Si no se especifica el almacén de datos, el valor predeterminado es un FileDataStore con una carpeta Google.Apis.Auth predeterminada. La carpeta se crea en Environment.SpecialFolder.ApplicationData.
  • El UserCredential que muestra este método se establece como HttpClientInitializer en el BooksService (mediante el inicializador). Como se explicó anteriormente, UserCredential implementa un iniciador de cliente HTTP.

  • Ten en cuenta que, en el código de muestra anterior, la información secreta del cliente se carga desde un archivo, pero también puedes hacer lo siguiente:

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

Echa un vistazo a nuestra muestra de libros.

Aplicaciones web (ASP.NET Core 3)

Las API de Google admiten OAuth 2.0 para aplicaciones de servidor web.

Google.Apis.Auth.AspNetCore3 es la biblioteca recomendada para usar en la mayoría de las situaciones de OAuth 2.0 basadas en Google en aplicaciones de ASP.NET Core 3. Implementa un controlador de autenticación OpenIdConnect específico de Google. Admite la autenticación incremental y define un IGoogleAuthProvider inyectable para proporcionar credenciales de Google que se pueden usar con las API de Google.

En esta sección, se describe cómo configurar y usar Google.Apis.Auth.AspNetCore3. El código que se muestra aquí se basa en Google.Apis.Auth.AspNetCore3.IntegrationTests, que es una aplicación de ASP.NET Core 3 completamente funcional y estándar.

Si deseas seguir esta documentación como un instructivo, necesitarás tu propia aplicación de ASP.NET Core 3 y completar estos pasos como un requisito previo.

Requisitos previos

  • Instala el paquete Google.Apis.Auth.AspNetCore3.
  • Usamos la API de Google Drive, por lo que también deberás instalar el paquete Google.Apis.Drive.v3.
  • Crea un proyecto de Google Cloud si aún no tienes uno. Para hacerlo, sigue estas instrucciones. Este será el proyecto con el que se identificará tu app.
  • Asegúrate de habilitar la API de Google Drive. Para habilitar las API, sigue estas instrucciones.
  • Crea credenciales de autorización para identificar tu app ante Google. Sigue estas instrucciones para crear credenciales de autorización y descargar el archivo client_secrets.json. Dos aspectos destacados:
    • Ten en cuenta que el tipo de credenciales debe ser Aplicación web.
    • Para ejecutar esta app, el único URI de redireccionamiento que debes agregar es https://localhost:5001/signin-oidc.

Configura tu aplicación para usar Google.Apis.Auth.AspNetCore3

Google.Apis.Auth.AspNetCore3 está configurado en la clase Startup o en una alternativa similar que podrías usar. Los siguientes fragmentos se extraen de Startup.cs en el proyecto Google.Apis.Auth.AspNetCore3.IntegrationTests.

  • Agrega lo siguiente con la directiva a tu archivo Startup.cs.
    using Google.Apis.Auth.AspNetCore3;
  • En el método Startup.ConfigureServices, agrega el siguiente código y cambia los marcadores de posición del ID de cliente y del secreto del cliente por los valores que contiene el archivo client_secrets.json. Puedes cargar estos valores directamente desde el archivo JSON o puedes almacenarlos de cualquier otra manera segura. Observa el método ClientInfo.Load en el proyecto Google.Apis.Auth.AspNetCore3.IntegrationTests para ver un ejemplo sobre cómo cargar estos valores directamente desde el archivo 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};
            });
    }
          
  • En el método Startup.Configure, asegúrate de agregar componentes de autenticación y autorización de middleware de ASP.NET Core 3 a la canalización, así como redireccionamientos de HTTPS:
    public void Configure(IApplicationBuilder app, IWebHostEnvironment env)
    {
        ...
        app.UseHttpsRedirection();
        ...
    
        app.UseAuthentication();
        app.UseAuthorization();
    
        ...
    }
          

Use la credencial de usuario para acceder a las API de Google en su nombre

Ahora estás listo para agregar métodos de acción a tus controladores que requieran la credencial de usuario para acceder a las API de Google en su nombre. En el siguiente fragmento, se muestra cómo enumerar los archivos en la cuenta de Google Drive del usuario autenticado. Observe dos cosas principalmente:

  • El usuario no solo debe autenticarse, sino que también debe haber otorgado el permiso https://www.googleapis.com/auth/drive.readonly a la aplicación, que debes especificar mediante el atributo GoogleScopedAuthorize.
  • Usamos el mecanismo de inyección de dependencias estándar de ASP.NET Core 3 para recibir un IGoogleAuthProvider que usamos a fin de obtener las credenciales del usuario.

El código:

  • Primero, agrega lo siguiente mediante directivas a tu controlador.
    using Google.Apis.Auth.AspNetCore3;
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Drive.v3;
    using Google.Apis.Services;
          
  • Agrega la acción del controlador de la siguiente manera (y acompáñala con una vista simple que reciba un 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);
    }
          

Estos son los conceptos básicos. Puedes consultar HomeController.cs del proyecto Google.Apis.Auth.AspNetCore3.IntegrationTests para descubrir cómo puedes lograr lo siguiente:

  • Solo autenticación de usuario, sin alcances específicos
  • Funcionalidad de cierre de sesión
  • Autorización incremental mediante código Ten en cuenta que el fragmento anterior muestra una autorización incremental mediante atributos.
  • Examina los permisos otorgados actualmente
  • Examina los tokens de acceso y actualización
  • Fuerza la actualización del token de acceso. Ten en cuenta que no tienes que hacerlo tú mismo porque Google.Apis.Auth.AspNetCore3 detectará si el token de acceso venció o está cerca de vencer, y lo actualizará automáticamente.

Aplicaciones web (ASP.NET MVC)

Las API de Google admiten OAuth 2.0 para aplicaciones de servidor web. Para ejecutar correctamente el siguiente código, primero debes agregar un URI de redireccionamiento a tu proyecto en la Consola de API de Google. Dado que usarás FlowMetadata y su configuración predeterminada, establece el URI de redireccionamiento en your_site/AuthCallback/IndexAsync.

A fin de encontrar los URI de redireccionamiento para tus credenciales de OAuth 2.0, haz lo siguiente:

  1. Abre la página Credenciales en la Consola de API.
  2. Si aún no lo hiciste, haz clic en Crear credenciales > ID de cliente de OAuth para crear tus credenciales de OAuth 2.0.
  3. Después de crear tus credenciales, consulta o edita las URL de redireccionamiento haciendo clic en el ID de cliente (para una aplicación web) en la sección ID de cliente de OAuth 2.0.

Después de crear un nuevo proyecto de aplicación web en tu IDE, agrega el paquete Google.Apis de NuGet correcto para Drive, YouTube o el otro servicio que desees usar. Luego, agrega el paquete Google.Apis.Auth.MVC. El siguiente código demuestra una aplicación ASP.NET MVC que consulta un servicio de API de Google.

  1. Agrega tu propia implementación de FlowMetadata.
    using System;
    using System.Web.Mvc;
    
    using Google.Apis.Auth.OAuth2;
    using Google.Apis.Auth.OAuth2.Flows;
    using Google.Apis.Auth.OAuth2.Mvc;
    using Google.Apis.Drive.v2;
    using Google.Apis.Util.Store;
    
    namespace Google.Apis.Sample.MVC4
    {
        public class AppFlowMetadata : FlowMetadata
        {
            private static readonly IAuthorizationCodeFlow flow =
                new GoogleAuthorizationCodeFlow(new GoogleAuthorizationCodeFlow.Initializer
                    {
                        ClientSecrets = new ClientSecrets
                        {
                            ClientId = "PUT_CLIENT_ID_HERE",
                            ClientSecret = "PUT_CLIENT_SECRET_HERE"
                        },
                        Scopes = new[] { DriveService.Scope.Drive },
                        DataStore = new FileDataStore("Drive.Api.Auth.Store")
                    });
    
            public override string GetUserId(Controller controller)
            {
                // In this sample we use the session to store the user identifiers.
                // That's not the best practice, because you should have a logic to identify
                // a user. You might want to use "OpenID Connect".
                // You can read more about the protocol in the following link:
                // https://developers.google.com/accounts/docs/OAuth2Login.
                var user = controller.Session["user"];
                if (user == null)
                {
                    user = Guid.NewGuid();
                    controller.Session["user"] = user;
                }
                return user.ToString();
    
            }
    
            public override IAuthorizationCodeFlow Flow
            {
                get { return flow; }
            }
        }
    }
          

    FlowMetadata es una clase abstracta que contiene tu propia lógica para recuperar el identificador de usuario y el IAuthorizationCodeFlow que usas.

    En el código de muestra anterior, se crea un GoogleAuthorizationCodeFlow nuevo con los permisos correctos, los secretos del cliente y el almacén de datos. Considera agregar tu propia implementación de IDataStore. Por ejemplo, puedes escribir una que use EntityFramework.

  2. Implementa tu propio controlador que usa un servicio de API de Google. En el siguiente ejemplo, se usa un DriveService:
    using System.Linq;
    using System.Threading;
    using System.Threading.Tasks;
    using System.Web.Mvc;
    
    using Google.Apis.Auth.OAuth2.Mvc;
    using Google.Apis.Drive.v2;
    using Google.Apis.Services;
    
    using Google.Apis.Sample.MVC4;
    
    namespace Google.Apis.Sample.MVC4.Controllers
    {
        public class HomeController : Controller
        {
            public async Task<ActionResult> IndexAsync(CancellationToken cancellationToken)
            {
                var result = await new AuthorizationCodeMvcApp(this, new AppFlowMetadata()).
                    AuthorizeAsync(cancellationToken);
    
                if (result.Credential != null)
                {
                    var service = new DriveService(new BaseClientService.Initializer
                        {
                            HttpClientInitializer = result.Credential,
                            ApplicationName = "ASP.NET MVC Sample"
                        });
    
                    // YOUR CODE SHOULD BE HERE..
                    // SAMPLE CODE:
                    var list = await service.Files.List().ExecuteAsync();
                    ViewBag.Message = "FILE COUNT IS: " + list.Items.Count();
                    return View();
                }
                else
                {
                    return new RedirectResult(result.RedirectUri);
                }
            }
        }
    }
          
  3. Implementa tu propio controlador de devolución de llamada. La implementación debería ser similar a la siguiente:
    using Google.Apis.Sample.MVC4;
    
    namespace Google.Apis.Sample.MVC4.Controllers
    {
        public class AuthCallbackController : Google.Apis.Auth.OAuth2.Mvc.Controllers.AuthCallbackController
        {
            protected override Google.Apis.Auth.OAuth2.Mvc.FlowMetadata FlowData
            {
                get { return new AppFlowMetadata(); }
            }
        }
    }
          

Cuenta de servicio

Las API de Google también admiten Cuentas de servicio. A diferencia de la situación en la que una aplicación cliente solicita acceso a los datos de un usuario final, las cuentas de servicio proporcionan acceso a los propios datos de la aplicación cliente.

Tu aplicación cliente firma la solicitud de un token de acceso con una clave privada descargada desde la Consola de API de Google. Después de crear un ID de cliente nuevo, debes elegir un tipo de aplicación “Cuenta de servicio” y, luego, descargar la clave privada. Echa un vistazo a nuestra muestra de cuentas de servicio que usan la API de 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();
        }
    }
}

El código de muestra anterior crea un ServiceAccountCredential. Se establecieron los alcances obligatorios y hay una llamada a FromCertificate, que carga la clave privada desde el X509Certificate2 determinado. Al igual que en todos los otros códigos de muestra, la credencial se establece como HttpClientInitializer.