Usar proveedores OAuth con MVC 4

por Tom FitzMacken

En este tutorial se muestra cómo crear una aplicación web ASP.NET MVC 4 que permite a los usuarios iniciar sesión con credenciales de un proveedor externo, como Facebook, Twitter, Microsoft o Google, y después integrar algunas de las funciones de esos proveedores en la aplicación web. Para simplificar, este tutorial se centra en trabajar con credenciales de Facebook.

Para usar credenciales externas en una aplicación web ASP.NET MVC 5, vea Creación de una aplicación ASP.NET MVC 5 con Facebook y Google OAuth2 y el inicio de sesión OpenID.

La habilitación de estas credenciales en los sitios web proporciona una ventaja significativa porque millones de usuarios ya tienen cuentas con estos proveedores externos. Estos usuarios pueden estar más inclinados a registrarse en el sitio si no tienen que crear y recordar un nuevo conjunto de credenciales. Además, después de que un usuario haya iniciado sesión en uno de estos proveedores, puede incorporar operaciones sociales del proveedor.

Qué va a crear

En este tutorial hay dos objetivos principales:

  1. Permitir que un usuario inicie sesión con credenciales de un proveedor de OAuth.
  2. Recuperar la información de la cuenta del proveedor e integrarla con el registro de cuentas del sitio.

Aunque los ejemplos de este tutorial se centran en el uso de Facebook como proveedor de autenticación, puede modificar el código para usar cualquiera de los proveedores. Los pasos para implementar cualquier proveedor son muy similares a los que verá en este tutorial. Solo observará diferencias significativas al realizar llamadas directas al conjunto de API del proveedor.

Requisitos previos

Or

Además, en este tema se supone que tiene conocimientos básicos sobre ASP.NET MVC y Visual Studio. Si necesita una introducción a ASP.NET MVC 4, vea Introducción a ASP.NET MVC 4.

Creación del proyecto

En Visual Studio, cree una aplicación web ASP.NET MVC 4 y asígnele el nombre "OAuthMVC". Puede seleccionar como destino .NET Framework 4.5 o 4.

create project

En la ventana Nuevo proyecto de ASP.NET MVC 4, seleccione Aplicación de Internet y deje Razor como motor de vistas.

select Internet Application

Habilitación de un proveedor

Al crear una aplicación web MVC 4 con la plantilla Aplicación de Internet, el proyecto se crea con un archivo denominado AuthConfig.cs en la carpeta App_Start.

AuthConfig file

El archivo AuthConfig contiene código para registrar clientes para proveedores de autenticación externos. De manera predeterminada, este código tiene comentarios, por lo que ninguno de los proveedores externos está habilitado.

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        // To let users of this site log in using their accounts from other sites such as Microsoft, Facebook, and Twitter,
        // you must update this site. For more information visit https://go.microsoft.com/fwlink/?LinkID=252166

        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

Debe quitar los comentarios de este código para usar el cliente de autenticación externa. Quite solo los comentarios de los proveedores que quiera incluir en el sitio. En este tutorial, solo habilitará las credenciales de Facebook.

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        OAuthWebSecurity.RegisterFacebookClient(
            appId: "",
            appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();        
    }
}

Observe que, en el ejemplo anterior, el método incluye cadenas vacías para los parámetros de registro. Si intenta ejecutar la aplicación ahora, la aplicación inicia una excepción de argumento porque no se permiten cadenas vacías para los parámetros. A fin de proporcionar valores válidos, debe registrar el sitio web con los proveedores externos, como se muestra en la sección siguiente.

Registro con un proveedor externo

Para autenticar a los usuarios con credenciales de un proveedor externo, debe registrar el sitio web con el proveedor. Al registrar el sitio, recibirá los parámetros (como la clave o el id. y el secreto) que debe incluir al registrar el cliente. Debe tener una cuenta con los proveedores que quiera usar.

En este tutorial no se muestran todos los pasos que debe realizar para registrarse con estos proveedores. Normalmente, los pasos no son difíciles. Para registrar correctamente el sitio, siga las instrucciones proporcionadas en esos sitios. Para empezar con el registro del sitio, vea el sitio para desarrolladores para:

