Autenticación básica en ASP.NET Web API

por Mike Wasson

La autenticación básica se define en RFC 2617, autenticación HTTP: autenticación básica e implícita de acceso.

Inconvenientes

  • Las credenciales de usuario se envían en la solicitud.
  • Las credenciales se envían como texto no cifrado.
  • Las credenciales se envían con cada solicitud.
  • No hay forma de cerrar sesión, excepto finalizando la sesión del explorador.
  • Vulnerable a la falsificación de solicitudes entre sitios (CSRF); requiere medidas para prevenirla.

Ventajas

  • Estándar de Internet.
  • Compatible con todos los exploradores principales.
  • Protocolo relativamente sencillo.

La autenticación básica funciona de la manera siguiente:

  1. Si una solicitud requiere autenticación, el servidor devuelve 401 (no autorizado). La respuesta incluye un encabezado WWW-Authenticate, que indica que el servidor admite la autenticación básica.
  2. El cliente envía otra solicitud, con las credenciales de cliente en el encabezado de autorización. Las credenciales tienen el formato de cadena "name:password", codificada en base64. Las credenciales no están cifradas.

La autenticación básica se realiza dentro del contexto de un "dominio". El servidor incluye el nombre del dominio kerberos en el encabezado WWW-Authenticate. Las credenciales del usuario son válidas dentro de ese dominio. El servidor define el ámbito exacto de un dominio. Por ejemplo, puede definir varios dominios para crear particiones de recursos.

Diagram of basic authentication

Dado que las credenciales se envían sin cifrar, la autenticación básica solo es segura a través de HTTPS. Consulte Trabajar con SSL en API web.

La autenticación básica también es vulnerable a ataques CSRF. Una vez que el usuario introduce sus credenciales, el navegador las envía automáticamente en las siguientes peticiones al mismo dominio, mientras dure la sesión. Esto incluye solicitudes AJAX. Consulte Evitar ataques de falsificación de solicitudes entre sitios (CSRF).

Autenticación básica con IIS

IIS admite la autenticación básica, pero hay una advertencia: el usuario se autentica con sus credenciales de Windows. Esto significa que el usuario debe tener una cuenta en el dominio del servidor. En el caso de un sitio web orientado al público, normalmente querrá autenticarse en un proveedor de pertenencia ASP.NET.

Para habilitar la autenticación básica mediante IIS, establezca el modo de autenticación en "Windows" en web.config del proyecto de ASP.NET:

<system.web>
    <authentication mode="Windows" />
</system.web>

En este modo, IIS usa credenciales de Windows para autenticarse. Además, debe habilitar la autenticación básica en IIS. En el Administrador de IIS, vaya a Vista características, seleccione Autenticación y habilite Autenticación básica.

Image of I I S Manager dashboard

En el proyecto de API web, agregue el atributo [Authorize] para las acciones del controlador que necesiten autenticación.

Un cliente se autentica a sí mismo estableciendo el encabezado de autorización en la solicitud. Los clientes del explorador realizan este paso automáticamente. Los clientes que no utilicen navegador tendrán que configurar en encabezado.

Autenticación básica con pertenencia personalizada

Como se mencionó, la autenticación básica integrada en IIS usa credenciales de Windows. Esto significa que debe crear cuentas para los usuarios en el servidor de hospedaje. Pero para una aplicación de Internet, las cuentas de usuario normalmente se almacenan en una base de datos externa.

En el siguiente código se muestra cómo un módulo HTTP que realiza la autenticación básica. Puede conectar fácilmente un proveedor de pertenencia a ASP.NET reemplazando el método CheckPassword, que es un método ficticio en este ejemplo.

En Web API 2, debe considerar la posibilidad de escribir un filtro de autenticación o middleware de OWIN, en lugar de un módulo HTTP.

namespace WebHostBasicAuth.Modules
{
    public class BasicAuthHttpModule : IHttpModule
    {
        private const string Realm = "My Realm";

        public void Init(HttpApplication context)
        {
            // Register event handlers
            context.AuthenticateRequest += OnApplicationAuthenticateRequest;
            context.EndRequest += OnApplicationEndRequest;
        }

        private static void SetPrincipal(IPrincipal principal)
        {
            Thread.CurrentPrincipal = principal;
            if (HttpContext.Current != null)
            {
                HttpContext.Current.User = principal;
            }
        }

        // TODO: Here is where you would validate the username and password.
        private static bool CheckPassword(string username, string password)
        {
            return username == "user" && password == "password";
        }

        private static void AuthenticateUser(string credentials)
        {
            try
            {
                var encoding = Encoding.GetEncoding("iso-8859-1");
                credentials = encoding.GetString(Convert.FromBase64String(credentials));

                int separator = credentials.IndexOf(':');
                string name = credentials.Substring(0, separator);
                string password = credentials.Substring(separator + 1);

                if (CheckPassword(name, password))
                {
                    var identity = new GenericIdentity(name);
                    SetPrincipal(new GenericPrincipal(identity, null));
                }
                else
                {
                    // Invalid username or password.
                    HttpContext.Current.Response.StatusCode = 401;
                }
            }
            catch (FormatException)
            {
                // Credentials were not formatted correctly.
                HttpContext.Current.Response.StatusCode = 401;
            }
        }

        private static void OnApplicationAuthenticateRequest(object sender, EventArgs e)
        {
            var request = HttpContext.Current.Request;
            var authHeader = request.Headers["Authorization"];
            if (authHeader != null)
            {
                var authHeaderVal = AuthenticationHeaderValue.Parse(authHeader);

                // RFC 2617 sec 1.2, "scheme" name is case-insensitive
                if (authHeaderVal.Scheme.Equals("basic",
                        StringComparison.OrdinalIgnoreCase) &&
                    authHeaderVal.Parameter != null)
                {
                    AuthenticateUser(authHeaderVal.Parameter);
                }
            }
        }

        // If the request was unauthorized, add the WWW-Authenticate header 
        // to the response.
        private static void OnApplicationEndRequest(object sender, EventArgs e)
        {
            var response = HttpContext.Current.Response;
            if (response.StatusCode == 401)
            {
                response.Headers.Add("WWW-Authenticate",
                    string.Format("Basic realm=\"{0}\"", Realm));
            }
        }

        public void Dispose() 
        {
        }
    }
}

Para habilitar el módulo HTTP, agregue lo siguiente al archivo web.config en la sección system.webServer:

<system.webServer>
    <modules>
      <add name="BasicAuthHttpModule" 
        type="WebHostBasicAuth.Modules.BasicAuthHttpModule, YourAssemblyName"/>
    </modules>

Reemplace "YourAssemblyName" por el nombre del ensamblado (no incluida la extensión "dll").

Debe deshabilitar otros esquemas de autenticación, como Formularios o autenticación de Windows.