jueves, 8 de diciembre de 2011

How to: Create Login with Ajax and CodeIgniter // Crear Login con Ajax y CodeIgniter

Bueno, hola otra vez :D. Últimamente debido a mis pequeños momentos de ocio que he podido tener en el trabajo, me he dedicado a aprender un poco más (nunca esta de mas conocer un poco acerca de las tecnologías actuales :) ) acerca de programación web con PHP 5 y el framework CodeIgniter (version 2.1.0).

Haciendo un poco de lectura en la documentación oficial del framework he decidido llevar a la practica lo que ahí se expone, claro esta, incluyendo una que otra funcionalidad con la libraría de javascript JQuery (usando esta para las llamadas ajax a los controladores [llamadas a bases de datos y demás cosas]).

En este punto doy por entendido que ya tienes Apache, PHP y MySQL instalado, configurado y el Framework CodeIgniter configurado con la base de datos que vamos a usar, de no ser así leer la documentación oficial.
En la base de datos debemos tener una tabla llamada user/usuario/persona/ o como querrás llamarla, eso es irrelevante (en mi caso user), acá una imagen de la estructura:


Ahora crearemos.
  • una vista; la cual llamaremos userLogin.php y esta estara situada en application/views
  • un Controlador; UserController.php y lo guardamos en application/controllers
  • y por ultimo un modelo; UserModel.php, application/models
 Creamos en la raíz del proyecto dos carpetas. llamadas
  • css. acá guardaremos (obviamente) las hojas de estilo y las imágenes del sistema
  • js. carpeta que almacenará la libreria de jquery y jquery-ui.min, entre otros scripts que deseemos crear.
Bueno una vez ya definido la estructura del sistema, ahora definiremos la lógica bajo la cual programaremos el Login.
Por el momento los campos que nos interesan de la tabla user con : password, nickname, email. mediante estos se validara al usuario.
Las llamadas a la base de datos serán de modo asíncronas  lo cual nos va a permitir consultar la base de datos sin necesidad de refrescar el sitio entero.
Empecemos entonces creando el Modelo:

<?php

/**
 * @author McCubo
 * @copyright 2011
 * @license GPL V2.
 */
class UserModel extends CI_Model {

    public function __construct() {
        parent::__construct(); //Llamada al constructor padre. Must
    }

    /**
     * @author McCubo
     * @param String $ident Nickname o Email del usuario a buscar
     * @return userResult objeto usuario del respectivo nick o email, null si no existe
     */
    public function getUserFromIdent($ident) {
        $this->db->where('nickname', $ident);
        $this->db->or_where('email', $ident);
        $userResult = $this->db->get('user')->row();
        return $userResult;
    }
}
?>

Como podemos ver, la única función del modelo es ejecutar la consulta a la base de datos, creamos la consulta y la convertimos a un único objeto ->row(); retorna null si no existe usuario asociado al identificador proporcionando.
Cabe hacer notar que los campos nickname como email son únicos en la tabla user.
Ahora crearemos el Controlador:
<?php

/**
 * @author McCubo
 * @copyright 2011
 */
class UserController extends CI_Controller {