Al registrar el sitio con Facebook, puede proporcionar "localhost" para el dominio del sitio y "http://localhost/" para la dirección URL, como se muestra en la imagen siguiente. El uso de localhost funciona con la mayoría de los proveedores, pero en la actualidad no funciona con el proveedor Microsoft. Para el proveedor Microsoft, debe incluir una dirección URL de sitio web válida.

register site

En la imagen anterior, se han quitado los valores del id. de la aplicación, el secreto de la aplicación y el correo electrónico de contacto. Cuando realmente registre el sitio, esos valores estarán presentes. Querrá anotar los valores de id. de aplicación y secreto de aplicación, ya que los agregará a la aplicación.

Creación de usuarios de prueba

Si no le importa usar una cuenta de Facebook existente para probar el sitio, puede omitir esta sección.

Puede crear fácilmente usuarios de prueba para la aplicación en la página de administración de aplicaciones de Facebook. Puede usar estas cuentas de prueba para iniciar sesión en el sitio. Para crear usuarios de prueba, haga clic en el vínculo Roles del panel de navegación de la izquierda y haga clic en el vínculo Crear.

create test users

El sitio de Facebook crea automáticamente el número de cuentas de prueba que solicite.

Adición del identificador y el secreto de la aplicación desde el proveedor

Ahora que ha recibido el identificador y el secreto de Facebook, vuelva al archivo AuthConfig y agréguelos como valores de parámetro. Los valores que se muestran a continuación no son valores reales.

public static class AuthConfig
{
    public static void RegisterAuth()
    {
        //OAuthWebSecurity.RegisterMicrosoftClient(
        //    clientId: "",
        //    clientSecret: "");

        //OAuthWebSecurity.RegisterTwitterClient(
        //    consumerKey: "",
        //    consumerSecret: "");

        //OAuthWebSecurity.RegisterFacebookClient(
        //    appId: "",
        //    appSecret: "");

        //OAuthWebSecurity.RegisterGoogleClient();
    }
}

Inicio de sesión con credenciales externas

Es todo lo que tiene que hacer para habilitar las credenciales externas en el sitio. Ejecute la aplicación y haga clic en el vínculo de inicio de sesión en la esquina superior derecha. La plantilla reconoce automáticamente que ha registrado Facebook como proveedor e incluye un botón para el proveedor. Si registra varios proveedores, se incluye automáticamente un botón para cada uno.

external login

En este tutorial no se explica cómo personalizar los botones de inicio de sesión para los proveedores externos. Para obtener esa información, vea Personalización de la interfaz de usuario de inicio de sesión al usar OAuth/OpenID.

Haga clic en el botón Facebook para iniciar sesión con las credenciales de Facebook. Al seleccionar uno de los proveedores externos, se le redirigirá a ese sitio y ese servicio le pedirá que inicie sesión.

En la imagen siguiente se muestra la pantalla de inicio de sesión de Facebook. Se muestra que usa la cuenta de Facebook para iniciar sesión en un sitio denominado oauthmvcexample.

facebook authentication

Después de iniciar sesión con las credenciales de Facebook, una página informa al usuario de que el sitio tendrá acceso a información básica.

request permission

Después de seleccionar Ir a la aplicación, el usuario debe registrarse en el sitio. En la imagen siguiente se muestra la página de registro después de que un usuario haya iniciado sesión con las credenciales de Facebook. Normalmente, el nombre de usuario se rellena previamente con un nombre del proveedor.

Screenshot shows a Register page where you can associate your Facebook account with this app.

Haga clic en Registrar para completar el registro. Cierre el explorador.

Puede ver que la nueva cuenta se ha agregado a la base de datos. En el Explorador de servidores, abra la base de datos DefaultConnection y abra la carpeta Tables.

database tables

Haga clic con el botón derecho en la tabla UserProfile y seleccione Mostrar datos de tabla.

show data

Verá la nueva cuenta que ha agregado. Examine los datos de la tabla webpage_OAuthMembership. Verá más datos relacionados con el proveedor externo para la cuenta que acaba de agregar.

Si solo quiere habilitar la autenticación externa, ha terminado. Pero puede integrar aún más información del proveedor en el nuevo proceso de registro de usuarios, como se muestra en las secciones siguientes.

Creación de modelos para información adicional del usuario

