La autenticación es una de las partes más importantes de cualquier aplicación web. En este tutorial, analizaremos los sistemas de autenticación basados en token y en qué se diferencian de los sistemas de inicio de sesión tradicionales. Al final de este tutorial, verá una demostración completamente funcional escrita en AngularJS y NodeJS.
También puede encontrar una amplia selección de scripts y aplicaciones de autenticación listos para usar en Envato Market, como:
O, si está luchando con un error en su código AngularJS, puede enviarlo a araneux en Envato Studio para que lo solucione.
Antes de continuar con un sistema de autenticación basado en token, primero echemos un vistazo a un sistema de autenticación tradicional.
Todo está bien hasta este punto. La aplicación web funciona bien y puede autenticar a los usuarios para que puedan acceder a los puntos finales restringidos; sin embargo, ¿qué sucede cuando desea desarrollar otro cliente, digamos para Android, para su aplicación? ¿Podrá usar la aplicación actual para autenticar clientes móviles y para servir contenido restringido? En su estado actual, no. Existen dos motivos principales para esto:
En este caso, necesitas una aplicación independiente del cliente..
En la autenticación basada en token, las cookies y las sesiones no se utilizarán. Se utilizará un token para autenticar a un usuario para cada solicitud al servidor. Rediseñemos el primer escenario con autenticación basada en token..
Utilizará el siguiente flujo de control:
En este caso, no hemos devuelto sesión o cookie, y no hemos devuelto ningún contenido HTML. Eso significa que podemos usar esta arquitectura para cualquier cliente para una aplicación específica. Puedes ver el esquema de arquitectura abajo:
Entonces, ¿qué es este JWT?
JWT significa Token web JSON y es un formato de token utilizado en los encabezados de autorización. Este token le ayuda a diseñar la comunicación entre dos sistemas de forma segura. Vamos a reformular JWT como el "token de portador" para los fines de este tutorial. Un token de portador consta de tres partes: encabezado, carga útil y firma.
Puede ver el esquema JWT y un token de ejemplo a continuación;
No es necesario implementar el generador de token de portador ya que puede encontrar versiones que ya existen en varios idiomas. Puedes ver algunos de ellos a continuación:
Idioma | URL de la biblioteca |
---|---|
NodeJS | http://github.com/auth0/node-jsonwebtoken |
PHP | http://github.com/firebase/php-jwt |
Java | http://github.com/auth0/java-jwt |
Rubí | http://github.com/progrium/ruby-jwt |
.RED | http://github.com/AzureAD/azure-activedirectory-identitymodel-extensions-for-dotnet |
Pitón | http://github.com/progrium/pyjwt/ |
Después de cubrir información básica sobre la autenticación basada en token, ahora podemos continuar con un ejemplo práctico. Eche un vistazo al siguiente esquema, después de lo cual lo analizaremos con más detalle:
https://api.yourexampleapp.com
. Si mucha gente usa la aplicación, es posible que se requieran varios servidores para realizar la operación solicitada.https://api.yourexampleapp.com
, Primero, el equilibrador de carga manejará una solicitud y luego redirigirá al cliente a un servidor específico..https://api.yourexampleapp.com
, la aplicación de back-end interceptará el encabezado de solicitud y extraerá la información del token del encabezado de autorización. Se realizará una consulta de base de datos utilizando este token. Si este token es válido y tiene el permiso requerido para acceder al punto final solicitado, continuará. Si no, devolverá un código de respuesta 403 (que indica un estado prohibido).La autenticación basada en token viene con varias ventajas que resuelven problemas graves. Algunos de ellos son los siguientes:
temperatura
Carpeta, al menos por primera vez. Digamos que tiene varios servidores y se crea una sesión en el primer servidor. Cuando realiza otra solicitud y su solicitud cae en otro servidor, la información de la sesión no existirá y recibirá una respuesta "no autorizada". Lo sé, puedes resolver eso con una sesión pegajosa. Sin embargo, en la autenticación basada en token, este caso se resuelve de forma natural. No hay ningún problema de sesión persistente, porque el token de solicitud se intercepta en cada solicitud en cualquier servidor.Esas son las ventajas más comunes de la autenticación y la comunicación basadas en token. Ese es el final de la charla teórica y arquitectónica sobre la autenticación basada en token. Tiempo para un ejemplo práctico..
Verá dos aplicaciones para demostrar la autenticación basada en token:
En el proyecto back-end, habrá implementaciones de servicio y los resultados del servicio estarán en formato JSON. No hay vista devuelta en servicios. En el proyecto de front-end, habrá un proyecto de AngularJS para HTML de front-end y luego los servicios de AngularJS rellenarán la aplicación de front-end para realizar solicitudes a los servicios de back-end..
En el proyecto back-end, hay tres archivos principales:
paquete.json
es para la gestión de dependencias.modelos \ usuario.js
contiene un modelo de usuario que se utilizará para realizar operaciones de base de datos sobre usuarios.server.js
es para el arranque del proyecto y el manejo de solicitudes.¡Eso es! Este proyecto es muy simple, para que pueda comprender el concepto principal fácilmente sin hacer una inmersión profunda.
"name": "angular-restful-auth", "version": "0.0.1", "dependencies": "express": "4.x", "body-parser": "~ 1.0.0" , "morgan": "más reciente", "mangosta": "3.8.8", "jsonwebtoken": "0.4.0", "motores": "nodo": "> = 0.10.0"
paquete.json
Contiene dependencias para el proyecto: exprimir
para MVC, analizador corporal
para simular el manejo de solicitudes posteriores en NodeJS, Morgan
para solicitud de registro, mangosta
para que nuestro marco ORM se conecte a MongoDB, y jsonwebtoken
para crear tokens JWT utilizando nuestro modelo de usuario. También hay un atributo llamado motores
eso dice que este proyecto se hace usando la versión NodeJS> = 0.10.0. Esto es útil para servicios de PaaS como Heroku. También cubriremos ese tema en otra sección..
var mongoose = require ('mongoose'); var Schema = mongoose.Scema; var UserSchema = new Schema (email: String, contraseña: String, token: String); module.exports = mongoose.model ('User', UserSchema);
Dijimos que generaríamos un token utilizando la carga útil del modelo de usuario. Este modelo nos ayuda a realizar operaciones de usuario en MongoDB. En Usuario.js
, el esquema de usuario se define y el modelo de usuario se crea utilizando un modelo de mangosta. Este modelo está listo para operaciones de base de datos..
Nuestras dependencias están definidas y nuestro modelo de usuario, así que ahora vamos a combinar todas aquellas para construir un servicio para manejar solicitudes específicas..
// Módulos requeridos var express = require ("express"); var morgan = require ("morgan"); var bodyParser = require ("body-parser"); var jwt = require ("jsonwebtoken"); var mongoose = require ("mongoose"); var app = express ();
En NodeJS, puede incluir un módulo en su proyecto usando exigir
. Primero, necesitamos importar los módulos necesarios en el proyecto:
var port = process.env.PORT || 3001; var User = require ('./ models / User'); // Conectar a DB mongoose.connect (process.env.MONGO_URL);
Nuestro servicio servirá a través de un puerto específico. Si alguna variable de puerto está definida en las variables de entorno del sistema, puede usar eso, o hemos definido el puerto 3001
. Después de eso, se incluye el modelo de Usuario y se establece la conexión de la base de datos para realizar algunas operaciones de usuario. No olvides definir una variable de entorno.-MONGO_URL
-para la URL de conexión a la base de datos.
app.use (bodyParser.urlencoded (extended: true)); app.use (bodyParser.json ()); app.use (morgan ("dev")); app.use (función (req, res, next) res.setHeader ('Access-Control-Allow-Origin', '*'); res.setHeader ('Access-Control-Allow-Methods', 'GET, POST '); res.setHeader (' Access-Control-Allow-Headers ',' X-Requested-With, tipo de contenido, Autorización '); next (););
En la sección anterior, hemos realizado algunas configuraciones para simular el manejo de una solicitud HTTP en NodeJS mediante Express. Estamos permitiendo que las solicitudes provengan de diferentes dominios para desarrollar un sistema independiente del cliente. Si no lo permite, activará un error CORS (Intercambio de solicitud de origen cruzado) en el navegador web.
Acceso-Control-Permitir-Origen
permitido para todos los dominios.ENVIAR
y OBTENER
peticiones a este servicio.Solicitado con X
y tipo de contenido
se permiten encabezados.app.post ('/ authenticate', function (req, res) User.findOne (email: req.body.email, contraseña: req.body.password, function (err, usuario) if (err) res.json (type: false, data: "Ocurrió un error:" + err); else if (usuario) res.json (type: true, data: user, token: user.token); else res.json (type: false, data: "Email / password incorrectos");););
Hemos importado todos los módulos necesarios y definido nuestra configuración, por lo que ahora es el momento de definir los manejadores de solicitudes. En el código anterior, cada vez que realice una ENVIAR
solicitud de /autenticar
Con nombre de usuario y contraseña, obtendrás un JWT
simbólico. Primero, la consulta de la base de datos se procesa utilizando un nombre de usuario y una contraseña. Si existe un usuario, los datos del usuario se devolverán con su token. Pero, ¿qué pasa si no hay tal usuario que coincida con el nombre de usuario y / o contraseña?
app.post ('/ signin', function (req, res) User.findOne (email: req.body.email, contraseña: req.body.password, function (err, usuario) if (err) res.json (type: false, data: "Ocurrió un error:" + err); else if (usuario) res.json (type: false, data: "¡El usuario ya existe!"); else var userModel = new User (); userModel.email = req.body.email; userModel.password = req.body.password; userModel.save (función (err, usuario) user.token = jwt.sign (usuario , process.env.JWT_SECRET); user.save (function (err, user1) res.json (type: true, data: user1, token: user1.token););)); );
Cuando haces un ENVIAR
solicitud de /registrarse
con nombre de usuario y contraseña, se creará un nuevo usuario utilizando la información del usuario publicada. Sobre el XIX
En la línea, puede ver que se genera un nuevo token JSON utilizando el jsonwebtoken
módulo, que ha sido asignado a la JWT
variable. La parte de autenticación está bien. ¿Qué pasa si tratamos de acceder a un punto final restringido? ¿Cómo podemos lograr acceder a ese punto final??
app.get ('/ me', asegurarAuthorized, function (req, res) User.findOne (token: req.token, function (err, usuario) if (err) res.json (type: false , datos: "Ocurrió un error:" + err); else res.json (type: true, data: user);););
Cuando haces un OBTENER
solicitud de /yo
, obtendrá la información del usuario actual, pero para continuar con el punto final solicitado, el asegurarAutorizado
la función será ejecutada.
función asegurarAutorizada (req, res, next) var bearerToken; var bearerHeader = req.headers ["autorización"]; if (typeof bearerHeader! == 'undefined') var bearer = bearerHeader.split (""); bearerToken = portador [1]; req.token = bearerToken; siguiente(); else res.send (403);
En esta función, los encabezados de solicitud son interceptados y la autorización
Se extrae la cabecera. Si existe un token de portador en este encabezado, ese token se asigna a req.token
para ser utilizado a lo largo de la solicitud, y la solicitud puede continuar utilizando siguiente()
. Si no existe un token, obtendrá una respuesta 403 (Prohibido). Volvamos al manejador. /yo
, y use req.token
para obtener datos de usuario con este token. Cada vez que crea un nuevo usuario, se genera un token y se guarda en el modelo de usuario en la base de datos. Esos tokens son únicos..
Tenemos solo tres manejadores para este proyecto simple. Después de eso, verás;
process.on ('uncaughtException', function (err) console.log (err););
La aplicación NodeJS puede bloquearse si se produce un error. Con el código anterior, ese bloqueo se evita y se imprime un registro de errores en la consola. Y finalmente, podemos iniciar el servidor usando el siguiente fragmento de código.
// Iniciar el servidor app.listen (puerto, función () console.log ("Servidor Express escuchando en el puerto" + puerto););
Para resumir:
Hemos terminado con el servicio de back-end. Para que pueda ser utilizado por varios clientes, puede implementar esta sencilla aplicación de servidor en sus servidores, o tal vez puede implementar en Heroku. Hay un archivo llamado Procfile
en la carpeta raíz del proyecto. Vamos a desplegar nuestro servicio en Heroku.
Puedes clonar el proyecto de back-end desde este repositorio de GitHub.
No discutiré cómo crear una aplicación en Heroku; puede consultar este artículo para crear una aplicación Heroku si no lo ha hecho antes. Después de crear su aplicación Heroku, puede agregar un destino a su proyecto actual usando el siguiente comando:
git remoto añadir heroku
Ahora ha clonado un proyecto y ha agregado un destino. Después git añadir
y git commit
, puedes empujar tu código a Heroku ejecutando git push heroku master
. Cuando impulsas un proyecto con éxito, Heroku realizará la npm instalar
comando para descargar dependencias en el temperatura
carpeta en Heroku. Después de eso, iniciará su aplicación y podrá acceder a su servicio utilizando el protocolo HTTP.
En el proyecto de front-end, verá un proyecto AngularJS. Aquí, solo mencionaré las secciones principales en el proyecto de front-end, porque AngularJS no es algo que se pueda cubrir en un solo tutorial.
Puedes clonar el proyecto desde este repositorio de GitHub. En este proyecto, verá la siguiente estructura de carpetas:
ngStorage.js
es una biblioteca para que AngularJS manipule las operaciones de almacenamiento local. Además, hay un diseño principal. index.html
y parciales que extienden el trazado principal bajo la parciales
carpeta. controllers.js
Es para definir nuestras acciones de controlador en el front-end.. servicios.js
es para hacer solicitudes de servicio a nuestro servicio que mencioné en el proyecto anterior. Tenemos un archivo tipo bootstrap llamado app.js
y en este archivo se aplican las configuraciones y las importaciones de módulos. Finalmente, cliente.js
es para servir archivos HTML estáticos (o simplemente index.html
, en este caso); esto nos ayuda a servir archivos HTML estáticos cuando implementa en un servidor sin usar Apache o cualquier otro servidor web.
...
En el archivo HTML de diseño principal, se incluyen todos los archivos JavaScript necesarios para las bibliotecas relacionadas con AngularJS, así como nuestro controlador personalizado, servicio y archivo de aplicación.
'uso estricto'; / * Controladores * / angular.module ('angularRestfulAuth') .controller ('HomeCtrl', ['$ rootScope', '$ scope', '$ location', '$ localStorage', 'Main', función ($ rootScope, $ scope, $ location, $ localStorage, Main) $ scope.signin = function () var formData = email: $ scope.email, password: $ scope.password Main.signin (formData, function (res) if (res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/";, function () $ rootScope.error = 'Falló el inicio de sesión';); $ scope.signup = function () var formData = email: $ scope.email, contraseña: $ scope.password Main.save (formData, function (res) if ( res.type == false) alert (res.data) else $ localStorage.token = res.data.token; window.location = "/", function () $ rootScope.error = 'No se pudo registro ';); $ scope.me = function () Main.me (function (res) $ scope.myDetails = res;, function () $ rootScope.error =' No se pudieron obtener los detalles '; ); $ scope.logout = function () Main.logout (fu nction () window.location = "/", function () alert ("¡Error al cerrar sesión!"); ); ; $ scope.token = $ localStorage.token; ])
En el código anterior, el HomeCtrl
Se define el controlador y algunos módulos requeridos se inyectan como $ rootScope
y $ alcance
. La inyección de dependencia es una de las propiedades más fuertes de AngularJS. $ alcance
es la variable puente entre los controladores y las vistas en AngularJS que significa que puede usar prueba
a la vista si lo definió en un controlador específico como $ scope.test =…
En este controlador, se definen algunas funciones de utilidad, tales como:
registrarse
para configurar un botón de inicio de sesión en el formulario de inicio de sesiónRegístrate
para el manejo del formulario de registroyo
para asignar el botón Me en el diseñoEn el diseño principal, en la lista del menú principal, puede ver el controlador de datos ng
atributo con un valor HomeCtrl
. Eso significa que este menú. dom
elemento puede compartir alcance con HomeCtrl
. Al hacer clic en el botón de registro en el formulario, se ejecutará la función de registro en el archivo del controlador, y en esta función, el servicio de registro se utiliza desde Principal
Servicio que ya está inyectado en este controlador..
La estructura principal es ver -> controlador -> servicio
. Este servicio hace solicitudes simples de Ajax al back-end para obtener datos específicos.
'uso estricto'; angular.module ('angularRestfulAuth') .factory ('Main', ['$ http', '$ localStorage', function ($ http, $ localStorage) var baseUrl = "your_service_url"; function changeUser (user) angular. extend (currentUser, user); function urlBase64Decode (str) var output = str.replace ('-', '+'). replace ('_', '/'); switch (output.length% 4) caso 0: interrupción; caso 2: salida + = '=='; interrupción; caso 3: salida + = '='; interrupción; predeterminado: lanzar 'Cadena base64url ilegal!'; return window.atob (salida); función getUserFromToken () var token = $ localStorage.token; var user = ; if (typeof token! == 'undefined') var encoded = token.split ('.') [1]; user = JSON. parse (urlBase64Decode (codificado)); usuario de retorno; var currentUser = getUserFromToken (); return save: function (data, success, error) $ http.post (baseUrl + '/ signin', data) .success ( success) .error (error), signin: function (data, success, error) $ http.post (baseUrl + '/ authenticate', data) .success (success) .error (error), me: function ( éxito, error) $ htt p.get (baseUrl + '/me').success(success).error(error), logout: function (success) changeUser (); eliminar $ localStorage.token; éxito(); ; ]);
En el código anterior, puede ver funciones de servicio como realizar solicitudes de autenticación. En controller.js, ya te habrás dado cuenta de que hay funciones como Main.me
. Esta Principal
El servicio se ha inyectado en el controlador, y en el controlador, los servicios que pertenecen a este servicio se llaman directamente.
Estas funciones son simplemente solicitudes Ajax a nuestro servicio que implementamos juntos. No olvides poner la URL del servicio en. baseUrl
En el código anterior. Al implementar su servicio en Heroku, obtendrá una URL de servicio como appname.herokuapp.com
. En el código anterior, establecerás var baseUrl = "appname.herokuapp.com"
.
En la parte de registro o inicio de sesión de la aplicación, el token de portador responde a la solicitud y este token se guarda en el almacenamiento local. Siempre que realice una solicitud a un servicio en el back-end, debe poner este token en los encabezados. Puedes hacer esto usando los interceptores AngularJS.
$ httpProvider.interceptors.push (['$ q', '$ location', '$ localStorage', función ($ q, $ location, $ localStorage) return 'request': function (config) config.headers = config.headers || ; if ($ localStorage.token) config.headers.Authorization = 'Bearer' + $ localStorage.token; return config;, 'responseError': function (response) if (response. status === 401 || response.status === 403) $ location.path ('/ signin'); return $ q.reject (response);;]);
En el código anterior, cada solicitud es interceptada y un encabezado y valor de autorización se colocan en los encabezados.
En el proyecto de front-end, tenemos algunas páginas parciales como registrarse
, Regístrate
, detalles del perfil
, y vb
. Estas páginas parciales están relacionadas con controladores específicos. Puedes ver esa relación en app.js
:
angular.module ('angularRestfulAuth', ['ngStorage', 'ngRoute']) .config (['$ routeProvider', '$ httpProvider', function ($ routeProvider, $ httpProvider) $ routeProvider. when ('/', templateUrl: 'partials / home.html', controller: 'HomeCtrl'). when ('/ signin', templateUrl: 'partials / signin.html', controller: 'HomeCtrl'). when ('/ signup ', templateUrl:' partials / signup.html ', controller:' HomeCtrl '). when (' / me ', templateUrl:' partials / me.html ', controller:' HomeCtrl '). De lo contrario ( redirigir a: '/' );
Como puede comprender fácilmente en el código anterior, cuando vaya a /
, la home.html
La página será renderizada. Otro ejemplo: si vas a /Regístrate
, signup.html
será rendido. Esta operación de representación se realizará en el navegador, no en el lado del servidor.
Puede ver cómo se pone en práctica todo lo que discutimos en este tutorial al ver esta demostración de trabajo.
Los sistemas de autenticación basados en token le ayudan a construir un sistema de autenticación / autorización mientras desarrolla servicios independientes del cliente. Al utilizar esta tecnología, solo se centrará en sus servicios (o API).
La parte de autenticación / autorización será manejada por el sistema de autenticación basado en token como una capa frente a sus servicios. Puede acceder y usar los servicios de cualquier cliente como navegadores web, Android, iOS o un cliente de escritorio.
Y si está buscando soluciones ya hechas, consulte los scripts de autenticación y las aplicaciones en Envato Market..