    public function __construct() {
        parent::__construct();
        $this->load->model('UserModel');
        $this->load->library(array('form_validation', 'session', 'encrypt'));
        $this->load->helper(array('security', 'form');
    }

    // Primer metodo al llamar al controlador
    public function index() {
        $this->load->view('userLogin');
    }
    
    // Metodo para logear usuario
    public function doLogin() {
        $this->form_validation->set_rules('userIdent', 'User Ident', 'required');
        $this->form_validation->set_rules('password', 'Password', 'required');
        if (!$this->form_validation->run()) {   // Falló la validación
            $message = validation_errors();
            $background = 'images/error.png';
        } else {    # Validacion paso sin problemas
            $userIdent = $this->input->post('userIdent');
            $password = $this->input->post('password');
            $user = $this->UserModel->getUserFromIdent($userIdent);
            if ($user != null) {    // usuario con $userIdent existe en systema
                if ($user->password == do_hash($password, 'md5')) { //Encriptamos la password (en la db se guarda encriptada)
                    $sessionData = array(
                        'username' => $user->nickname,
                        'email' => $user->email,
                        'loggedIn' => true
                    );
                    $this->session->set_userdata($sessionData); // agregamos info a la session
                    $message = "Now You're Logged in as " . $this->session->userdata('username');
                    $background = 'images/ok.png';
                } else {    // Wrong password
                    $message = "Password Incorrect!";
                    $background = 'images/error.png';
                }
            } else {    // user with $userIdent not Found in DB
                $message = "user Ident. " . $userIdent . " not found on McCubos System";
                $background = 'images/error.png';
            }
        }
        $returnedData = array(
            'message' => $message,
            'background' => $background
        );
        $jsonData = json_encode($returnedData);
        echo $jsonData; //array asociativo json format
    }
}
?>

Bueno una breve explicación. el método obtiene el identificado del usuario (puede ser nickname o email) y consulta si existe un registro con la información proporcionada (se ejecuta en el modelo). si el usuario existe, verificamos que la contraseña del formulario es igual a la contraseña que esta almacenada en la base de datos (codificadas con MD5). si ambas contraseñas coinciden, significa que la contraseña corresponde al registro con el identificador del usuario, iniciamos sesión con la cual controlaremos la seguridad de la pagina (mediante la propiedad loggedIn). de lo contrario se almacena en la variable mensaje el mensaje correspondiente al fallo que ocurrió en el proceso.
Al final imprimimos un arreglo en formato json.

para terminar y hacer funcionar el sistema de login creamos la vista, osea el formulario que llamará de forma asíncrona al método doLogin del controlador UserController.
La Vista:

<!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.01 Transitional//EN">
<html>
    <head>
        <title>Login to McCubo's System</title>
        <meta http-equiv="Content-Type" content="text/html; charset=UTF-8">
        <script type="text/javascript" src="<?= base_url("js/jquery.js"); ?>"></script>
        <script type="text/javascript" src="<?= base_url("js/jquery-ui.min.js"); ?>"></script>
        <link rel="stylesheet" href="<?= base_url("css/main.css"); ?>" />
        <link rel="stylesheet" href="<?= base_url("css/smoothness/jquery-ui-1.8.16.custom.css"); ?>" />
        <script type="text/javascript" >
                $("#loginButton").click(function(){
                    var base_url = '<?= base_url('css'); ?>';
                    var doLoginMethodUrl = '<?= site_url("userController/doLogin") ?>';
                    var userIdent = $("#userIdent").val();
                    var password = $("#userPassword").val();                    
                    $.ajax({
                        type: "POST",
                        url: doLoginMethodUrl,
                        dataType: "json",                        
                        data: 'userIdent='+userIdent+"&password="+password,
                        cache: false,                        
                        success: function(data){
                            $(".userDialog")
                            .html(data.message)
                            .css("background", 'url('+base_url+"/"+data.background+') no-repeat right')
                            .dialog({
                                autoOpen: true,
                                show: "explode",
                                hide: "FadeOut"
                            })
                        }
                    });
                    return false;
                });
            });
        </script>
        <style type="text/css" >
            .userDialog{
                display: none; 
            }
        </style>
    </head>
    <body>
        <div class="userDialog"></div>
        <fieldset style="width: 300px;"><legend>user Data</legend>
            <?php form_open() ?>
            <label class="form">Nickname/Email</label><input type="text" name="userIdent" id="userIdent" />
            <label class="form">Password</label><input type="password" name="userPassword" id="userPassword" />
            <input type="submit" id="loginButton" name="loginButton" value ="Log in" />
            <?php form_close() ?>
        </fieldset>
    </body>
</html>

De esta forma hemos creado un login el cual efectua las llamadas al controlador de forma asíncrona y respetando el modelo vista controlador(MVC) separando la lógica de la aplicación :D.

Los resultados de un login exitoso seria como el de la imagen (user ident proporcionado nickname)
o  (user ident proporcionado email):

Error en la validación de Campos:


Identificador Existe pero contraseña no corresponde a dicho registro:

Identificador no existe:

Bueno aplicarle estilo a la pagina principal lo dejo a ustedes, ya que en esta entrada no es CSS el objetivo de aprendizaje. x)
Por ahora dejamos esta entrada hasta acá, pero próximamente, si e Optimus Prime lo quiere y mi estado de animo no esta muy deteriorado, publicare futuros ejemplo de mantenimiento a la base de datos (Insert, Update, Delete) claro esta con Ajax ;).
Recuerden crear los directorio en el raiz, dicho directorios deben tener este contenido (o similar).


y el Contenedor del Css de jquery y del Sitio:
Espero haberme dado a entender con este pequeño post, también que a alguien ahí afuera le sirva o almenos lo tome de guía para mejorarlo o algo así x). Nos vemos en el siguente post.
Pásenla bien y gracias por leerme :)