Como ha observado en las secciones anteriores, no es necesario recuperar información adicional para que el registro de la cuenta integrada funcione. Pero la mayoría de los proveedores externos pasan información adicional sobre el usuario. En las secciones siguientes se muestra cómo conservar esa información y guardarla en una base de datos. En concreto, conservará los valores del nombre completo del usuario, el URI de la página web personal del usuario y un valor que indica si Facebook ha verificado la cuenta.

Usará Migraciones de Code First a fin de agregar una tabla para almacenar información de usuario adicional. Va a agregar la tabla a una base de datos existente, por lo que primero deberá crear una instantánea de la base de datos actual. Al crear una instantánea de la base de datos actual, puede crear posteriormente una migración que contenga solo la nueva tabla. Para crear una instantánea de la base de datos actual:

  1. Abra la Consola del administrador de paquetes
  2. Ejecute el comando enable-migrations
  3. Ejecute el comando add-migration initial –IgnoreChanges
  4. Ejecute el comando update-database

Ahora, agregará las nuevas propiedades. En la carpeta Models, abra el archivo AccountModels.cs y busque la clase RegisterExternalLoginModel. La clase RegisterExternalLoginModel contiene valores que proceden del proveedor de autenticación. Agregue propiedades denominadas FullName y Link, como se resalta a continuación.

public class RegisterExternalLoginModel
{
    [Required]
    [Display(Name = "User name")]
    public string UserName { get; set; }

    public string ExternalLoginData { get; set; }

    [Display(Name = "Full name")]
    public string FullName { get; set; }

    [Display(Name = "Personal page link")]
    public string Link { get; set; }
}

También en AccountModels.cs, agregue una nueva clase denominada ExtraUserInformation. Esta clase representa la tabla que se creará en la base de datos.

[Table("ExtraUserInformation")]
public class ExternalUserInformation
{
    public int Id { get; set; }
    public int UserId { get; set; }
    public string FullName { get; set; }
    public string Link { get; set; }
    public bool? Verified { get; set; }
}

En la clase UsersContext, agregue el código resaltado siguiente para crear una propiedad DbSet para la nueva clase.

public class UsersContext : DbContext
{
    public UsersContext()
        : base("DefaultConnection")
    {
    }

    public DbSet<UserProfile> UserProfiles { get; set; }
    public DbSet<ExternalUserInformation> ExternalUsers { get; set; }
}

Ahora ya puede crear la tabla. Vuelva a abrir la consola del Administrador de paquetes y esta vez:

  1. Ejecute el comando add-migration AddExtraUserInformation
  2. Ejecute el comando update-database

Ahora la tabla existe en la base de datos.

Recuperación de los datos adicionales

Hay dos maneras de recuperar datos de usuario adicionales. La primera manera es conservar los datos de usuario que se pasan de forma predeterminada durante la solicitud de autenticación. La segunda forma consiste en llamar específicamente a la API del proveedor y solicitar más información. Facebook pasa automáticamente los valores para FullName y Link. Un valor que indica si Facebook ha verificado la cuenta se recupera mediante una llamada a la API de Facebook. En primer lugar, rellenará los valores de FullName y Link y, después, obtendrá el valor verificado.

Para recuperar los datos de usuario adicionales, abra el archivo AccountController.cs de la carpeta Controllers.

Este archivo contiene la lógica de registro y administración de cuentas. En concreto, observe los métodos ExternalLoginCallback y ExternalLoginConfirmation. Dentro de estos métodos, puede agregar código para personalizar las operaciones de inicio de sesión externos para la aplicación. La primera línea del método ExternalLoginCallback contiene lo siguiente:

AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
    Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));

Los datos de usuario adicionales se devuelven en la propiedad ExtraData del objeto AuthenticationResult que se devuelve del método VerifyAuthentication. El cliente de Facebook contiene los siguientes valores en la propiedad ExtraData:

  • id
  • name
  • vínculo
  • gender
  • accesstoken

Otros proveedores tendrán datos similares pero ligeramente diferentes en la propiedad ExtraData.

Si el usuario es nuevo en el sitio, recuperará algunos de los datos adicionales y los pasará a la vista de confirmación. El último bloque de código del método solo se ejecuta si el usuario es nuevo en el sitio. Reemplace la línea siguiente:

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel 
{ 
    UserName = result.UserName, 
    ExternalLoginData = loginData 
});

por esta otra:

