sábado, 1 de mayo de 2010

Login – Usando Password con Hash

 


Introducción


Este artículo intentara plantear la forma en que se puede crear un login que pueda ser usado tanto para un entorno Web, como para uno WinForms.

Se vera como mediante la definición de funcionalidad en una capa de Servicio el código de autenticación puede ser reutilizado, en realidad este nombre que le he dado a la capa podría llevar cualquier otro nombre, podrías ser directo la capa de Datos, o un Repository, la idea es mantener la técnica que permita reutilizar la funcionalidad.

Algo particular que encontraran en el ejemplo esta referido al password no se guarda de forma plana en la db, sino que por motivos de seguridad se aplica una función de hash.

La misma tiene una particularidad interesante, ya que el algoritmo solo se puede aplicar en un solo sentido, siempre obteniendo el hash de un texto o mensaje, pero nunca de un valor hash obtener el mensaje original.

Al aplicar el algoritmo hash a un mensaje, el resultado será siempre el mismo, por lo tanto esta técnica utilizada en el login permite que el password este siempre seguro. En el ejemplo verán como en el momento de login se aplica el algoritmo al la entrada del usuario y este valor se busca como password en la tabla, al asegurarse que un mismo texto produce el mismo resultado de hash, si se ingreso correctamente usuario y password este debe coincidí con el presente en la base de datos.


Definición de la capa de Servicio



El primer paso que explicaremos en el articulo es el código común que será reutilizado tanto por la capa web como winform, se trata de la denominada capa de servicio.

La misma básicamente se compone de una fachada estática, con métodos para autenticar o insertar un usuario, por supuesto la funcionalidad adicional para actualizar no se incluyo en el ejemplo, pero podrías agregarse si fuera necesario.

public static bool Autenticar(string usuario, string password)
{
    string sql = @"SELECT COUNT(*)
                      FROM Usuarios
                      WHERE NombreLogin = @nombre AND Password = @password";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
        conn.Open();

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("@nombre", usuario);

        string hash = Helper.EncodePassword(string.Concat(usuario, password));
        command.Parameters.AddWithValue("@password", hash);

        int count = Convert.ToInt32(command.ExecuteScalar());

        if (count == 0)
            return false;
        else
            return true;

    }
}
public static UsuarioEntity Insert(string nombre, string apellido, string nombreLogin, string password)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = nombre;
    usuario.Apellido = apellido;
    usuario.NombreLogin = nombreLogin;
    usuario.Password = password;

    return Insert(usuario);
}

public static UsuarioEntity Insert(UsuarioEntity usuario)
{

    string sql = @"INSERT INTO Usuarios (
                           Nombre
                          ,Apellido
                          ,NombreLogin
                          ,Password)
                      VALUES (
                            @Nombre, 
                            @Apellido, 
                            @NombreLogin,
                            @Password)
                    SELECT SCOPE_IDENTITY()";


    using (SqlConnection conn = new SqlConnection(ConfigurationManager.ConnectionStrings["default"].ToString()))
    {

        SqlCommand command = new SqlCommand(sql, conn);
        command.Parameters.AddWithValue("Nombre", usuario.Nombre);
        command.Parameters.AddWithValue("Apellido", usuario.Apellido);
        command.Parameters.AddWithValue("NombreLogin", usuario.NombreLogin);

        string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));
        command.Parameters.AddWithValue("Password", password);

        conn.Open();

        usuario.Id = Convert.ToInt32(command.ExecuteScalar());

        return usuario;
    }
}

El método importante en estos dos métodos es el siguiente:

string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));

Esta línea es la que arma el password real que se salvara en el campo de la tabla, como se observa para una seguridad aun mayor se une el nombre del usuario y el password, pasándolos luego por la función que aplica el hash.

internal class Helper
{
    public static string EncodePassword(string originalPassword)
    {
        SHA1 sha1 = new SHA1CryptoServiceProvider();

        byte[] inputBytes = (new UnicodeEncoding()).GetBytes(originalPassword);
        byte[] hash = sha1.ComputeHash(inputBytes);

        return Convert.ToBase64String(hash);
    }
}

En el método Autenticar() como se había comentado no se obtiene el password que originalmente el usuario tienen, sino que se aplica el hash sobre los valores ingresados al momento de realizar el login, ejecutando el query con esta información como filtro.

Si el usuario y password ingresados en la autenticación son los correctos al aplicar el hash retornara la misma cadena que se tiene en la tabla, por lo tanto al ejecutar el query debería devolver el registro del usuario.


Autenticación desde una aplicación Winform



En el método Main() del archivo Program.cs se encontrara la llamada a el formulario que será responsable de al autenticación.

static class Program
{
    /// <summary>
    /// The main entry point for the application.
    /// </summary>
    [STAThread]
    static void Main()
    {
        Application.EnableVisualStyles();
        Application.SetCompatibleTextRenderingDefault(false);
        
        frmLogin frm = new frmLogin();
        frm.ShowDialog();

        if(frm.DialogResult == DialogResult.OK)
            Application.Run(new frmPrincipal());

    }
}

El formulario de login (frmLogin.cs), tendrá un código muy simple, solo tomara el usuario y password invocara al servicio y según el resultado retornara la respuesta al método Main() para que se abra o no el formulario principal.

private void btnAceptar_Click(object sender, EventArgs e)
{
    string nombre = txtNombre.Text;
    string password = txtPassword.Text;

    if (LoginService.Autenticar(nombre, password))
        this.DialogResult = DialogResult.OK;
    else
        this.DialogResult = DialogResult.Abort;

}

private void btnCancelar_Click(object sender, EventArgs e)
{
    this.DialogResult = DialogResult.Cancel;
}

Adicionalmente se agrego un formulario para la creación de nuevos usuarios, si bien en este no se ha programado la edición completa del usuario, o sea toda la funcionalidad de ABM, el Insert permitirá analizar como se registra un nuevo usuario y se guarda su password.

private void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtUsuario.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin= txtNombreLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    MessageBox.Show(string.Format("Se ha creado el usuario, ID: {0}", usuario.Id));
}

Nuevamente gracias a la funcionalidad encapsulada del Servicio, el código del formulario es muy simple.

 


Autenticación desde una aplicación Web



La autenticación en un entorno web es algo distinta a una aplicación Windows, en este caso se ha configurado en el web.config, las líneas necesarias para que todo el sitio este bajo una autenticación por medio de “Forms”

<authentication mode="Forms">
	<forms name="appNameAuth" path="/" loginUrl="frmLogin.aspx" defaultUrl="Default.aspx" protection="All" />
</authentication>
  
  <authorization>
    <deny users="?" />
  </authorization>

En el archivo de configuración del sitio se especifica cual será la pagina por defecto y cual la de login, de esta forma si no hay usuario autenticado, solo el sitio redirección a estas url.
El formulario web frmLogin.aspx tiene un código muy simple:

protected void ProcessLogin(object sender, EventArgs e)
{
    if (LoginService.Autenticar(txtUser.Text, txtPassword.Text))
    {
        FormsAuthentication.RedirectFromLoginPage(txtUser.Text, chkPersistLogin.Checked);
    }
    else
        ErrorMessage.InnerHtml = "<b>Usuario o contraseña incorrectos...</b> por favor re-ingrese las credenciales...";
}

Como se observa luego de la autenticación se hace uso de los métodos provistos por .net en el namespace System.Web.Security, para trabajar con al seguridad.
Aquí se esta indicando el nombre del usuario que paso la validación, y esto hace que solo el sitio se redireccione a la pagina marcada por defecto en el web.config.

En la pagina principal del sitio se ha agregado algo de código para mostrar el nombre del usuario autenticado, y dar la posibilidad de un logout.

protected void Page_Load(object sender, EventArgs e)
{
    Label1.Text = string.Format("Bienvenido al Sistema: {0}", Thread.CurrentPrincipal.Identity.Name); 
}

protected void Menu1_MenuItemClick(object sender, MenuEventArgs e)
{
    if (Menu1.SelectedValue == "Salir")
    {
        FormsAuthentication.SignOut();
        FormsAuthentication.RedirectToLoginPage();
    }
}

También se ha creado una pagina para crear nuevos usuarios (Usuarios.aspx)

protected void btnAceptar_Click(object sender, EventArgs e)
{
    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = txtNombre.Text;
    usuario.Apellido = txtApellido.Text;
    usuario.NombreLogin = txtLogin.Text;
    usuario.Password = txtPassword.Text;

    usuario = LoginService.Insert(usuario);

    ClearControls();
    lblMessage.InnerHtml = string.Format("Se ha creado el usuario, ID: {0}", usuario.Id);

}

protected void btnCancelar_Click(object sender, EventArgs e)
{
    Response.Redirect("~/Default.aspx");
}

private void ClearControls()
{
    txtNombre.Text="";
    txtApellido.Text="";
    txtLogin.Text="";
    txtPassword.Text="";
}

Código de ejemplo


Como requerimiento será necesario contar con al menos Sql Server Express 2008, para que pueda ejecutarse la aplicación.

La tabla de usuario ya posee un registro para ingresar la primera vez, la información es la siguiente:
Usuario: admin
Password: pass123

Para no tener dos base de datos iguales, se agrego un Build Event en el proyecto web para que copie la db desde el proyecto de Class Library a la carpeta App_Data del sitio Web.

Puede que a veces si se compila repetida veces la compilación lance un error al intentar copiar la db a la carpeta App_Data, cuya operación no pueda hacerlo porque el servicio de sql server la tenga loqueada, en ese caso simplemente ejecuten a pesar de este mensaje de error, ya que funcionara sin problemas.