11 comentarios:

  1. muy bueno! muchas gracias por ayuda como esta!

    ResponderEliminar
  2. Bueno, podrias subir los fuentes soy nuevo en programacion y CodeIgniter encontre errores en el manuar v2.1 y no he podido enganchar la base de datos, saludos

    ResponderEliminar
  3. @An�nimo Hola a los que comentan, primero:
    . Me alegra mucho que el ejemplo te parezca bueno, esa es la intención con la cual publico todos los articulos, mas que nada para dar una guia y no un sistema totalmente funcional.
    Segundo:
    Los fuentes practicamente estan solo de copiar y pegar y llamar a los archivos como indico al inicio del articulo, repasalo y veras lo que digo, no subo el proyecto completo porque tendria que subir el Framework y eso pesa mucho. ademas si me dices que problemas son, quizas podriamos darle una solucion con la DB. segui comentando y buscamos solucion, a mi me fue mas que bien la documentacion oficial de la version 2.1 (que es la que uso en este ejemplo).
    Saludos a ambos!!!

    ResponderEliminar
  4. porque no usas sessiones en tu ejemplo? se supone que es un login.

    ResponderEliminar
    Respuestas
    1. Hola... no se si en realidad has leído el articulo, y aun mas importante el código.
      y tienes razón NO uso sesiones de PHP, porque uso la librería de Codeigniter (session). en el código esta, acá te lo muestro.
      $sessionData = array(
      'username' => $user->nickname,
      'email' => $user->email,
      'loggedIn' => true
      );
      $this->session->set_userdata($sessionData); // agregamos info a la session

      Deberías leer la guía Oficial:
      http://codeigniter.com/user_guide/libraries/sessions.html
      Espero sea de ayuda... y lee el articulo, te lo recomiendo :D

      Eliminar
  5. Estoy haciendo un login siguiendo este tutorial pero, cuando el login es validado, al redirigir a otro controlador en la llamada a AJAX (al controlador de la parte privada) los datos de sesión se pierden y no puedo hacer validaciones. ¿cual crees que puede ser el problema?

    ResponderEliminar
    Respuestas
    1. Hola, bueno, me imagino que el redirect lo estas haciendo en el success de la llamada ajax del login

      (que te recomiendo que lo hagas mediante jquery, ex. : $(location).attr('href', "ruta_controller");).

      si es asi, asegurate que tienes cargado la libreria de 'session' en el otro controller (o configurala para que sea cargada de forma global).
      Lo que no capto es que quieres decir con: "al controlador de la parte privada".
      Espero sea de ayuda. comenta si el problema persiste. Saludos!

      Eliminar
  6. estas haciendo echo en un controlador! eso no es bueno!
    No se debe hacer echo desde ningun controlador en CodeIgniter, no es que no se pueda! tu codigo lo ejemplifica! pero de acuerdo a las reglas de seguridad y de logica del modelo MVC el Controlador no imprime nada! no debe!

    Ahora! como se haria un login con respuesta JSON? bueno si las credenciales son invalidas! guardas la respuesta en un arreglo y lo pasas a otra vista! esa vista puede ser solo para mensajes! el html o texto plano que quieres mostrar! al cargar esa vista con los datos de tus variables! tu codigo AJAX recibe eso como respuesta! y lo colocas donde quieras en la pagina actual donde se esta ejecutando todo!

    Mi pregunta seria! hay alguna otra forma aparte de la presentada y la planteada x mi?

    ResponderEliminar
    Respuestas
    1. Hola Carlos, pues lo de la impresión en el controlador pueda que tengas razón, aunque cuando estas trabajando lo que importa son los resultados obtenidos, y creo que realizar un echo para imprimir un json en una llamada ajax no se te va a venir abajo el mundo x). igual no soy un experto en programación así que acepto que no se debería, pero funciona :). ademas todas las entradas que publico en el blog son simplemente la forma en la cual yo entiendo lo que leo cuando estoy aprendiendo (ajax, mysql, php, java). En ningún momento he dicho que las cosas se deben de realizar como yo lo hago/entiendo. si quieres seguir standars de MVC deberías leer/comprar una fuente especializada en dicho tema.

      Aunque gracias por el comentario, los lectores deberían de tomarlo en cuenta.
      Saludos!

      Eliminar
    2. Hey! gracias x responder. Vamos! no necesito comprar libros de MVC hay mucha informacion en internet!
      Tampoco creo que mi comentario haya sido tan imperativo, fue una observacion! Ademas una de las razones por las que no es bueno imprimir echo´s en un controlador es x seguridad!
      Creo que esta demas decir cuales son esas razone de seguridad! Ahora como dices! si crees que tu aplicacion vagamente sera atacada.... pues que mas da! jaja!

      Eliminar
    3. Hey hola, la verdad no se si no entendiste mi respuesta a tu comentario o no me di a entender... vamos la voy a poner fácil x).
      Con las entradas en este blog (aparte que son un apunte personal) la principal razón es ser una guía introductoria. no es algo asi bien avanzado, por eso nunca (o casi nunca) le doy seguimiento a un tema, con parte 2,3,4, etc. así que, como guía introductoria deberías de tener en cuenta que no se va a cubrir todos los puntos que pueden ser tratados en un tema (igual creo que no podría abarcar todos los temas). simplemente estudio la programación, y lo que me gusta lo publico.
      Y quizá fue el tono en el que me exprese, pero en verdad aprecio tus comentarios constructivos acá en el blog.
      Saludos!

      Eliminar