return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
{
    UserName = result.UserName,
    ExternalLoginData = loginData,
    FullName = result.ExtraData["name"],
    Link = result.ExtraData["link"]
});

Este cambio simplemente incluye valores para las propiedades FullName y Link.

En el método ExternalLoginConfirmation, modifique el código como se resalta a continuación para guardar la información adicional del usuario.

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}
else
{
    ModelState.AddModelError("UserName", "User name already exists. Please enter a different user name.");
}

Ajuste de la vista

Los datos de usuario adicionales que recupere del proveedor se mostrarán en la página de registro.

En la carpeta Views/Account, abra ExternalLoginConfirmation.cshtml. Debajo del campo existente para el nombre de usuario, agregue campos para FullName, Link y PictureLink.

<li>
    @Html.LabelFor(m => m.FullName)
    @Html.TextBoxFor(m => m.FullName)
</li>
<li>
    @Html.LabelFor(m => m.Link)
    @Html.TextBoxFor(m => m.Link)
</li>

Ya casi puede ejecutar la aplicación y registrar un nuevo usuario con la información adicional guardada. Debe tener una cuenta que todavía no esté registrada en el sitio. Puede usar otra cuenta de prueba o eliminar las filas de las tablas UserProfile y webpages_OAuthMembership para la cuenta que quiera reutilizar. Al eliminar esas filas, se asegurará de que la cuenta se vuelva a registrar.

Ejecute la aplicación y registre el nuevo usuario. Observe que esta vez la página de confirmación contiene más valores.

Screenshot shows where you can enter a user name and other information after associating a Facebook account with the app.

Después de completar el registro, cierre el explorador. Examine la base de datos para ver los nuevos valores de la tabla ExtraUserInformation.

Instalación del paquete NuGet para la API de Facebook

Facebook proporciona una API a la que puede llamar para realizar operaciones. Puede llamar a la API de Facebook mediante el envío de solicitudes HTTP o mediante la instalación de un paquete NuGet que facilite el envío de esas solicitudes. El uso de un paquete NuGet se muestra en este tutorial, pero la instalación del paquete NuGet no es esencial. En este tutorial se muestra cómo usar el paquete del SDK de C# de Facebook. Hay otros paquetes NuGet que ayudan a llamar a la API de Facebook.

En las ventanas Administrar paquetes NuGet, seleccione el paquete del SDK de C# de Facebook.

install package

Usará el SDK de C# de Facebook para llamar a una operación que necesita el token de acceso para el usuario. En la sección siguiente se muestra cómo obtener el token de acceso.

Recuperación del token de acceso

La mayoría de los proveedores externos pasan un token de acceso después de comprobar las credenciales del usuario. Este token de acceso es muy importante porque permite llamar a operaciones que solo están disponibles para los usuarios autenticados. Por tanto, recuperar y almacenar el token de acceso es esencial cuando se quiere proporcionar más funcionalidad.

En función del proveedor externo, el token de acceso puede ser válido solo durante un período de tiempo limitado. Para asegurarse de que tiene un token de acceso válido, lo recuperará cada vez que el usuario inicie sesión y lo almacenará como un valor de sesión en lugar de guardarlo en una base de datos.

En el método ExternalLoginCallback, el token de acceso también se devuelve en la propiedad ExtraData del objeto AuthenticationResult. Agregue el código resaltado a ExternalLoginCallback para guardar el token de acceso en el objeto Session. Este código se ejecuta cada vez que el usuario inicia sesión con una cuenta de Facebook.