[C#] 
[VB.NET] 

241 comentarios:

  1. muy buen tutorial, intentaré adaptarlo ahora a una base MySQL a ver que pasa. La duda que me queda es si a la tabla de usuarios puedo agregarle más columnas

    ResponderEliminar
  2. hola

    que bueno que resulte util

    si este diseño no esta cerrado para nada, las tablas como habras visto las cree yo con los campos que vi utiles para el ejemplo, pero si necesitas poner mas campos puedes hacerlo sin problemas.

    Ya sea que uses una db Sql Server, Access o MySql, no afecta, solo que no podrias usar el ejemplo de codigo de forma directa, deberas reemplazar el provider de Sql Server de ado.net por su equivamente en MySql, o sea donde dice SqlConnection, seguro usaras MySqlConection y asi con el resto de los objetos, pero la tecnica se mantiene sin cambios

    Es mas el tema del hash por ser un simple string tampoco se ve afectado.

    saludos

    ResponderEliminar
  3. Buenísimo, ahora a ponerme a programar y dps te cuento, muchísimas gracias!

    ResponderEliminar
  4. Hola, muy buen tutorial
    Tengo una duda,para winform
    si yo me conecto a oracle en mi cadena de conexion tengo el usuario y la contraseña y la base de datos, pero cada cierto tiempo necesito cambiar esta contraseña, entonces cuando el usuario se logea y verifica que ya caduco debe cambiar la contraseña pero al ser la contraseña de la cadea de conexion me da error, como se podría realizar esto? muchas gracias.

    ResponderEliminar
  5. hola asukitta

    El tema es que la contraseña de tu connection string no deberia porque cambiar, deberia ser fija.

    Lo que expone este articulo es como implementar la autenticacion de un usuario pero del negocio de tu aplicacion, no de la conexion a la base de datos.

    Se supone que la info de la cadena de conexion no deberia variar, porque es logico que esta en un string y no es simple modificar.

    Apunto a que no solo es dificil de cambiarlo en cada cliente que use la aplicacion, sino que tambien hay que sincronizar el cambio con Oracle.

    En sql server para esto casos se usa la seguridad integrada con windows, de esta forma no hace falta especificar un usuario y password en la cadena de conexion, se usa el mismo usuario que se autentico a Windows, cuando realizo el logon.

    mira este link:
    OracleConnection.ConnectionString (Propiedad)

    por ahi en Oracle tambien puedas implementar seguridad integrada.

    Connection strings for Oracle

    veras que usa:
    Data Source=MyOracleDB;Integrated Security=yes;

    Por ahi tambien puedas implementarlo si es que Oracle puede usuar a los usuario de un Dominio de Windows.

    saludos

    ResponderEliminar
  6. Leandro muy buen post, solo queria hacerte una consulta, en el caso de que quisiera hacer que en el form de login se tengan 3 intentos para ingresar la contraseña, como deberia quedar el codigo en el frmlogin, ya que por mas que intente luego de que se pulsa el botno aceptar se cierra este form. Saludos

    ResponderEliminar
  7. hola Fernando

    disculpa la tardanza en la respuesta

    se me ocurre que podria ser algo como esto:

    private int intentos = 0;

    private void btnAceptar_Click(object sender, EventArgs e)
    {
    string nombre = txtNombre.Text;
    string password = txtPassword.Text;

    if (LoginService.Autenticar(nombre, password))
    this.DialogResult = DialogResult.OK;
    else
    {
    if(intentos == 3)
    this.DialogResult = DialogResult.Abort;
    else
    {
    txtNombre.Text = "";
    txtPassword.Text = "";
    intentos++;
    }
    }

    }

    Como veras la variable intentos estara definida a nivel del la clase del formulario

    saludos

    ResponderEliminar
  8. Hola, Leandro.

    Muy útil tu post. Pero lo he visto un poquito tarde pues ya diseñé la parte del Login, pero lo hice también con HashCode, sólo que no usé MD5, sino que empleé la función GetHashCode de las cadenas. Algo así como:

    int nHash = this.txtPwd.Text.GetHashCode();

    Pero me queda preguntar si hay algún inconveniente en ello. Es decir, ¿debería cambiarlo?

    Muchas gracias!

    ResponderEliminar
  9. hola AlexLander

    En principio el resultado deberia ser el mismo.

    Solo que como veras en el articulo para otorgar algo mas de seguridad la clave es compuesta, o sea se cifra tanto el password, como el nombre del usuario.

    Ademas en el ejemplo no se aplica directo en un control Textbox, sino que se realiza en una clase de mapeo de datos, por lo tanto hay que recurrir a otra funcionalidad.

    Pero si en tu caso aplica bien como lo has usado, es valido.

    saludos

    ResponderEliminar
  10. Gracias Leandro, te lo has currado. Me viene muy bien tu trabajo, 2 libros y en ninguno tan claro como en tu artículo.

    Nuevamente gracias por tu esfuerzo!.

    ResponderEliminar
  11. estimado podria subir denuevo el cod de ejemplo... no puedo hacer funcionar...


    class LoginService
    {
    public static bool Autenticar(string usuario, string password)
    {
    string sql = @"SELECT COUNT(*)
    FROM Usuarios
    WHERE NombreLogin = @nombre AND Password = @password";


    using (SqlConnection conn = new SqlConnection("Data Source=ENZZO-PC\\SQLEXPRESS;Initial Catalog=ISW;Integrated Security=True"))
    {
    conn.Open();

    SqlCommand command = new SqlCommand(sql, conn);
    command.Parameters.AddWithValue("@nombre", usuario);

    string hash = Helper.EncodePassword(string.Concat(usuario, password));
    command.Parameters.AddWithValue("@password", hash);

    int count = Convert.ToInt32(command.ExecuteScalar());

    if (count == 0)
    return false;
    else
    return true;

    }
    }


    public static UsuarioEntity Insert(string nombre, string apellido, string nombreLogin, string password)
    {

    UsuarioEntity usuario = new UsuarioEntity();

    usuario.Nombre = nombre;
    usuario.Apellido = apellido;
    usuario.NombreLogin = nombreLogin;
    usuario.Password = password;

    return Insert(usuario);
    }

    public static UsuarioEntity Insert(UsuarioEntity usuario)
    {

    string sql = @"INSERT INTO Usuarios (
    Nombre
    ,Apellido
    ,NombreLogin
    ,Password)
    VALUES (
    @Nombre,
    @Apellido,
    @NombreLogin,
    @Password)
    SELECT SCOPE_IDENTITY()";


    using (SqlConnection conn = new SqlConnection("Data Source=ENZZO-PC\\SQLEXPRESS;Initial Catalog=ISW;Integrated Security=True"))
    {

    SqlCommand command = new SqlCommand(sql, conn);
    command.Parameters.AddWithValue("Nombre", usuario.Nombre);
    command.Parameters.AddWithValue("Apellido", usuario.Apellido);
    command.Parameters.AddWithValue("NombreLogin", usuario.NombreLogin);

    string password = Helper.EncodePassword(string.Concat(usuario.NombreLogin, usuario.Password));
    command.Parameters.AddWithValue("Password", password);

    conn.Open();

    usuario.Id = Convert.ToInt32(command.ExecuteScalar());

    return usuario;
    }
    }

    internal class Helper
    {
    public static string EncodePassword(string originalPassword)
    {
    MD5 md5 = MD5.Create();
    byte[] inputBytes = System.Text.Encoding.ASCII.GetBytes(originalPassword);
    byte[] hash = md5.ComputeHash(inputBytes);

    return BitConverter.ToString(hash).Replace("-", "");
    }
    }


    }


    me tira error en el UsuarioEntity usuario = new UsuarioEntity();

    ResponderEliminar
  12. hola Enzzo

    podrias poner cual es el mensaje de error que estas teniendo ?

    esto se produce cuando abres el proyecto que descargas de este ejemplo, o cuando estas adaptando el codigo a uno propio ?

    saludos

    ResponderEliminar
  13. Leandro que se necesitaria para aplicar ASP.NET MVC2 a tu ejemplo???

    ResponderEliminar
  14. Saludos

    ¿Como se podria recuperar la contraseña real? (sin codificaR)

    Gracias

    ResponderEliminar
  15. Hola Leandro, hoy me inscribí en MSDN y vi tu post. Mi duda es la siguiente: ¿Es mejor hacerlo de esta forma (encriptando la clave en la capa de Servicio) o conviene encriptarlo en una capa superior de forma que se transmita ya encriptado?
    Desde ya muchas gracias tanto por tu respuesta como por el tiempo que te tomas para crear ejemplos y responder dudas
    Saludos

    ResponderEliminar
  16. hola Richof

    Segun veo no deberia se muy distinto, solo cambiaria el lugar donde realizas la validacion siendo un controlador

    ASP.Net MVC Framework - Using Forms Authentication

    lo que si veo es que no podras usar es el
    FormsAuthentication.RedirectFromLoginPage()
    sino que deberas asignar el
    FormsAuthentication.SetAuthCookie()
    y luego hacer el RedirectToAction() a la apgina principal

    saludos

    ResponderEliminar
  17. hola Enrique

    La idea justamente es no poder recuperar el password ingresado, ya que algoritmo hash solo se aplica en una direccion.

    Se aplica la misma tecnica que usarias con Windows, en este cuando pierdes la clave no puede loguearte salvo que un admin la resetee, aqui es identico si el usuario pierde el password la idea es que en alguna pantalla alguien con perfil de admin la reasigne para que el usuario luego la cambie.

    Pero no se puede recuperar una vez aplicado el hash.

    saludos

    ResponderEliminar
  18. hola José

    La capa de Servicio seria la fachada al sistema, el punto de entrada a la logica del negocio, creo que es un lugar optimo para aplicar la logica de encriptacion.

    Sucede que la presentacion no podrias decidir si encriptar o no porque no es su responsabilidad, ademas en este caso no hay una comuniacion remota entre las capas (recuerda que son dll que estan en el \bin del sitio), estas estan en el servidor, lo que se envia de forma plana es la comunicion del postback, pero para segurizar esto deberias encriptar el canal usando SSL.

    saludos

    ResponderEliminar
  19. Hola Leandro, excelente post. Descargue el archivo que viene en VB y resulta que tengo Visual Basic 2010 y trabajo con SQL 2005. Ahora al abrir el archivo Login, el programa me dice que si quiero actualizar. Le dije que si y se tomo un tiempo. Ahora el unico detalle que tengo es que la base da datos database.mdf no funciona. No se que hacer. Como lo hago para que funcione esto? y luego para exportarlo a como yo trabajo como seria? muchas gracias por tu tiempo. Saludos

    ResponderEliminar
  20. Hola leandro muy buen post qe has ta el dia de hoy es util, yo utilizare este tipo de apicacion para qe un usuario registrado en la bd pueda acceder al siguiente form, el cual contendra una aplicacion qe guardara datos en la BD de mi servidor, mi pregunta es, qe archivos ASP/ASPX debO subir O me pOdrias recomendar un manual para esto, el servidor esta montado en ubuntu server 10.10, de antemano muchas gracias.

    ResponderEliminar
  21. hola Luis

    Te comento la mejor forma para saber que subir a un situio es suando las opcion de Publish que brinda el VS

    How to: Publish Web Sites (Visual Studio)

    en resumen usa esta opcion
    imagen

    a la cual le indicas un path fisico de tu disco rigido y alli dejara lo que tienes que publicar

    saludos

    ResponderEliminar
  22. Leandro una consulta...
    hecho el ejercicio que planteastes y sale bien...logro registrarme y logearme.. pero cuando quiero acceder de otro proyecto osea logearme no puedo! y eso que aplico el mismo metodo del hash..
    xq sera?? o solo me permite usar el hash en ese mismo proyecto
    gracias por la respuesta..

    ResponderEliminar
  23. hola Leonel

    Cuando te refieres a proyecto, apuntas al proyecto web o a otro del tipo class library, o es otro punto diferente al cual apuntas.

    El hash es solo un algoritmo de encritacion, si envias el texto correcto al aplciarlo ya sea desde un proyecto web o clas library este generara la misma salida como resultado, porque la libreria es la misma.

    Pero valida que la entrada de informacion es identica, veras alli que en el ejemplo no uso el passwaord solo para aplciar el hash, sino que uno mas de un valor, de esta forma se hace algo mas segura.

    Valida que al realziar la autenticacion envias la info correcta, sino no obntendras la misma salida.

    saludos

    ResponderEliminar
  24. gracias por la respuesta Leandro!
    bueno si es desktop lo que hecho es hacer yo mismo uno identico al que propones y si me salio, hasta ahi todo bien..pero cuando registro un usuario en tu modelo de ejemplo y luego quiero acceder del mismo que yo hecho,no logro logearme y viceversa, crees que sea por esto:

    cmd.Parameters.Add(new SqlParameter("@Nombre", SqlDbType.VarChar,25));
    cmd.Parameter["@Nombre"].Value=nom;

    yo use "Add" en remplazo de "AddWithValue", lo use al registrar y logear..
    gracias

    ResponderEliminar
  25. hola leandro tengo una pregunta estoy utilizando tu login pero solo quiero proteger una pagina aspx y no todas como lo puedo hacer... es decir tengo como 5 pestañas y solo quiero proteger la de estadisticas ???

    ResponderEliminar
  26. hola Leonel

    El que no puedas loguarte pueden ser por varios motivos
    el incorrecto pasaje de parametros podria ser uno, veo que defines puntualmente un varchar de 25, pero esto podria estar truncando el valor que envias, has probado usando el AddWithValue() con este tampoco funciona ?

    Recuerda que la constraseña utiliza una logica especial, o sea se une el nombre al password y se aplica el hash para guardar en la tabla, debes replciar esto mismo cuando quieras validar si es correcto el login ingresado

    saludos

    ResponderEliminar
  27. hola David

    Puedes indicar por medio de configuracion que carpetas o archivos estas libres de la seguridad definida

    Indicar elementos públicos o privados en un mismo sitio de ASP.NET

    Veras que definiendo el tag <location ..> puede indicar como aplicar la seguridad a determiandos sitios de tu aplicacion

    saludos

    ResponderEliminar
  28. Gracias me ha servido de mucho pero tengo otra pregunta...

    ¿como puede el usuario cambiar el paswoord, si lo perdio o solo por seguridad?

    creo que se puede recetear la contraseña para que no se pierda ese usuario... como se podria realizar eso

    gracias de antemano y disculpa tantas molestias

    ResponderEliminar
  29. hola David

    En realidad depende de como quieras aplicar el proceso, en el ejemplo si bien la contraseña aplica hash para aumentar la seguridad y no dejar a la vista el password, podrias en el reset tomar cualquier texto, aplica el hash y guardarlo en la tabla de usuario

    y como segundo paso tomas ese string que guardaste y lo envias por mail a la cuenta configurada para ese usuario, por supuesto deberas poner un campo de mail obligatorio para poder recuperar la constraseña que se genera sola

    para generar la nueva clave existen algoritmos random que genernal cadenas de string

    Generating Random Number and String in C#"

    cuando tengas ala cadena random del password que lo resetea, como comente aplica el has y lo guardas en la tabla, y luego el password sin encriptar lo envias por mail

    saludos

    ResponderEliminar
  30. Hola Leandro, use tu ejemplo y me fue de gran ayuda, pero tengo una duda, con este ejemplo existe forma de aplicar autenticacion de usuarios basada en roles usando HttpModules, lo que tengo desarrollado hasta ahora en cuestion de autenticacion es lo que tu posteaste, no se si deba seguir otro camino o con eso mismo pueda usar HttpModules, si me pudieras orientar te lo agradeceré mucho

    ResponderEliminar
  31. Leandro excelente Blog gracias por compartir tus conocimientos, tengo una consulta, necesito utilizar el coigo del usuario logeado para poder filtrar informacion que solo este relacionada con este usuario y asi por enede solo mostrarle informacion que este solo relacionada con el...

    de antemano gracias...

    ResponderEliminar
  32. hola YesyM

    te refieres a algo como esto

    Autenticación de Usuarios basada en Roles utilizando HTTPModules en ASP .NET

    como veras alli tambien se implememetnas bases propias de autenticacion

    en realidad la autenticacion dice si puede acceder al sitio o no, pero no indica a que pagina puedes ingresar

    esto podris hacerlo con modulos, o sino por medio de el sitemap

    Authenticate User by Roles in ASP.NET


    saludos

    ResponderEliminar
  33. hola Richard

    hay varias formas de hacerlo

    - una podria ser usando el mismo nombre de login, imagino este debe ser unico por usuario, por lo tanto como lo tienes en el

    Thread.CurrentPrincipal.Identity.Name

    podrias usarlo directo como filtro en la queries

    - la otra es que al momento del login recuperes el id del usaurio que se autentica y dejes esa info en una variable de session

    entonces tomarias de esta el id que usarias de filtro

    saludos

    ResponderEliminar
  34. Hola, me parece muy interesante su blog y este aporte, pero no puedo descargar el ejemplo. Me podría ayudar? Doy clic y me da error.

    ResponderEliminar
  35. Hola, me gusta su post y en especial necesito de esta aplicacion, quisiera descargarla pero me da error, me podria ayudar?

    ResponderEliminar
  36. hola francisco

    por un problema temporal con el hosting, las imagenes y archivos no se pueden descargar reemplaza el dominio ltuttini.com.ar por http://200.58.122.109/ltuttini.com.ar, pero este problema es temporal

    accede al blog con el IE para que te muestre los lugares dodne deberian estar las imagenes

    saludos

    ResponderEliminar
  37. Hola Leandro, me ha servido de maravilla el ejemplo pero al momento de ingresar un usuario nuevo me esta enviando un error, Falta punto y coma (;) al final de la instrucción SQL.
    Hice mi BD en access y se loguea perfectamente el usuario usando la funcion de Hash, pero si deceo agregar un nuevo usuario me lanza ese error.
    y uso la funcion siguiente:

    Public Shared Function Insert(usuario As UsuarioEntity) As UsuarioEntity


    Dim sql As String = "INSERT INTO Usuarios (" & _
    "Codigo_Usuario " & _
    ",Cedula " & _
    ",Nombre_Apellido " & _
    ",Email " & _
    ",Clave_Usuario) " & _
    "VALUES (" & _
    "@NombreLogin, " & _
    "@Cedula, " & _
    "@Nombre, " & _
    "@Correo, " & _
    "@Password) " & _
    "SELECT SCOPE_IDENTITY()"

    Using conn As New OleDbConnection(ConfigurationManager.ConnectionStrings("default").ToString())

    Dim command As New OleDbCommand(sql, conn)

    command.Parameters.AddWithValue("Codigo_Usuario", usuario.NombreLogin)
    command.Parameters.AddWithValue("Cedula", usuario.Cedula)
    command.Parameters.AddWithValue("Nombre_Apellido", usuario.Nombre)
    command.Parameters.AddWithValue("Email", usuario.Correo)

    Dim password As String = Helper.EncodePassword(String.Concat(usuario.NombreLogin, usuario.Password))
    command.Parameters.AddWithValue("Clave_Usuario", password)

    conn.Open()

    usuario.Id = Convert.ToInt32(command.ExecuteScalar())

    Return usuario
    End Using
    End Function

    ResponderEliminar
  38. hola
    no has probado usando

    Dim sql As String = "INSERT INTO Usuarios (" & _
    "Codigo_Usuario " & _
    ",Cedula " & _
    ",Nombre_Apellido " & _
    ",Email " & _
    ",Clave_Usuario) " & _
    "VALUES (" & _
    "@NombreLogin, " & _
    "@Cedula, " & _
    "@Nombre, " & _
    "@Correo, " & _
    "@Password); " & _
    "SELECT SCOPE_IDENTITY()"

    un punto y coma para separar el insert del scope_identity

    saludos

    ResponderEliminar
  39. Muy bueno su proyecto, sobre todo por la seguridad.
    me preguntaba si me puede ayudar con algo parecido. tengo una base de datos en sql server 2000, con la tabla de usuarios, lo que nesecito es ingresar desde la web con una session y una ves ingresado puedan hacer ciertas consultas a la base de datos segun corresponda a cada usuario, mejor si es en VB, por que C# no le entiendo bien. Gracias

    ResponderEliminar
  40. hola msn

    bien en realidad la implementacion web aqui esta planteada

    lo de la base de datos no afecta en nada, podrias implementar esto mismo con sql 2000 sin problemas, ya que la estructura de la tabla es bien simple.

    con respecto a la consulta por usuario imagino apuntas a implementar filtros en als consultas en conde incluyas al usuario como parte del mismo

    habria que analziar como las tablas que quieres consultar se relaciona con al tabla de usuario para poder armar los filtros de las queries

    saludos

    ResponderEliminar
  41. Hola quisiera que por favor me ayude con este codigo de autenticacion me sale el siguiente error: Es incorrecto utilizar una sección registrada como allowDefinition='MachineToApplication' mas allá del nivel de aplicación. Este error puede tener como origen un directorio virtual que no esté configurado como aplicación en IIS.










    de antemano Gracias.
    Ahh estoy utilizando un directorio dentro de mi proyecto al cual para acceder los usuarios tiene que identificarse este directorio tiene su propio web.config

    ResponderEliminar
  42. Hola, alguien me puede ayudar con una duda que tengo.
    he logrado configurar el proveedor de ASP.NET para que utilice mi base de datos que esta en sql 2000 en el cual ASP.NET me ha añadido otras tablas entre elllas la de usuario el cual funciona muy bien, el problema es que yo ya tengo una tabla asignada para usuario en mi base de datos el cual quiero que sea el utilizado por los que van a acceder a la web. ¿como puedo hacer que mi tabla sea el que utilice ASP.Net? tengo que modificar el System.config?? Ayudeme por favor de antemano le quedo muy agradecido

    ResponderEliminar
  43. hola msn

    Pero justamente en este articulo se hace uso de una base de datos y estructura de tablas propia, como estas describiendo

    no se usa la estructura que propone asp.net para la seguridad

    mas alla que uses sql 2000, y aqui se usa sql 2008, el resto deberia ser perfectamente compatible con lo que planteas

    saludos

    ResponderEliminar
  44. hola Leonardo..
    disculpa que sea tan molestoso pero he utilizado tu codigo cambiandole la conexion a mi base de datos, al parecer funciona a la perfeccion pero no logra ingresarar al default del usuario me sale el mensaje "Usuario ocontraceña incorrecta" podra ser por que el campo usuario es numerico?? mi conexion es la siguiente:
    connectionStrings
    add name="default"
    connectionString="Data Source=MI SERVIDOR;Initial Catalog=MIBASE;Integrated Security=True"
    providerName="System.Data.SqlClient"
    connectionStrings
    Muchas Gracias de antemano

    ResponderEliminar
  45. hola msn

    pero este mensaje es porque no puedes conectarte a la db, o porque no pasa la autenticacion que escribes en los textbox de la pagina de login

    por ese mensaje parece ser de la validacion de la pagina, no por un problema de conexion a la db

    saludos

    ResponderEliminar
  46. Hola nuevamente
    se conecta a la BD, los textbox toman los datos, lo que no entiendo es por que no me muestra el resultado que espero si lo datos introducidos estan en la BD mensionada
    Saludos!!!

    ResponderEliminar
  47. hola msn

    la verdad no sabria decirte sin analizar el codigo

    pero podrias poner un breakpoint y ver que datos esta tomando para validar si es correcta la info que envias a la query

    saluado

    ResponderEliminar
  48. Saludos
    Ya he logrado adaptar tu proyecto a mi base de datos y funciona bien. ahora el problema es cuando lo quiero meter dentro de mi proyecto, no me llega a mostrar el nombre del usuario en la pagina Default.aspx, todo corre bien, ingresa bien pero no me muestra el nombre del usuario en la etiqueta Label1. cual podra ser el problema.
    Gracias de antemano

    ResponderEliminar
  49. hola msn

    Estas tomando el usuario de

    Thread.CurrentPrincipal.Identity.Name

    y cuando te autenticas redireccionas usando

    FormsAuthentication.RedirectFromLoginPage()

    has validado que el codigo pase por dodne asigna el valor al label ?
    saludos

    ResponderEliminar
  50. Hola Leonardo:
    la redireccion funciona bien el codigo del label esta bien pero sigue sin mostrar el usuario, sera que tiene que ver al go con que lo estoy usando dentro de una sub carpeta a la que estoy poniendo como privado pero el web.config de la misma no me funciona la authentication asi que lo tube que sacar al web:config general del proyecto solo deje dentro de la carpeta la opcion de authorization. o como puedo hacer para que los dos funcione en el web.config que esta dentro de la carpeta que pretendo dar seguridad
    Gracias por tu tiempo!!!

    ResponderEliminar
  51. hola msn

    hay algo que no entiendo, el problea es que no puedes tomar el usaurio que se autentica o que no puede definir una carpeta para que este dentro o fuera de la seguridad de asp.net aplicada al sitio ?

    porque son cosas muy distintas

    saludos

    ResponderEliminar
  52. Hola Leandro:
    no me muestra el usuario.
    mi pregunta es: ¿sera que es por que tengo 2 wib.config? tengo 1 en el proyecto y otro en la carpeta de seguridad a donde tienen que logearse los usuarios y me muestra el error siguiente: Es incorrecto utilizar una sección registrada como allowDefinition='MachineToApplication' mas allá del nivel de aplicación. pero se soluciona cuando saco la Authentication al web.config general

    ResponderEliminar
  53. hola leandro

    con este codigo me sube al form1:

    SqlConnection conx = new SqlConnection("Data Source=joel;Initial Catalog=contabilidad;Integrated Security=True");

    try
    {
    SqlCommand log = new SqlCommand("select usuario,contrasena from loginadm where usuario='" + usuario.Text + "' and contrasena = '" + contrasena.Text + "'", conx);
    conx.Open();
    log.ExecuteNonQuery();

    DataSet ds = new DataSet();
    SqlDataAdapter da = new SqlDataAdapter(log);
    da.Fill(ds, "loginadm");
    DataRow dro;
    dro = ds.Tables["loginadm"].Rows[0];
    if ((usuario.Text == dro["usuario"].ToString() || (contrasena.Text == dro["contrasena"].ToString())))
    {
    this.Hide();
    Form1 a = new Form1();
    a.Show();
    }


    }

    catch
    {
    MessageBox.Show("Clave o Usuario incorrectos");
    contrasena.Clear();
    usuario.Clear();
    usuario.Focus();
    }

    finally
    {
    conx.Close();
    }

    {
    lo que quiero saber es:

    como lo puedo hacer para desde ese mismo login me pueda llamar tambien al form2 con su usuario y clave normalmente

    saludos

    ResponderEliminar
  54. Hola Leandro:
    Ya pude hacer funcionar el login era un problema de herencia, gracias por tu ayuda.
    Ahora te pido otro favor, ¿como seria para mostrar otro campo de mi tabla aparte del usuario?, osea mostrar 2 campos.
    Saludos!!!

    ResponderEliminar
  55. hola msn

    bien si puedes recuperar el nombre del usuario usando
    Thread.CurrentPrincipal.Identity.Name

    quiere decir que es enombre es unico, UNIQUE en tu tabla en la base de datos, por lo tento este lo usarias en una query hacia la tabla de tu db para recuperar el resto de la informacion que requieras mostrar el usuario


    saludos

    ResponderEliminar
  56. Hola Leonardo:
    yo me referia de que al momento de presionar el boton login me pueda pasar dos campos a la pagina default.aspx, de todas maneras me podrias pasar un ejemplillo sencillo del query si no es molestia?? (msnpostal@gmail.com)
    Gracias!!!

    ResponderEliminar
  57. hola msn

    la query seria tan simple como consulta la tabla de usuario

    SELECT * FROM Usuarios WHERE nombre = @nombre

    a este parametro le pasarias el
    Thread.CurrentPrincipal.Identity.Name

    solo seria esto lo que comente deberia realizarse para obteenr la info adicional del usuario que se autentica

    saludos

    ResponderEliminar
  58. hola leandro favor de darmele respuesta

    con este codigo me sube al form1:

    SqlConnection conx = new SqlConnection("Data Source=joel;Initial Catalog=contabilidad;Integrated Security=True");

    try
    {
    SqlCommand log = new SqlCommand("select usuario,contrasena from loginadm where usuario='" + usuario.Text + "' and contrasena = '" + contrasena.Text + "'", conx);
    conx.Open();
    log.ExecuteNonQuery();

    DataSet ds = new DataSet();
    SqlDataAdapter da = new SqlDataAdapter(log);
    da.Fill(ds, "loginadm");
    DataRow dro;
    dro = ds.Tables["loginadm"].Rows[0];
    if ((usuario.Text == dro["usuario"].ToString() || (contrasena.Text == dro["contrasena"].ToString())))
    {
    this.Hide();
    Form1 a = new Form1();
    a.Show();
    }


    }

    catch
    {
    MessageBox.Show("Clave o Usuario incorrectos");
    contrasena.Clear();
    usuario.Clear();
    usuario.Focus();
    }

    finally
    {
    conx.Close();
    }

    {
    lo que quiero saber es:

    como lo puedo hacer para desde ese mismo login me pueda llamar tambien al form2 con su usuario y clave normalmente

    saludos

    ResponderEliminar
  59. hola Joel

    un primer punto que puedo notar es porque no usas parametros en la query, no concatenes el string

    despues lo del form2 no quedo claro, porque alli veo que instancia y abres un form1, porque no podrias hacer lo mismo con el form2

    ademas si has analizado este articulo veras que el login se implementa anterior al inicio de la aplciacion por lo que el form del login no deberia invocar a ningun otro form, solo deja continuar y define el Application.Run() o no segun se pase la autenticacion

    saludos

    ResponderEliminar
  60. Hola Leandro:
    me podrias dar una idea para hacer un reporte de mi base de datos sql utilizando un DataList o cualquier regilla de datos pero que solo muestre los datos correspondientes al usuario identificado. Disculpa las molestias por estas pequeñeces esque estoy empesando con VisualStudio. Gracias!!!!

    ResponderEliminar
  61. hola msn

    para mostrar informacion con el usaurio que se autentica imagino estas tablas deberian contar con una columan que referencie a la tabla de usuarios

    o sea si tienes una tabla de Compras, esta tabla deberia contar con el campo IdUsuario el cuals e relaciona con la tabla de usuario para emntener la integridad

    si haces esto el resto es tan simple como en un query poner el filtro WHERE por este campo IdUsuario

    saludos

    ResponderEliminar
  62. Hola Leandro:
    sigo con las molestias, el asusnto es que he pasado de esta forma en parametro de identificacion y en la consulta a la bd no me funciona:
    Protected Sub Page_Load(ByVal sender As Object, ByVal e As EventArgs)

    Label1.Text = String.Format("Bienvenido al Sistema: {0}", Thread.CurrentPrincipal.Identity.Name)
    Dim username As String
    username = Thread.CurrentPrincipal.Identity.Name
    End Sub
    de que otra forma puedo utilizar el ID del usuario identificado?
    Gracias por tu tiempo!!!

    ResponderEliminar
  63. hola msn

    pero alli no veo que estes realizando ninguna consulta, solo muestras el nombre en un label

    la rpegunta seria, en ese label puede visualizar correctamente el nombre del usuario que se autentica?

    porque si puedes hacerlo solo es cuestion de usar esa infromacion para filtrar el registro en la query que haces a la tabla y recupera el registros


    saludos

    ResponderEliminar
  64. Hola Leandro:
    esta es la consulta que hago a la base de datos mediante un DetailsView.

    (/asp:DetailsView)
    (asp:SqlDataSource ID="SqlDataSource1" runat="server"
    ConnectionString="(% ConnectionStrings:SAAIINT1ConnectionString %)"
    SelectCommand="SELECT [nomprs], [apepat], [apemat], [demail] FROM [Ba02Perso] WHERE ([codreg] = @codreg)")
    (SelectParameters)
    (asp:QueryStringParameter Name="codreg" QueryStringField="label1{0}"
    Type="String" /)
    (/SelectParameters)
    (/asp:SqlDataSource)
    (/td)
    (/tr)
    (/table)
    (/asp:Content)

    Que otro codigo tendre que aumentar y en que archivo (El label1 toma perfectamente el campo codreg despues de iniciar sesion)
    Gracias por tu cooperación

    ResponderEliminar
  65. hola msn

    la verda no he usado mucho el SqlDataSource, pero me genera desconfianza el usar

    QueryStringField="label1{0}"

    no has probado aplicar esto mismo pero desde codigo, sin el SqlDataSource


    saludos

    ResponderEliminar
  66. hola Chechomancr

    la duda o problema cual seria ?

    saludos

    ResponderEliminar
  67. hola, resulta que tengo una pagina web en el servidor gratuito "yola" ¿que puedo hacer para crear una pestaña con seguridad, osea que requiera login y password?

    ResponderEliminar
  68. hola Photography

    que seria una pestaña de seguridad ?



    saludos

    ResponderEliminar
  69. Hola Leandro, te comento que esto es justo lo que buscaba.
    Pero le he hecho algunas modificacioncillas, me he creado una nueva solución (.sln) y el click del botón aceptar del crear nuevo usuario me entra dos veces y por tanto me inserta dos veces el insert. Corriendo el proyecto desde tu solución no repite el aceptar dos veces pero corriendo desde el mío que está basado en el tuyo me entra dos veces. No sabes el por qué de esto? Yo no entiendo. Si podrías darme luces por favor. Gracias.

    Saludos.

    ResponderEliminar
  70. hola «Pr!nc3s!ta»

    no ahs comentado que ti de proyecto estyas desarollando si es web o winforms, ni en que lenguaje programas

    pero bueno mas alla de eso valdiad como asignas el evento al boton, porque puede que tenga dos handler identicos repetidos lo que causaria que se lance dos veces aunque presionas solo una

    sino prueba de eliminar ese boton y crear uno nuevo, a ver si se elimina el efecto

    saludos

    ResponderEliminar
  71. Saludos,
    Me gustaría saber como es el código o la función para decodificar, es decir, sacar el código encriptado y volverlo normal, una cadena string.

    Gracias.

    ResponderEliminar
  72. hola Steven Gomez

    no se puede decodificar, el algoritmo hash es en un solo sentido

    y eso es lo bueno justamente de este algoritmo el no poder descifrarlo, sino que se trabaja por comparacion, solo un mismo texto identico genera la misma salida

    si el password del usuario no lo conoces deberas geenrar uno nuevo

    saludos

    ResponderEliminar
  73. Buenos dias Leandro un saludo cordial.

    Quiero hacerte una consulta . excelnete tutorial.
    Cómo puedo redireccionar a distintas páginas con perfil, usando este ejempo sin necesidad de ASPmembership.
    Se me ocurre usar dropdownlist para el perfil admin, user, otro, pero como lo implementaria ? muchas gracias

    ResponderEliminar
  74. hola Orlando Alarcón

    el tema es que no deberias teenr diferentes paginas por perfil, deberia ser una sola y segun que rol accesa cambiarle o limitarle la funcionalidad a una unica pagina

    sino el tema es que despues de autenticas vas a tener que hacer uso del Response.Redirect() para redireccionar donde necesites, no podras usar la info por defecto

    es mas la pagina principal luego del login a los sumo tendria que tener un menu y nada de funcionalidad, lo basico como para brindar el acceso a las acciones basicas

    saludos

    ResponderEliminar
  75. hola leandro quisiera saber que debes cambiar para adaptar el ejemplo a una base de datos 2008 pero que no sea la version express, ya lo intente pero al momento de grabar usuarios me aparece un error con el id cuando toma el valor entero, me dice que no puede ser null

    ResponderEliminar
  76. hola william

    en principio si la idea es usar una version full del sql server solo cambiarias el connection string

    o sea tomarias el .mdf y lo adjuntas al servicio de sql server y luego cambias en el .config el connection string para que no use el AttachDbFilename
    sino que debes poner el
    Initial Catalog=database name

    o sea

    Data Source=yourserver;Initial Catalog=DataBaseName;

    saludos

    ResponderEliminar
  77. lo que pasa esque la estoy haciendo con una base de datos que yo cree, en sql server 2008 r2 y copie el usuario de tu base de datos a la mia admin pass123 y si me puedo loguear pero al momento de crear un usuario me dice que Object cannot be cast from DBNull to other types. no es express la base de datos que estoy utilizando

    ResponderEliminar
  78. ok ya quedo solucionado, era un problema con el IdUsuario no lo tenia para que se incrementara automaticamente y obviamente marcaba error gracias por ayudar Leandro y Felicidades por tu blog esta muy interesante espero que sigas publicando temas porque son de gran ayuda e interes en hora buena

    ResponderEliminar
  79. hola leandro tengo una duda, si quisieramos agregar un metodo mas al service web, que es lo que tendriamos que hacer? ya vez que esta el autenticar, insert y agrege uno mas pero cuando intento hacer referencia con LoginService. no me sale el metodo que agrege

    ResponderEliminar
  80. hola william

    recuerda que si agregas un nuevo webmethod al web service debes luego en el cliente una actualizacion del proxy

    si haces click derecho en el web reference veras en el menu la opcion para actualizar la referencia al servicio ,de esta forma reconozca el nuevo webmethod agregado

    saludos

    ResponderEliminar
  81. ok ya quedo, ya me reconoce el nuevo webmethod la verdad esque nunca he utilizado servicios web, pero me estoy dando cuenta de la seguridad que proporcionan al manejar informacion,gracias por el dato

    ResponderEliminar
  82. Hola Leandro, muy bueno tu bloq. Una consulta, tengo una aplicacion en punto net y cuando verifico la contraseña en SQL 2008 no diferencia mayúsculas y minúsculas, tu crees que me puedas ayudar. Te adjunto el código que tengo hecho:

    Function Busca_Usuario()
    DTAREADER = OBJAUX.BUSCAR("SELECT * FROM USUARIO WHERE Id_Usuario = '" & TxtUsuario.Text & "' and Clave = '" & TxtClave.Text & "'")
    If DTAREADER.HasRows Then
    While DTAREADER.Read
    GIDUSUAR = DTAREADER.Item("Id_Usuario")
    GPERFIL = DTAREADER.Item("Id_Perfil")
    ENTRA = "SI"
    Me.DialogResult = Windows.Forms.DialogResult.OK
    Me.Close()
    End While
    Else
    MsgBox("Usuario o Contraseña Inválidos, Verifique", MsgBoxStyle.Exclamation)
    End If
    DTAREADER.Close()
    End Function

    ResponderEliminar
  83. hola Gaby

    estas planteando de forma incorrecta el codigo, deberias usar parametros y no concatenar los valors en un string

    revisa el articulo y veras que en ningun momento concateno para armar la query

    creo que el problema esta en OBJAUX.BUSCAR() ese metodo en lugar de ayudar termina empeorando el codigo

    saludos

    ResponderEliminar
  84. Hola leandro he mirado tus tutos y son geniales me gustaria una ayudita en cuanto al login yo quiero tener un boton en la aplicacion que me permita cambiar de user ocea que cuando el de click en ese boton le aparesca el login otra vez y que si el cambie de user no aya problemas

    ResponderEliminar
  85. hola mykolweb

    porque simplemente no realizas un

    Application.Restart()

    con ese seria como cerrar y volver a iniciar la aplicaicon mostrando el login nuevamente

    saludos

    ResponderEliminar
  86. Hola leandro gracias por la explicación.

    Solo una consulta no me quedo claro para que concatenas el usuario el password, dices que para mayor seguridad pero me podrías explicar porque mayor seguridad.

    ResponderEliminar
  87. hola Cristian Torres

    recueerda que el algoritmo del hash puede generar el mismo resultado ante ciertas cadenas de entrada, o sea dos password podrian generar el mismo hash, no es que se de normalmente esta situacion pero puede llegar a darse el caso

    es por eso que para reducir este inconveniente se agrega un dato adicional como es el nombre del usuario

    saludos

    ResponderEliminar
  88. ante todo te felicito por tu trabajo, pero tengo una consulta yo acabo de descargarlo y funciona bien, pero tu pryecto no crea nuevos usuarios Login con su contrseña o si porque en mi pc no crea la contraseña encriptada

    ResponderEliminar
  89. hola

    la contraseña no se encripta, se aplica un hash

    la verdad es raro que no cree usuario, estas seguro que validas la db correcta?

    que tipo de aplciacion desarrollas web, o winforms?

    saludos

    ResponderEliminar
  90. Hola Leandro una pregunta como puedo indicarle al webform que tengo un usuario "Admin" y solo el pueda crear nuevos usuarios... y ocultar el tap de usuarios en el form. Default y que solo sea visible para el usuario Admin(Admin seria el nombre del login) muchas gracias.

    ResponderEliminar
  91. hola Norimaki88

    en ese caso lo idea seria implementar una version custom del RolProvider

    Implementing a Role Provider

    analiza el ejemplo de implementacion, seguro debas adaptarlo pero es ae sla idea, para que luego puedas usar algun sitemap o sino desde codigo consultar si se esta en el rol y ocukltar o no determinada opcion

    saludos

    ResponderEliminar
  92. Ok muchas gracias Leandro por contestarme, voy a revisar lo que me comentas y te platico. Y otra cosa se me presento un detalle a la hora de pasar el formLogin a un formulario de la Master en la linea *ErrorMessage.InnerHtml* me marca el siguiente error...(error message.innerhtml is not declared. it may be inaccessible)como puedo solucionar esto. de ante mano gracias.

    ResponderEliminar
  93. hola Norimaki88

    pero ErrorMessage que seria ? es un control de validacion de asp.net o es un tag html con el runat=server

    ademas si usas el intellisense del VS te arroja como opcion esa propiedad

    saludos

    ResponderEliminar
  94. es un es un control de validacion de asp.net va dentro del metodo "Protected Sub ProcessLogin" tal ves tenga que ver que meti el codigo del siseño que antes iva en el body ahora va en un content2.. Porque cree un formulario usando la masterPage. Esto para darle el formato a todos mis forms por igual. :S

    ResponderEliminar
  95. Aqui una imagen del error del que te hablo.

    http://sidecia.com/images/banners/error.jpg

    ResponderEliminar
  96. hola Norimaki88

    bien la imagen aclra un poco mas pero igual no veo de dodne sale ErrorMesasage

    si es un Label por lo que veo lo has elimiando, si es un <span> osimilar te falta el runat="server"

    o sea si buscas ese ErrorMesasage en el proyecto que es ErrorMesasage ? que control se trata
    que pasa si solo lo quitas o reemplazas por algun otro control donde mostrar el mensaje

    saludos

    ResponderEliminar
  97. Gracias estimado Leandro.. ya lo solucione. Lo que pasa es que en un principio probe cambiando el message por un label y no funciono... crei que era por otra cosa pero después de un rato cuando cerré VS y lo intente de nuevo si se pudo. NO se a que se deba tal ves anda fallando mi programa de VS. pero ya quedo con la etiqueta muchas gracias. ahora estoy revisando lo de los roles. para que no cualquier usuario tenga acceso al FrmUsuario y cree cuentas nuevas. voy a revisar lo que me propusiste con anterioridad.

    ResponderEliminar
  98. Hola Leandro.

    Para poder modificar toda la BD con los datos encriptados como comentas. La manera de realizarlo ¿sería realizando un UPDATE desde c# aplicando el nuevo password? o ¿habría alguna manera de realizarlo desde SQLServer?

    Otra pequeña duda.
    Estoy trabajando con ASP.NET C#.
    Para añadir la función a mi proyecto sería meter un fichero .cs en la carpeta por ejemplo /lib/Encripta.cs y eliminar el namespace projecto



    using System;
    using System.Security.Cryptography;
    using System.Text;

    namespace projecto
    {

    internal class Encriptar
    {
    public static string EncodePassword(string originalPassword)
    {
    SHA1 sha1 = new SHA1CryptoServiceProvider();

    byte[] inputBytes = (new UnicodeEncoding()).GetBytes(originalPassword);
    byte[] hash = sha1.ComputeHash(inputBytes);

    return Convert.ToBase64String(hash);
    }
    }
    }

    Para poder usarlo desde cualquier parte..

    Muchas gracias por tus aportaciones.

    ResponderEliminar
  99. hola misretoques

    disculpa la demora en la respuesta

    - la idea es que la aplicacion sea quien realice los cambios de password, como es logica de negocio no creo que sea bueno que se pueda realizar desde la db, de ultima podrias hacer una pequeña aplicacion winform que tenga esta funcionalidad si es que quieres darle esto a algun admin de red

    - si estas con c# el namespace es obligatorio, quizas deberias poner el mismo que usa la aplicacion web si es que has creado un web application, lo cual recomiendo

    saludos

    ResponderEliminar
  100. Gracias por la contestación Leandro.

    al final lo he puesto dentro del namespace de mi proyecto.

    ¿El tema de internal o public, es mejor ponerlo en internal?

    Lo he dejado en internal. Ya lo tengo funcionando muchas gracias! ;)

    Un saludo y Gracias!

    ResponderEliminar
  101. Leandro Tuttini
    Gracias por los aportes, la verdad esta muy interesante

    Lo he implementado con los cambios necesarios y corre de maravilla, me ha dado nuevas ideas
    Nuevamente Gracias

    ResponderEliminar
  102. Hola!
    Me parece muy buena tu explicacion.
    Tengo una duda como le puedo hacer para autentificar varios tipos de usuario como son el administrador, empledos y etc.y que este los mande a diferentes paginas. Actualmente estoy programando visual Basic C# WPF.
    ojala pudieras ayudarme!
    Gracias, Saludos!!

    ResponderEliminar
  103. hola

    quizas podrias usar el

    Application.Startup Event

    y alli creas el new de la window de login para luego cuando termine de autenticar abrir la que corresponde al rol

    para esto codificarias en el App.xaml, analiza este link

    Application Management Overview

    te va a venir bien conocer lo que puedes hacer antes de iniciar la aplicacion

    saludos

    ResponderEliminar
  104. Hola Leandro una consulta par que funciona la clase UsuarioEntity

    ResponderEliminar
  105. hola ksoto

    esa clase se usa para definir la entidad del usuario que define el modelo del dominio

    se usa para recuperar los datos de la db y poder mapear eso a una clase de .net, si modelas en capas se defineen entidades de negocio

    saludos

    ResponderEliminar
  106. AYUDA!!

    perdón por las mayusculas pero ando algo desesperada...

    No puedo adaptarlo a MySQL, no se como =(

    Alguien me puede ayudar?

    Gracias y muy bueno el programita.

    Gracias!!!

    ResponderEliminar
  107. hola Monzy

    pero en mysql tienes uan tabla de usuario con al menso el nombre y el password ?

    porque si es asi solo es cuestion de uar el proveedor de ado.net para mysql

    http://social.msdn.microsoft.com/Forums/es/netfxwebes/thread/48867ed7-bad2-4c14-835f-3366d0ac2395

    asi podrias usar el MySqlConnection. MySqlCommand, etc
    la estructura del codigo es practicamente igual, solo que los parametros en lugar de usar @ usarias ?

    saludos

    ResponderEliminar
  108. Hola Leandro:

    Yo utilizo los login de 2010 y me funciona todo correctamente, pero tanto si utilizo tú código o el de 2010. ¿hay alguna forma da activar un usuario por código, conociendo el nombre de usuario y su contraseña? en el caso de tú ejemplo -admin- pass123.

    esto me solucionaría un problema de autenticación por certificado dígital, porque en el caso que la autenticación sea correcta entraría con un usuario generico. La verdad me estoy volviendo loco para encontrar una solución.

    Un saludo y gracias.

    ResponderEliminar
  109. hola Gerardo

    la verdad no entendi, que tiene que ver la definicion de un usuario en tu db con un certificado digital

    se supone que los certificado en un entorno wweb son para segurizar el transporte, o sea los usarias para implementar SSL y acceder por medio de https

    pero eso no tendria nada que ver con un usuario y password

    saludos

    ResponderEliminar
  110. Como estas Leandro, he utilizado este código hash gracias a ti y me funciona de maravilla, voy a colocarlo en el nuevo visual studio 2012 y te comento como me va.

    ResponderEliminar
  111. Hola Leandro:

    Muy bueno tu blog:

    Una pregunta, como puedo adaptar tu ejemplo de vb, para que funcione desde una aplicación en silverlight para que autentique contra MSSQL. Muchas gracias de antemano.

    ResponderEliminar
  112. Hola Leandro:

    Muy Bueno tu Blog:

    Por favor dime si se puede utilizar este mismo esquema para autenticar y autorizar desde una aplicación de silverlight...?

    Gracias de antemano.

    ResponderEliminar
  113. hola Carlos

    la verdad no he aplicado esta tecnica con silverlight seguramente lo que es el modelo de seguridad se pueda usar, pero la forma de autenticar seguramente requiera implementar servicio web para que silverlight se comunique

    Authentication in Silverlight using WCF and ASP.NET Membership Provider

    por supuesto en el link es un ejemplo que se debe adaptar, pero los servicios wcf podrian usar los metodo de membership para devolver la info

    saludos

    ResponderEliminar
  114. Hola Leandro, guen tutorial muy interesante

    ResponderEliminar
  115. Hola Leandro

    Si quisiera actualizar la contraseña del usuario admin, ¿Como la realizaría? ya que es una contraseña con Hash.

    ResponderEliminar
  116. hola radx

    simplemente la pisas con un UPDATE
    podrias ser:

    UPDATE tabla SET contraseña = @pass WHERE id = @id

    saludos

    ResponderEliminar
  117. hola Leandro

    Lo adapte para una base de datos Access e intente hacerlo de esta forma;

    Public Sub modificarclave()
    Using con As New OleDbConnection(ConfigurationManager.ConnectionStrings("default").ToString())
    Dim cmdmodificar As OleDbCommand

    Dim password As String = Helper.EncodePassword(String.Concat(txtmensaje.Text, txtmensaje.Text))
    Dim sql As String = "update usuarios set [password] ='" + txtmensaje.Text + "' where (Usuarios.usuario = '" + TextBox1.Text + "')"


    Try

    con.Open()
    cmdmodificar = New OleDbCommand(sql, con)
    cmdmodificar.ExecuteNonQuery()
    cmdmodificar.Dispose()
    con.Close()
    Catch ex As Exception
    MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Error")
    End Try
    End Using

    El detalle es cuando despues de haber actualizdo el password e intento accesar a la aplicación, no ingresa, ademas cuando reviso en la base de datos el campo "password" la contraseña no esta cifrado como cuando estaba antes de actualizar ¿Por que pasa esto?

    ResponderEliminar
  118. hola radx

    me pregunto porque no usas parametros? eso de concatenar en un string no es correcto

    ademas si el password lo pones en la variable "password" porque usas txtmensaje.Text ?

    saludos

    ResponderEliminar
  119. Hola

    Lo que pasa es que realizo una aplicación que te permite recuperar tu contraseña si la olvidas por medio de tu correo electronico, entonces el contenido del txtmensaje.text es el texto que va a mostrar en correo electronico, el txtmensaje contiene la nueva contraseña por eso es que para la consulta en el campo password pongo como valor a este cuadro de texto txtmensaje.text

    ResponderEliminar
  120. En cuanto a los parámetros esto seria asi;

    Public Sub modificarclave1()
    Using con As New OleDbConnection(ConfigurationManager.ConnectionStrings("default").ToString())


    'Dim password As String = Helper.EncodePassword(String.Concat(txtmensaje.Text, txtmensaje.Text))
    Dim sql As String = "update usuarios set [password] = @password where (Usuarios.usuario = @usuario)"


    Try

    Dim cmdmodificar As New OleDbCommand(sql, con)

    cmdmodificar.CommandType = CommandType.Text

    cmdmodificar.Parameters.AddWithValue("@usuario", TextBox1.Text)

    Dim password As String = Helper.EncodePassword(String.Concat(TextBox1.Text, txtmensaje.Text))
    cmdmodificar.Parameters.AddWithValue("@password", txtmensaje.Text)


    con.Open()

    cmdmodificar.ExecuteNonQuery()
    cmdmodificar.Dispose()
    con.Close()
    Catch ex As Exception
    MsgBox(ex.Message, MsgBoxStyle.OkOnly, "Error")
    End Try
    End Using
    End Sub


    Pero no se por que ahora no hace la actualización de la contraseña en la base de datos, tampoco me marca error

    ResponderEliminar
  121. yo actualizo los datos de esta manera:

    Private Shared Function UpdateTecnico(CTecnico As TecnicoEntity) As TecnicoEntity
    '* Función Para Actualizar Datos del Técnico *

    Dim sql As String = "UPDATE Usuarios SET " & _
    "[IdUsuario] = @IdUsuario " & _
    ",[Titulo] = @Titulo " & _
    ",[Telefono] = @Telefono " & _
    ",[Extension] = @Extension " & _
    ",[IdDepartamento] = @IdDepartamento " & _
    ",[Nombre] = @Nombre " & _
    ",[NombreLogin] = @NombreLogin " & _
    ",[Password] = @Password " & _
    ",[Email] = @Email " & _
    ",[TipoUsuario] = @TipoUsuario " & _
    "WHERE [IdUsuario] = @IdUsuario"

    Using conn As New SqlConnection(ConfigurationManager.ConnectionStrings("default").ToString())

    Dim command As New SqlCommand(sql, conn)
    command.Parameters.AddWithValue("IdUsuario", CTecnico.IdUsuario)
    command.Parameters.AddWithValue("Nombre", CTecnico.Nombre)
    command.Parameters.AddWithValue("Titulo", CTecnico.Titulo)
    command.Parameters.AddWithValue("Telefono", CTecnico.Telefono)
    command.Parameters.AddWithValue("Extension", CTecnico.Extension)
    command.Parameters.AddWithValue("IdDepartamento", CTecnico.IdDepartamento)
    command.Parameters.AddWithValue("NombreLogin", CTecnico.NombreLogin)
    command.Parameters.AddWithValue("Password", CTecnico.Password)
    command.Parameters.AddWithValue("Email", CTecnico.Email)
    command.Parameters.AddWithValue("TipoUsuario", CTecnico.TipoUsuario)

    conn.Open()

    command.ExecuteNonQuery()

    Return CTecnico
    End Using
    End Function

    Claro esta lo que deseas hacer si el usuario olvida la clave se la quieres enviar por mail, no se si se puede hacer usando el metodo Hash. ya que hash lo que hace es encriptar una cadena y compararla con la que esta en la BD que esta encriptada tambien. no se si es posible hacerlo.

    ResponderEliminar
  122. hola radx

    puede ser que no actualice porque el parametro del usuario no encuantre ningun registro, porque no usas

    where usuario = @usuario

    en la query
    pero valida

    int rowafectadas = cmdmodificar.ExecuteNonQuery()

    si es que devuelve row afectadas si devuelve cero es que no encontro ningun registro con ese usuario que proporcionas

    saludos

    ResponderEliminar
  123. hola Enyelber

    una vez que aplcias el hash ya no se puede obtener la contraseña original, lo que ahces en ese caso es generar una random y asignas esa para informarla al usuario

    este ingresa pero depsues deria cambiarla por una correcta
    o sea tu eres quien le genera una nueva password temporal si es que este olvido la que tenia, las contrseñas siemrpe se regeneran, no se pueden recuperar

    saludos

    ResponderEliminar
  124. muy buen tutorial Leandro, lo estoy adecuando al un proyecto de Windows Mobile, y la BD no entiendo muy bien la estructura de la tabla, haber si por fa me ayudas con eso.

    ResponderEliminar
  125. hola Felipe

    estructura de la tabla ? pero si solo define al usuario con su info basica, se podria decir que casi ni tiene estructura

    no se que es lo que no se puede entender

    saludos

    ResponderEliminar
  126. gracias, es la estructura de la tabla. y quisiera saber si usted sabe sobre variables de sesion en C# pero para windows forms. Gracias

    ResponderEliminar
  127. hola Felipe

    lo de estructura de tablas sigo si comprenderlo

    ademas en windoform no existe el concepto de sesion, este objeto si existe en un desarrollo web
    alli la Session tiene un timeout en el que expira, pero en winforms no esta este concepto

    saludos

    ResponderEliminar
  128. Lo que pasa es que es un login con BD, y pues como se necesita hacer un select, pues se necesita saber como formada la tabla de los usuarios por decirlo asi. Para poder hacer la comparacion. Gracias

    ResponderEliminar
  129. hola Felipe

    Pero podrias usar la estructura que aplico en este articulo, algo simple un id de usuario, un nombre y password, con eso basico ya puedes autenticar a un usuario

    me pregunto este articulo lo leiste y entendiste ? porque lo que planteas esta alli, es mas uso un select como mencionas

    saludos

    ResponderEliminar
  130. Hola Leandro, me ha interesado mucho el post dado que he estado trabajando con variables de sesion y claramente no es buena idea, solo tengo una duda; en el caso de que quiera substituir el form por default al que redirige la aplicación es decir en FormsAuthentication.RedirectFromLoginPage(txtUsuario.Text, false). Me di cuenta que hay un tercer parametro en el cual le puedo indicar el path pero no me funciona o talvez no lo estoy haciendo bien.
    Lo hice asi:

    FormsAuthentication.RedirectFromLoginPage(txtUsuario.Text, false, "~/Management/menu.aspx");

    pero me sale el error que no se encuentra el la pagina "default.aspx". Me ayudas pls? :)

    ResponderEliminar
  131. hola EddieF

    lo que veo en la doc del msdn es que ese path corresponde a las cookies

    FormsAuthentication.RedirectFromLoginPage (Método) (String, Boolean, String)

    para redireccionar de forma concreta deberias dejar de eusar ese metodo y crear el ticket de autenticacion de forma concreta y luego usar el Response.Redirect()

    o sea usarias el
    FormsAuthenticationTicket (Clase)

    saludos

    ResponderEliminar
  132. Muchas gracias Leandro, pero asumiendo que eso lo hago en el formulario de login; donde especifico el webform al que quiero se redirija cuando me loguee??
    es decir, yo quiero que al logearme me redirija al form que está en "Management/menu.aspx"
    pero en el metodo no veo en donde le indico que se vaya a esa direccion...

    Saludos y gracias :)

    ResponderEliminar
  133. hola EddieF

    si usas la seguridad de asp.net esto lo defines en el web.config, en la config defines a que pagina se debe redireccionar

    no es en el metodo donde lo defines, es en la configuracion

    saludos

    ResponderEliminar
  134. Hola, Leandro si ya tengo implementado tu ejemplo en asp.net
    como hago para configurar las opciones del menu por usuario,
    digamos que a un usuario no quiero que accese a la opción de ventas del menu que se ponga Ventas Enable=false..Saludos

    ResponderEliminar
  135. hola manuel

    podrias poner el enabled en false para ese item del menu, pero deberias definir algun rol
    entonces sabiendo el rol podrias aplciar logica que habilite o no el item del menu

    saludos

    ResponderEliminar
  136. Buen tutorial Leandro, una pregunta; como podria validar que un usuario solo pueda estar logueado una sola vez, si intentan logearse desde otra maquina con el mismo usuario que esta activo.
    Saludos

    ResponderEliminar
  137. hola Pit

    el tema es que al estar en un ambiente web no es facil de lograr

    aqui

    http://social.msdn.microsoft.com/Forums/en-US/b7d7e8f1-ca9b-4915-965a-9dab12310f9d/evitar-sesiones-mltiples-de-un-mismo-usuario-en-una-aplicacin-web

    se plantea lo mismo

    saludos

    ResponderEliminar
  138. Hola Leandro, como hago para aplicar esto de la inserción en un proyecto asp.net mvc, solo es que me lo expliques no pido ningún codigo

    Att: JYag

    ResponderEliminar
  139. Hola Leandro, como hago para realizar la insercion en asp.net mvc, solo pido una explicacion...al aplicar el autenticar me dio perfecto y es mas que lo realice con busquedas del usuario en varias bases de datos

    ResponderEliminar
  140. Hola Leandro, una pregunta con el tema de usuarios.. estoy trabajando en 3 capas,, y la unica solucion rapida que pude encontrar por ahora es declarar una variable global dentro de un modulo para guardar el usuario. Segun lei no es recomendado. Este modulo lo coloque dentro de la capa de entidades para que sea visible en todas las capas.. Conoces otro metodo de almacenar un usuario y que sea visible por todas las capas? Gracias..

    ResponderEliminar
  141. hola Joe NUÑEZ

    lo que no entendi es a que inserción haces referencia, apuntas al insert que se realiza en la tabla del usuario ?

    porque ese es codigo ado.net simple, el cual imagino deberias implementar en alguna clase en otro proyecto del tipo class librar y desde el action de algun controlador invocarlo

    saludos

    ResponderEliminar
  142. hola Si Señor

    podrias implementar una clase singleton que dentro utilice un objeto Session

    com se planteo aqui

    http://social.msdn.microsoft.com/Forums/es-ES/10bbbd6a-b8f5-4b43-bb2b-778341ac01f0/singleton-en-varias-capas

    de esta forma desde la UI asignas el valor y podrias tomarlo desde otras capas

    saludos

    ResponderEliminar
  143. Hola Leandro, me olvide de decirte que el tema del usuario era para una aplicación de escritorio.. Gracias..

    ResponderEliminar
  144. hola Si Señor

    entonces es mas simple

    aplcias el mismo concepto de singleton solo que no necesitas al objeto Session, sino que usas directo una variable

    algo como esto
    http://ltuttini.blogspot.com.ar/2011/07/winforms-singleton-pasar-datos-entre.html

    solo que tu usaras esa clase tambien desde otros proyectos

    Nota: esto lo vas a tener que implementar en un proyecto separado para asi poder referenciarlo desde los demas proyectos (otras capas) que necesiten de la info

    saludos

    ResponderEliminar
  145. Hola Leandro, me funciono perfecto lo del singleton. Lo que hice fue hacer lo del singleton y agregue una propiedad de usuarioLogueado y en login le asigno el usuario. una pregunta:
    1)cual es la diferencia entre crear un singleton + la propiedad en crear una clase sin el singleton pero con la propiedad? osea cual es el objetivo del singleton.

    Saludos

    ResponderEliminar
  146. hola Si Señor

    el patron singleton tiene por objetivo devolver la misma instancia para todo aquel que lo invoque

    por lo que lo convierte en un patron ideal como punto central donde poner informacion global, lo bueno es que como utilizas objetos puedes implementar info mas compleja en funcionalidad comparado con tener solo una simple variable public y static

    saludos

    ResponderEliminar
  147. HOLA Leandro mira el punto es que necesito que me ayudes a hacer una encriptacion de mi contraseña con hash pero estoy utilizando MVC de Asp.net y SQL Server 2012

    ResponderEliminar
  148. hola Carlos

    pero en este mismo ejemplo muestro como implementar un hash para la contraseña, en mvc es exactamente igual, y la db tampoco afecta

    saludos

    ResponderEliminar
  149. Pero en que parte tengo que colocar ese codigo

    ResponderEliminar
  150. hola Carlos

    analizaste el codigo del articulo ?
    porque alli muestro donde lo utilizo

    cuando creas un nuevo usuario, o cuando vayas a validar, en ambos casos tomas lo que el usuario ingresa en los controles y aplicas el hash

    saludos

    ResponderEliminar
  151. Hola Leandro, implemente tu ejemplo en mi proyecto y funciono de 10.
    ahora te queria pedir si me podes ayudar a implementar roles para aplicar la seguridad en las paginas de mi proyecto.
    los accesos/permisos a las paginas las quiero tener en la base de datos y no en el web.config, como debo hacer?

    ResponderEliminar
  152. hola anibal

    podrias crear una table de roles y otra de funciones

    las funcioens dirian algo como ser "leer articulos", "editar proveedor", etc
    o sea que funcion cumple, estas funciones se agruparian en los roles, entocnes un rol tendria N funciones

    a los usuario le asignas el rol por lo que tendria un grupo de funciones que usarias en tu codigo para poder saber si puede realizar una determinada accion, esto lo implementas en el codigo con logica que valida si para ahcer determinada accion dispone de esa funcion

    saludos

    ResponderEliminar
  153. gracias Leandro, lo que hice hasta ahora es el login, y guardar perfil y permisos del usuario en cookies, mi duda es como hago para leer de esas cookies en cada pagina.
    vi este ejemplo(http://msdn.microsoft.com/es-es/library/bb972205.aspx), pero no me andan las interfaces que utiliza, esta bien utilizar modulos o deberia usar otra cosa? gracias!!!

    ResponderEliminar
  154. hola anibal

    la verdad ese articulo es un poco antiguo, no creo que usar un handler para implementar seguridad sea lo correcto

    por eso existe asp.net membership

    Custom MembershipProvider Y RoleProvider

    puse el link de una implementacion custom porque en este caso dwefines una estructura diferente a la estandar

    saludos

    ResponderEliminar
  155. gracias, voy a revisarlo...
    estoy en un proceso de asimilacion de estas herramientas.
    Saludos!

    ResponderEliminar
  156. Hola muy bueno tu articulo, si sabes...me podrías decir cuál sería una manera "segura" de restringir paginas (ya tengo roles),pero sin usar cookies y no estar haciendo consultas a la BD cada momento. ?

    ResponderEliminar
  157. hola ethnox

    en asp.net el acceso a las paginas no esta limitado, si podrias definir en el config el tag he indicar que rol o si se requiere autenticacion para una pagina o carpeta dentro del sitio

    podrias tambien unir el rolProvider con el sitemap para que algun menu muestre o no los link de acceso a las paginas

    http://social.msdn.microsoft.com/Forums/es-ES/17190ffc-a8a2-4e9d-9282-572f05ec817f/como-habilitar-elementos-del-menu-segun-rol-con-aspnet-y-c

    http://social.msdn.microsoft.com/forums/es-ES/ff76b43a-9be8-42a8-9eec-9e353517a9ee/dar-permisos-para-usuario-en-menus-aspnet

    http://social.msdn.microsoft.com/Forums/es-ES/e38aa8fd-7b77-4fd7-8c64-25168ed44e75/web-sitemap-con-un-role-provider-personalizado

    saludos

    ResponderEliminar
  158. Hola Leandro.

    Me gustaría que me aconsejaras que método de encriptación usar para guardar contraseñas de los usuarios.

    Ya que no paro de leer que MD5 y SHA-1 no son aconsejables.

    ¿Qué método recomiendas para encriptar passwords?

    Un saludo y gracias.

    ResponderEliminar
  159. hola misretoques

    como que el hash no es aconsejable, es justamente lo que debes usar para aplicar la mejor seguridad en el password del usuario

    en este articulo justamente uso estos para que nadie pueda obtener la contraseña

    saludos

    ResponderEliminar
  160. Hola leandro, una pregunta.. me muestra error en la linea "UsuarioEntity usuario = new UsuarioEntity();" y en la linea "SHA1 sha1 = new SHA1CryptoServiceProvider();"

    a qué puede deberse? desde ya gracias! excelente post

    ResponderEliminar
  161. hola Mily

    cual es el mensaje de error que recibes ?

    este se produce al compilar o cuando ejecutas la aplicacion?

    saludos

    ResponderEliminar
  162. Para la linea: "UsuarioEntity usuario = new UsuarioEntity();" me muestra el siguiente error de compilación:
    "No se puede encontrar el tipo o el nombre de espacio de nombres 'UsuarioEntity' (¿falta una directiva using o una referencia de ensamblado?)"

    y para la línea: "SHA1 sha1 = new SHA1CryptoServiceProvider();" me muestra el siguiente error de compilación:
    "No se puede encontrar el tipo o el nombre de espacio de nombres 'SHA1' (¿falta una directiva using o una referencia de ensamblado?)"

    ResponderEliminar
  163. hola Mily

    validaste que esten las referencias al proyecto de entidades ? y que la definicion del "using" donde defines el namespace de esta entidad este bien definido

    es mas si realizas una busqueda por UsuarioEntity en la solucion se encuentra esta clase definida ?

    para SHA1 recuerda que esta en el namespace System.Security.Cryptography por lo que deberias definirlo en el using

    saludos

    ResponderEliminar
  164. Excelente ejemplo, pero tengo una pregunta, ¿cómo puedo hacer para que un usuario pueda tener solo una sesión iniciada? Es decir, si intenta iniciar sesión en otra máquina cuando ya tiene una sesión iniciada le niegue el acceso, y ¿cómo poder controlar para que las sesiones no se queden "colgadas" en caso de que no pongan "logout"?

    ResponderEliminar
  165. hola Sergio

    no lo veo facil de lograr, la verdad nunca he implementado algo similar pero imagino deberias mantener la info de los usuario que estan activos en el objeto Application para que sea global al sitio web

    podrias validar alli si el usuario ya se autentico, quizas si por alguna razon no pasa por el Session_End para quitarlo de la lista, adicionalmente se podria crear alguna funcionalidad de admin para que alguien pueda removerlo en caso de quedar alli registrado, aunque igualmente al vencer la session a los 20min se deberia remover solo

    por supuesto si el sitio se reinicia el application se perdera, pero igual es algo temporal

    saludos

    ResponderEliminar
  166. Primero que nada Muchas Gracias por todos tu ayuda, ami me han ayudado un buen tus ejemplos. en este caso solo quiesiera si me pudieras ayudar a conectarla a una base de datos externa, que no se encuentre en el mismo poryecto, ya trate y trate y no he podido. Espero que me ayudes.

    Gracias!!

    ResponderEliminar
  167. hola Jose

    como defines el connection string? porque si la db esta en una pc diferentes solo haria falta especificar en el DataSource el nombre o la ip de esa pc

    recuerda tambien validar que el firewall de windows no este bloqueando la conexion

    y si usas un sql express de habilitar el servicio para poder recibir coneciones remotas, ya que por defecto solo las permite locales

    saludos

    ResponderEliminar
  168. Leandro, gracias por tu respuesta, ya lo adapte, el siguiente problema fue al momento de guardar no se pudo, pero lo intente de otra manera y ya quedo,, gracias!!!

    ResponderEliminar
  169. Leandro referente a tu artículo escrito "Login - Usando Password con Hash", ¿Qué pasa si en el botón cancelar del loginUI agrego una confirmación si realmente desea salir del sistema? lo hice, pero al presionar el botón "NO" en la confirmación, igual se cierra el sistema, ¿Me podrías ayudar con eso?

    Saludos y gracias

    ResponderEliminar
  170. hola Daniela

    pero esta validacion la realizas en el evento formclosing del form ?

    si es asi al cancelar el dialog estas asignando el e.Cancel para que anule el cierre

    Form.Closing Event

    saludos

    ResponderEliminar
  171. Mira en el frmLogin_FormClosing tengo esto

    private void frmlogin_FormClosing(object sender, FormClosingEventArgs e)
    {
    switch (e.CloseReason)
    {
    case CloseReason.FormOwnerClosing:
    e.Cancel = true;
    break;
    //Si se deja este 'caso', no permite entrar cuando se presiona el botón Ingresar.
    case CloseReason.None:
    e.Cancel = true;
    break;
    case CloseReason.TaskManagerClosing:
    e.Cancel = true;
    break;
    case CloseReason.UserClosing:
    e.Cancel = true;
    break;
    case CloseReason.WindowsShutDown:
    e.Cancel = true;
    break;
    }
    }

    y luego en el botón cancelar tengo esto

    private void btnCerrar_Click(object sender, EventArgs e)
    {

    DialogResult Resultado = MessageBox.Show("¿Está seguro que desea salir?", "Confirmación", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);


    if (Resultado == DialogResult.Yes)
    {
    Application.Exit();
    }
    }

    ResponderEliminar
  172. hola Daniela

    en el evento del cancelar no pones ningun mensaje, lo debes poner en el formclosing

    private void frmlogin_FormClosing(object sender, FormClosingEventArgs e)
    {
    DialogResult Resultado = MessageBox.Show("¿Está seguro que desea salir?", "Confirmación", MessageBoxButtons.YesNo, MessageBoxIcon.Question, MessageBoxDefaultButton.Button2);

    if (Resultado != DialogResult.Yes)
    {
    e.Cencel = true;
    }
    }

    private void btnCerrar_Click(object sender, EventArgs e)
    {
    this.Close()
    }


    no se lo del switch de las razones de cerrar el form, peor la pregunta se realiza en el evento de cierre para anularlo si es que se selecciona esto en el messagebox

    saludos

    ResponderEliminar
  173. hola leandro un saludo desde peru, como puedo recuperar el usuario ,en otros formularios oseo si es usurio q inicia seccion como puedo recuparar a ese usuario en cualquiero formulario

    ResponderEliminar
  174. hola Deyvi

    podrias usar la tecnica

    [Winforms] Singleton - Pasar datos entre formularios

    para asi poner el usuario en el singleton global al cual puedes acceder desde cualquier form

    saludos

    ResponderEliminar
  175. hola, para comenzar con esta aplicacion debemos crear una aplicacion normal de windows form cierto?

    ResponderEliminar
  176. Eric

    exacto, luego si lo necesitas a la solucion le vas agregando otros proyecto, peor inicias con un Windows Application

    saludos

    ResponderEliminar
  177. Hola Leandro,
    Un gusto saludarle,
    Tengo una duda, me preguntaba si hay alguna diferencia en cifrar los datos desde el Sql Server, y con este método con el que tu haces.
    En esta página:
    http://msdn.microsoft.com/es-es/library/bb972194.aspx
    En la página se dice que hay 4 formas de cifrar los datos, uno con más segurida que el otro, pero con más uso de recursos.

    ResponderEliminar
  178. hola Miguel

    el tema por lo que veo es que la seguridad en sql server esta generando claves simetricas o publica-privada, por lo que de alguna forma es posible consiguir nuevamente el dato de la db

    lo que yo planteo implica no poder volver hacia atras, el hash solo va en una direccion, por eso aplicas el hash y comparas con lo que tienes en la db, no hay password ni claves que permitan volver a obtener el valor original que el usaurio puso como clave de su login de ingreso

    si desde sql server puedes aplicar hash entonces realizalo seria lo mismo

    esto lo planteo para autenticacion, ahora si es para otro tipo de datos de negocio eso es otro tema

    saludos

    ResponderEliminar
  179. Leandro muchas gracias, me ha servido mucho. Pero me queda una duda. Tengo un gridview, y en la parte de la contraseña que es el siguiente:












    Al hacer clic para actualizar los datos, si actualizo el campo contraseña no hay problema, pero si actualizo cualquier otro campo y no hago nada en el campo contraseña, esta no se mantiene, o sea toma el valor de este Text="****", y realiza el hash con "****". Cómo puedo hacer para que, si no quiero actualizar la contraseña pero si otro campo, la antigua contraseña se mantenga? y no se cambie por otros valores?
    Muchas muchas gracias!

    ResponderEliminar
  180. Leandro muchas gracias, me ha servido mucho. Pero me queda una duda. Tengo un gridview, y en la parte de la contraseña que es el que la tengo dentro de un templatefield
    Al hacer clic para actualizar los datos, si actualizo el campo contraseña no hay problema, pero si actualizo cualquier otro campo y no hago nada en el campo contraseña, esta no se mantiene, o sea toma el valor de este Text="****", y realiza el hash con "****". Cómo puedo hacer para que, si no quiero actualizar la contraseña pero si otro campo, la antigua contraseña se mantenga? y no se cambie por otros valores?
    Muchas muchas gracias!

    ResponderEliminar
  181. hola Patricio

    por lo generar se definen dos form, uno para editar todos los datos del usuario (menos la contraseña) y otro adicional para cambiar el password (serian el tipico form que piden ingresar la contraseña actual y repetir dos veces la nueva)

    entonces cuando creas el usuario pides todos los datos incluyendo la contraseña
    pero cuando actualizas la contraseña no la pides, ya que tiene el otro form para el cambio de contraseña

    saludos

    ResponderEliminar
  182. HOLA LEANDRO, quisiera utilizar esa validacion medicante un webmethod
    pero me sale error al ejecutar el
    FormsAuthentication.RedirectFromLoginPage


    este es el codigo..

    Public Shared Sub validar(ByVal usuario As String, ByVal contrasena As String)
    If Autenticar(usuario, contrasena) Then
    FormsAuthentication.RedirectFromLoginPage("xxx", False)
    Else
    'ErrorMessage.InnerHtml = "Usuario o contraseña incorrectos... por favor re-ingrese las credenciales..."
    End If

    ResponderEliminar
  183. Hola Leandro,estoy tratando de adaptarlo tu ejemplo con mi ide de visual y sql server 2012.El web form lo puedo ver sin problema en el navegador,pero como hago para establecer la conexion con la bd, o en que parte puedo verla como esta compuesta la bd para crear las tablas aparte y establecer la nueva conexion en el app.config?

    ResponderEliminar
  184. hola Andres

    si el desarrollo es web deberias revisar el web.config
    alli estara la conexion definida

    en este caso como no uso el proveedor de asp.net membership puede poner las tablas juntos al resto, no necesitas separarlo

    saludos

    ResponderEliminar
  185. Que tal

    me esta sirviendo mucho tu codigo

    Tanto que quiero implementarlo en mi proyecto pero necesito hacerlo con procedimientos almacenados como se implementaria la validacion en sql y como lo implementaria en visual basic

    ResponderEliminar
  186. hola Anthony

    pero conoces como crear stored procedure ?
    porque en este caso seria tan solo pasar las consulta y definir en el SqlComand el CommandType indicando que es un procedure

    en el articulo se puede descargar el codigo en vb.net

    saludos

    ResponderEliminar
  187. Este comentario ha sido eliminado por el autor.

    ResponderEliminar
  188. Hola, excelente y muy explicado todo, sin embargo necesito hacer lo siguiente, si me pudiera ayudar sería perfecto:
    Debo almacenar un valor llamado cod_cia, el cual debe permanecer siempre desde que se inicia la sesión, hasta que se cierra.
    Cual es la manera más recomendada para esto? Actualmente lo hago con variable Session

    ResponderEliminar
  189. Hola leandro una pregunta , este codigo lo puedo definir en el master page o tengo que hacerlo pagina por pagina

    ResponderEliminar
  190. Hola Leandro, muy buen tutorial, sin embargo tengo un problema con una aplicación winforms. Estoy tratando de llamar desde el Main() mi formulario Login antes del principal, de la sig manera:
    Application.EnableVisualStyles();
    Application.SetCompatibleTextRenderingDefault(false);

    Login login = new Login();
    login.ShowDialog();

    if (login.DialogResult == DialogResult.OK)
    {
    Application.Run(new HomeStudent());
    }

    Cuando el usuario es validado correctamente, hago lo siguiente para que entre en la condición del Main():
    this.DialogResult = System.Windows.Forms.DialogResult.OK;

    Pero al hacer el cambio de formularios mi aplicación se cierra por completo, estoy pasando algo por alto? Estoy utiliando VS2012 y la versión 4.5 del .NET Framework.

    Un saludo
    Mario

    ResponderEliminar
    Respuestas
    1. hola
      Que seria el cambio de formulario ?
      Recuerda que si haces un close del HomeStudent la aplicacion se va a cerrar, porque esta es la que defines como inicial
      saludos

      Eliminar
    2. Listo Leandro, había olvidado que tenía un evento Closing en el form Login, por lo tanto se cerraba la aplicación cuando cambiaba el valor de la propiedad DialogResult.

      Un saludo!!

      Eliminar
  191. hola leandro prodias verificar que le falta mi codigo que cuando me logeo no ingresa a la pagina principal ..

    using System;
    using System.Collections.Generic;
    using System.Linq;
    using System.Web;
    using System.Data;
    using System.Data.SqlClient;
    using System.Web.Configuration;

    namespace Sistema_ProcePeso
    {
    public class Datos
    {

    public static bool Autenticar(string usuario, string password)
    {
    string sql = @"SELECT COUNT(*)
    FROM ma_usuarios
    WHERE login_name = @nombre AND Password = @password";


    using (SqlConnection conn = new SqlConnection(System.Configuration.ConfigurationManager.ConnectionStrings["default"].ToString()))
    {
    conn.Open();

    SqlCommand command = new SqlCommand(sql, conn);
    command.Parameters.AddWithValue("@nombre", usuario);

    string hash = Helper.EncodePassword(string.Concat(usuario, password));
    command.Parameters.AddWithValue("@password", hash);

    int count = Convert.ToInt32(command.ExecuteScalar());

    if (count == 0)
    return false;
    else
    return true;

    }
    }

    ------------------------------------------------ Este de la clase loginservice-----------------------
    using System;
    using System.Collections.Generic;
    using System.Data.SqlClient;
    using System.Linq;
    using System.Web;

    namespace Sistema_ProcePeso
    {
    public class LoginService
    {

    public static bool Autenticar(string usuario, string password)
    {
    string sql = @"SELECT COUNT(*)
    FROM MA_USUARIOS
    WHERE NombreLogin = @nombre AND Password = @password";


    using (SqlConnection conn = new SqlConnection("Data Source=localhost;Initial Catalog=VAD10;Persist Security Info=True;User ID=sa;Password=123456;"))
    {
    conn.Open();

    SqlCommand command = new SqlCommand(sql, conn);
    command.Parameters.AddWithValue("@nombre", usuario);

    string hash = Helper.EncodePassword(string.Concat(usuario, password));
    command.Parameters.AddWithValue("@password", hash);

    int count = Convert.ToInt32(command.ExecuteScalar());

    if (count == 0)
    return false;
    else
    return true;

    }
    }
    }
    }
    }
    }

    ResponderEliminar
    Respuestas
    1. hola
      Para que defines la clase "Datos" si despues tienes el mismo codigo en la clase "LoginService" ?
      Se supone que la capa de servicio deberias hacer uso de la capa de datos, pero no se repite el codigo
      saludos

      Eliminar
  192. Hola leandro, veo que tu post es del 2010 y aun pasando 5 años me ha ayudado para mi aplicacion. Muchas Gracias!!!

    ResponderEliminar
  193. Hola Leandro:

    Una consulta. Luego del Login, como se podría hacer para que en algún formulario distinto de la aplicación se pueda saber qué usuario esta Logeado? Por ejemplo si dicho usuario realiza una modificación enviar el usuario como parámetro a otra tabla de la base de datos?

    ResponderEliminar
  194. hola leandro, muy bueno tu blog, una consulta, tengo un proyecto de ejemplo creado en visual studio 2008 SP1 y tengo diferentes paginas default, default2, default3, login, no tengo masterpage, la pagina 1 tiene un link que me redirecciona a la pagina 2 y la 2 un link que me redirecciona la pagina 3, pero cuando cierro sesión 3 al darle atras en el botón de navegacion de mi navegador al principio si me presenta la de login, pero conforme le voy dando click para atras en el boton del navegador ya me presenta las paginas 3, 2 y no se queda en la de login espero tu ayuda saludos

    ResponderEliminar