Vimos en el post anterior cómo montar un sistema de autenticación por token en Django. Ahora veremos como utilizarlo desde Vue.js.
Antes de nada es imprescindible incluir las siguientes librerías en nuestro proyecto Vue:
- Axios: es una librería de JavaScript que nos permite gestionar la programación asíncrona con promesas.
- Vue-Axios: permite utilizar axios desde Vue.js
- Http jwt_decode: es una pequeña biblioteca de navegador que ayuda a decodificar un token JWTs codificado en Base64Url. Nos ayuda a descibrar el token para obtener la marca de tiempo y saber cuando expira.
- Vuex: se trata de una implementación de Flux, y consiste en un patrón de diseño que nos permitirá controlar el estado de nuestra aplicación. Vuex simplifica la forma en que nuestros componentes se comunican entre ellos, centralizando este proceso.
Instalaremos estas librerías en nuestro proyecto node de la siguiente forma:
1 | npm install --save vue-axios axios vuex jwt-decode |
Ahora vamos a crear nuestro main.js, importando todas estas librerías:
1 2 3 4 5 6 7 8 9 | // main.js import axios from 'axios' import VueAxios from 'vue-axios' import jwt_decode from 'jwt-decode' import Vuex from 'vuex' Vue.use(Vuex); Vue.use(VueAxios, axios); // a partir de aquí, nuestro código |
// a partir de aquí, nuestro código
Ahora añadimos el código que nos va a permitir definir todos nuestros métodos en las acciones de Vuex Store. Pero antes de ello os voy a explicar brevemente como funciona Vuex pues considero que es la parte más complicada de entender de este artículo:
Cómo funciona Vuex, a grandes rasgos
Vuex es una librería que nos permite implementar un patrón de gestión de estado en Vue.js. Es una especie de «Store» centralizada para todos los componentes de la aplicación. Y en ella implementamos reglas que garantizan que el estado de nuestra aplicación sólo puede mutar de una forma predecible.
También se integra con la extensión devtools oficial de Vue para proporcionar funciones avanzadas como la depuración zero-config time-travel y la exportación / importación de instantáneas del estado de la aplicación.
En una aplicación Vue.js normalmente tenemos tres partes fundamentales que intervienen en los cambios de estado:
- El estado propiamente dicho, que es lo uqe realmente impulsa nuestra aplicación.
- La vista, que podemos verla como una visualización del estado.
- Las acciones, que son mecanismos para cambiar de un estado a otro en vase, por ejemplo, a imputs del usuario.
Esta podría considerarse una representación extremadamente simple del concepto de one-way data flow y funciona así
Sin embargo ahora podemos plantear como se complicaría esto cuando tenemos varios componentes Vue que deben compartir el estado.
- Podríamos tener varias vistas que comparten una misma porción del estado.
- También podríamos querer implementar acciones que desde diferentes puntos de vista nos lleven a un mismo estado.
Hacer todo esto con componentes anidados, hermanos, padres o hijos, puede dar lugar a código muy inmanejable. Y es aquí donde entra el patrón planteado por Vuex. Con Vuex el esquema de funcionamiento es parecido al siguiente, si bien lo entenderemos mejor en el ejemplo:
A pesar de que Vuex nos ayuda a lidiar con la gestión compartida del estado, también viene con el costo de más conceptos y repeticiones. Es una compensación entre la productividad a corto y largo plazo.
Si estás construyendo un SPA de mediano o gran tamaño, es probable que te topes con situaciones que te exijan poder manejar el estado fuera de tus componentes Vue. Y como os he comentado, Vuex será la solución.
Creamos nuestra instancia Vuex para autenticar:
Lo que haremos es utilizar Vuex para crear un estado que almacene JWT. También crearemos endpoints y mutaciones para poder manipular el estado de JWT
Observa que para almacenar nuestro token utilizamos almacenamiento local (localStorage). Esto es recomendable ya que Vuex Store se reiniciará si el usuario actualiza la página. Deberá mantener su token almacenado persistentemente de forma local para no tener que reiniciar sesión cada vez que refresque la pantalla.
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 | // main.js const store = new Vuex.Store({ state: { jwt: localStorage.getItem('t'), endpoints: { obtainJWT: 'http://0.0.0.0:8000/auth/obtain_token', refreshJWT: 'http://0.0.0.0:8000/auth/refresh_token' } }, mutations: { updateToken(state, newToken){ localStorage.setItem('t', newToken); state.jwt = newToken; }, removeToken(state){ localStorage.removeItem('t'); state.jwt = null; } }, actions: { } }) |
Pero todavía tenemos que definir las acciones en nuestra Vuex Store. Como verás es bastante sencillo e intuitivo, no hace falta explicar mucho:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 | actions:{ obtainToken(username, password){ const payload = { username: username, password: password } axios.post(this.state.endpoints.obtainJWT, payload) .then((response)=>{ this.commit('updateToken', response.data.token); }) .catch((error)=>{ console.log(error); }) }, refreshToken(){ const payload = { token: this.state.jwt } axios.post(this.state.endpoints.refreshJWT, payload) .then((response)=>{ this.commit('updateToken', response.data.token) }) .catch((error)=>{ console.log(error) }) } inspectToken(){ // WE WILL ADD THIS LATER } } |
Cómo inspeccionar la marca de tiempo de vencimiento de JWT
Nuestro programa deberá inspeccionar, cada cierto tiempo, el JWT con el objetivo de actualizarlo cuando caduque. Para decidificar dicho token, tal como he indicado antes, utilizaremos jwt decode y decodificaremos el atributo orig_iat. El parámetro orig_iat es la marca de tiempo de emisión del primer token en la cadena de tokens. En este ejemplo vamos a establecer 1 día de vida.
Añadamos esto a la acción, todavía no implementada, inspectToken:
1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 | inspectToken(){ const token = this.state.jwt; if(token){ const decoded = jwt_decode(token); const exp = decoded.exp const orig_iat = decode.orig_iat if(exp - (Date.now()/1000) < 1800 && (Date.now()/1000) - orig_iat < 84600){ this.dispatch('refreshToken') } else if (exp -(Date.now()/1000) < 1800){ // No hacer nada } else { // Re-login } } } |
El funcionamiento es el siguiente:
- Si expira en 30 minutos (1800 segundos) y no está llegando a su duración (1 día – 30 minutos = 86400-1800 = 84600), entonces actualizamos el token.
- Si expira en 30 minutos y está llegando a su fin, no actualizar.
- Si ha expirado, no actualizamos y además iniciamos el proceso de re-obtención del token (invocamos a la pantalla de inicio de sesión, por ejemplo).
Nuestra aplicación Django + Vue.js va tomando forma. Continuaremos en el siguiente post. Por favor, comparte si te ha resultado interesante. ?
Excelente artículo, sería tan amable de compartir todo el código fuente