[AllowAnonymous]
public ActionResult ExternalLoginCallback(string returnUrl)
{
    AuthenticationResult result = OAuthWebSecurity.VerifyAuthentication(
        Url.Action("ExternalLoginCallback", new { ReturnUrl = returnUrl }));
    if (!result.IsSuccessful)
    {
        return RedirectToAction("ExternalLoginFailure");
    }

    if (result.ExtraData.Keys.Contains("accesstoken"))
    {
        Session["facebooktoken"] = result.ExtraData["accesstoken"];
    }

    if (OAuthWebSecurity.Login(
        result.Provider, 
        result.ProviderUserId, 
        createPersistentCookie: false))
    {
        return RedirectToLocal(returnUrl);
    }

    if (User.Identity.IsAuthenticated)
    {
        // If the current user is logged in add the new account
        OAuthWebSecurity.CreateOrUpdateAccount(
            result.Provider,
            result.ProviderUserId, 
            User.Identity.Name);
        return RedirectToLocal(returnUrl);
    }
    else
    {
        // User is new, ask for their desired membership name
        string loginData = OAuthWebSecurity.SerializeProviderUserId(
            result.Provider, 
            result.ProviderUserId);
        ViewBag.ProviderDisplayName =
            OAuthWebSecurity.GetOAuthClientData(result.Provider).DisplayName;
        ViewBag.ReturnUrl = returnUrl;
        return View("ExternalLoginConfirmation", new RegisterExternalLoginModel
        {
            UserName = result.UserName,
            ExternalLoginData = loginData,
            FullName = result.ExtraData["name"],
            Link = result.ExtraData["link"]
        });    
    }
}

Aunque en este ejemplo se recupera un token de acceso de Facebook, puede recuperar el de cualquier proveedor externo mediante la misma clave denominada "accesstoken".

Cerrar sesión

El método LogOff predeterminado cierra la sesión del usuario en la aplicación, pero no cierra la sesión el usuario en el proveedor externo. Para cerrar la sesión del usuario en Facebook e impedir que el token persista después de que el usuario haya cerrado la sesión, puede agregar el código resaltado siguiente al método LogOff en AccountController.

[HttpPost]
[ValidateAntiForgeryToken]
public ActionResult LogOff()
{
    WebSecurity.Logout();
    if (Session["facebooktoken"] != null)
    {
        var fb = new Facebook.FacebookClient();
        string accessToken = Session["facebooktoken"] as string;
        var logoutUrl = fb.GetLogoutUrl(new { access_token = accessToken, next = "http://localhost:39852/" });

        Session.RemoveAll();
        return Redirect(logoutUrl.AbsoluteUri);
    }

    return RedirectToAction("Index", "Home");
}

El valor que proporcione en el parámetro next es la dirección URL que se usará después de que el usuario haya cerrado sesión en Facebook. Al probar en el equipo local, tendría que proporcionar el número de puerto correcto para el sitio local. En producción, tendría que proporcionar una página predeterminada, como contoso.com.

Recuperación de información de usuario que necesita el token de acceso

Ahora que ha almacenado el token de acceso e instalado el paquete del SDK de C# de Facebook, puede usarlos de manera conjunta para solicitar información adicional del usuario de Facebook. En el método ExternalLoginConfirmation, cree una instancia de la clase FacebookClient y pase el valor del token de acceso. Solicite el valor de la propiedad verified para el usuario autenticado actual. La propiedad verified indica si Facebook ha validado la cuenta mediante otros medios, como el envío de un mensaje a un teléfono móvil. Guarde este valor en la base de datos.

if (user == null)
{
    // Insert name into the profile table
    UserProfile newUser = db.UserProfiles.Add(new UserProfile { UserName = model.UserName });
    db.SaveChanges();

    bool facebookVerified;

    var client = new Facebook.FacebookClient(Session["facebooktoken"].ToString());
    dynamic response = client.Get("me", new { fields = "verified" });
    if (response.ContainsKey("verified"))
    {
        facebookVerified = response["verified"];
    }
    else
    {
        facebookVerified = false;
    }

    db.ExternalUsers.Add(new ExternalUserInformation 
    { 
        UserId = newUser.UserId, 
        FullName = model.FullName, 
        Link = model.Link, 
        Verified = facebookVerified 
    });
    db.SaveChanges();

    OAuthWebSecurity.CreateOrUpdateAccount(provider, providerUserId, model.UserName);
    OAuthWebSecurity.Login(provider, providerUserId, createPersistentCookie: false);

    return RedirectToLocal(returnUrl);
}

Tendrá que eliminar de nuevo los registros de la base de datos del usuario o usar otra cuenta de Facebook.

Ejecute la aplicación y registre el nuevo usuario. Examine la tabla ExtraUserInformation para ver el valor de la propiedad Verified.

Conclusión

En este tutorial, ha creado un sitio que se integra con Facebook para la autenticación de usuarios y los datos de registro. Ha obtenido información sobre el comportamiento predeterminado configurado para la aplicación web MVC 4 y cómo personalizarlo.