Single Page Apps con Vue.js y Flask: Autenticación JWT

    Autenticación JWT

    Bienvenido a la sexta entrega de esta serie de tutoriales de varias partes sobre desarrollo web full-stack usando Vue.js y Flask. En esta publicación, mostraré una forma de usar la autenticación JSON Web Token (JWT).

    El código de esta publicación se puede encontrar en mi cuenta de GitHub en la rama SixthPost .

    Contenido de la serie

    • Seup y familiarización con VueJS
    • Navegando por el enrutador Vue
    • Gestión de estado con Vuex
    • API RESTful con Flask
    • Integración AJAX con API REST
    • Autenticación JWT (estás aquí)
    • Implementación en un servidor privado virtual

    Introducción básica a la autenticación JWT

    Al igual que en algunas de las otras publicaciones de esta serie, no entraré en detalles significativos sobre la teoría de cómo funciona JWT . En cambio, adoptaré un enfoque pragmático y demostraré los detalles de su implementación utilizando las tecnologías de interés dentro de Flask y Vue.js. Si está interesado en obtener una comprensión más profunda de los JWT, lo remito a la excelente publicación de Scott Robinson aquí en Pharos.sh, donde explica los detalles de bajo nivel de la técnica.

    En el sentido básico, un JWT es un objeto JSON codificado que se utiliza para transmitir información entre dos sistemas que se compone de un encabezado, una carga útil y una firma en forma de [HEADER].[PAYLOAD].[SIGNATURE]todo lo contenido en el encabezado HTTP como «Autorización: Portador [HEADER]. [PAYLOAD]. [FIRMA] «. El proceso comienza cuando el cliente (sistema solicitante) se autentica con el servidor (un servicio con un recurso deseado) que genera un JWT que solo es válido por un período de tiempo específico. Luego, el servidor lo devuelve como un token firmado y codificado para que el cliente lo almacene y lo use para verificación en comunicaciones posteriores.

    La autenticación JWT funciona bastante bien para aplicaciones SPA como la que se está construyendo en esta serie y ha ganado una popularidad significativa entre los desarrolladores que las implementan.

    Implementación de la autenticación JWT en la API RESTful de Flask

    En el lado de Flask, usaré el paquete Python PyJWT para manejar algunos de los detalles relacionados con la creación, análisis y validación de JWT.

    (venv) $ pip install PyJWT
    

    Con el paquete PyJWT instalado, puedo pasar a implementar las piezas necesarias para la autenticación y verificación en la aplicación Flask. Para empezar, le daré a la aplicación la capacidad de crear nuevos usuarios registrados que estarán representados por una Userclase. Al igual que con todas las demás clases de esta aplicación, la Userclase residirá en el módulo models.py.

    El primer elemento que debe hacer es importar un par de funciones, generate_password_hashy check_password_hashdesde el módulo del paquete werkzeug que usarésecurity para generar y verificar contraseñas hash. No es necesario instalar este paquete, ya que viene con Flask automáticamente.

    """
    models.py
    - Data classes for the surveyapi application
    """
    
    from datetime import datetime
    from flask_sqlalchemy import SQLAlchemy
    
    from werkzeug.security import generate_password_hash, check_password_hash
    
    db = SQLAlchemy()
    

    Directamente debajo del código anterior defino la Userclase, que hereda de la Modelclase SQLAlchemy similar a las otras definidas en publicaciones anteriores. Esta Userclase debe contener un campo de clase de clave primaria de entero generado automáticamente llamado idluego dos campos de cadena llamados emaily passwordcon el correo electrónico configurado para ser único. También le doy a esta clase un relationshipcampo para asociar cualquier encuesta que el usuario pueda crear. En el otro lado de esta ecuación, agregué una creator_idclave externa a la Surveyclase para vincular a los usuarios con las encuestas que crean.

    Anulo el __init__(...)método para poder aplicar hash a la contraseña al crear una instancia de un nuevo Userobjeto. Después de eso, le doy el método de clase authenticate, para consultar a un usuario por correo electrónico y verificar que el hash de contraseña proporcionado coincide con el almacenado en la base de datos. Si coinciden, devuelvo el usuario autenticado. Por último, pero no menos importante, agregué un to_dict()método para ayudar a serializar objetos de usuario.

    """
    models.py
    - Data classes for the surveyapi application
    """
    
    #
    # omitting imports and what not
    #
    
    class User(db.Model):
        __tablename__ = 'users'
    
        id = db.Column(db.Integer, primary_key=True)
        email = db.Column(db.String(120), unique=True, nullable=False)
        password = db.Column(db.String(255), nullable=False)
        surveys = db.relationship('Survey', backref="creator", lazy=False)
    
        def __init__(self, email, password):
            self.email = email
            self.password = generate_password_hash(password, method='sha256')
    
        @classmethod
        def authenticate(cls, **kwargs):
            email = kwargs.get('email')
            password = kwargs.get('password')
            
            if not email or not password:
                return None
    
            user = cls.query.filter_by(email=email).first()
            if not user or not check_password_hash(user.password, password):
                return None
    
            return user
    
        def to_dict(self):
            return dict(id=self.id, email=self.email)
    
    class Survey(db.Model):
        __tablename__ = 'surveys'
    
        id = db.Column(db.Integer, primary_key=True)
        name = db.Column(db.Text)
        created_at = db.Column(db.DateTime, default=datetime.utcnow)
        questions = db.relationship('Question', backref="survey", lazy=False)
        creator_id = db.Column(db.Integer, db.ForeignKey('users.id'))
    
        def to_dict(self):
          return dict(id=self.id,
                      name=self.name,
                      created_at=self.created_at.strftime('%Y-%m-%d %H:%M:%S'),
                      questions=[question.to_dict() for question in self.questions])
    

    El siguiente paso es generar una nueva migración y actualizar la base de datos para emparejar la Userclase Python con una tabla de base de datos sqlite de usuarios. Para hacer esto, ejecuto los siguientes comandos en el mismo directorio que mi módulo manage.py.

    (venv) $ python manage.py db migrate
    (venv) $ python manage.py db upgrade
    

    Ok, es hora de saltar al módulo api.py e implementar la funcionalidad para registrar y autenticar usuarios junto con la funcionalidad de verificación para proteger la creación de nuevas encuestas. Después de todo, no quiero que ningún robot web infame u otros malos actores contaminen mi increíble aplicación de encuestas.

    Para comenzar, agrego la Userclase a la lista de importaciones desde el módulo models.py hacia la parte superior del módulo api.py. Mientras esté allí, seguiré adelante y agregaré un par de otras importaciones que usaré más adelante.

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    from functools import wraps
    from datetime import datetime, timedelta
    
    from flask import Blueprint, jsonify, request, current_app
    
    import jwt
    
    from .models import db, Survey, Question, Choice, User
    

    Ahora que tengo todas las herramientas que necesito importadas, puedo implementar un conjunto de funciones de vista de registro e inicio de sesión en el módulo api.py.

    Comenzaré con la register()función de vista que espera que se envíe un correo electrónico y una contraseña en JSON en el cuerpo de la solicitud POST. El usuario simplemente se crea con lo que se proporcione para el correo electrónico y la contraseña, y felizmente devuelvo una respuesta JSON (que no es necesariamente el mejor enfoque, pero funcionará por el momento).

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other view functions
    #
    
    @api.route('/register/', methods=('POST',))
    def register():
        data = request.get_json()
        user = User(**data)
        db.session.add(user)
        db.session.commit()
        return jsonify(user.to_dict()), 201
    
    

    Frio. El backend es capaz de crear nuevos usuarios deseosos de crear montones de encuestas, así que es mejor que agregue alguna funcionalidad para autenticarlos y dejar que sigan creando sus encuestas.

    La función de inicio de sesión utiliza el User.authenticate(...)método de clase para intentar encontrar y autenticar a un usuario. Si se encuentra el usuario que coincide con el correo electrónico y la contraseña dados, la función de inicio de sesión progresa para crear un token JWT; de lo contrario, Nonese devuelve, lo que da como resultado que la función de inicio de sesión devuelva un mensaje de «falla en la autenticación» con el código de estado HTTP apropiado de 401.

    Creo el token JWT usando PyJWT (como jwt) codificando un diccionario que contiene lo siguiente:

    • sub – el asunto del jwt, que en este caso es el correo electrónico del usuario
    • iat – el momento en que se emitió el jwt en
    • exp – es el momento en que el jwt debe expirar, que es 30 minutos después de emitido en este caso
    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other view functions
    #
    
    @api.route('/login/', methods=('POST',))
    def login():
        data = request.get_json()
        user = User.authenticate(**data)
    
        if not user:
            return jsonify({ 'message': 'Invalid credentials', 'authenticated': False }), 401
    
        token = jwt.encode({
            'sub': user.email,
            'iat':datetime.utcnow(),
            'exp': datetime.utcnow() + timedelta(minutes=30)},
            current_app.config['SECRET_KEY'])
        return jsonify({ 'token': token.decode('UTF-8') })
    

    El proceso de codificación utiliza el valor de la propiedad de la BaseConfigclase SECRET_KEYdefinida en config.py y se mantiene en la current_apppropiedad de configuración de la ‘una vez que se crea la aplicación Flask.

    A continuación, me gustaría dividir la funcionalidad GET y POST que actualmente reside en una función de vista mal nombrada que se fetch_survey(...)muestra a continuación en su estado original. En su lugar, dejaré que fetch_surveys(...)sea ​​el único responsable de obtener todas las encuestas cuando solicite «/ api / survey /» con una solicitud GET. La creación de encuestas, por otro lado, que ocurre cuando se accede a la misma URL con una solicitud POST, ahora residirá en una nueva función llamada create_survey(...).

    Así que esto…

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other view functions
    #
    
    @api.route('/surveys/', methods=('GET', 'POST'))
    def fetch_surveys():
        if request.method == 'GET':
            surveys = Survey.query.all()
            return jsonify([s.to_dict() for s in surveys])
        elif request.method == 'POST':
            data = request.get_json()
            survey = Survey(name=data['name'])
            questions = []
            for q in data['questions']:
                question = Question(text=q['question'])
                question.choices = [Choice(text=c) for c in q['choices']]
                questions.append(question)
            survey.questions = questions
            db.session.add(survey)
            db.session.commit()
            return jsonify(survey.to_dict()), 201
    

    se convierte en esto …

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other view functions
    #
    
    @api.route('/surveys/', methods=('POST',))
    def create_survey(current_user):
        data = request.get_json()
        survey = Survey(name=data['name'])
        questions = []
        for q in data['questions']:
            question = Question(text=q['question'])
            question.choices = [Choice(text=c) for c in q['choices']]
            questions.append(question)
        survey.questions = questions
        survey.creator = current_user
        db.session.add(survey)
        db.session.commit()
        return jsonify(survey.to_dict()), 201
    
    
    @api.route('/surveys/', methods=('GET',))
    def fetch_surveys():
        surveys = Survey.query.all()
        return jsonify([s.to_dict() for s in surveys])
    

    La clave real ahora es proteger la create_survey(...)función de visualización para que solo los usuarios autenticados puedan crear nuevas encuestas. Dicho de otra manera, si se realiza una solicitud POST contra «/ api / survey», la aplicación debe verificar para asegurarse de que la realiza un usuario válido y autenticado.

    ¡Llega el práctico decorador de Python! Usaré un decorador para ajustar la create_survey(...)función de vista que verificará que el solicitante contenga un token JWT válido en su encabezado y rechazará cualquier solicitud que no lo tenga. Llamaré a este decorador token_requiredy lo implementaré sobre todas las otras funciones de vista en api.py así:

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other view functions
    #
    
    def token_required(f):
        @wraps(f)
        def _verify(*args, **kwargs):
            auth_headers = request.headers.get('Authorization', '').split()
    
            invalid_msg = {
                'message': 'Invalid token. Registeration and / or authentication required',
                'authenticated': False
            }
            expired_msg = {
                'message': 'Expired token. Reauthentication required.',
                'authenticated': False
            }
    
            if len(auth_headers) != 2:
                return jsonify(invalid_msg), 401
    
            try:
                token = auth_headers[1]
                data = jwt.decode(token, current_app.config['SECRET_KEY'])
                user = User.query.filter_by(email=data['sub']).first()
                if not user:
                    raise RuntimeError('User not found')
                return f(user, *args, **kwargs)
            except jwt.ExpiredSignatureError:
                return jsonify(expired_msg), 401 # 401 is Unauthorized HTTP status code
            except (jwt.InvalidTokenError, Exception) as e:
                print(e)
                return jsonify(invalid_msg), 401
    
        return _verify
    

    La lógica principal de este decorador es:

    • Asegúrese de que contenga el encabezado «Autorización» con una cadena que parezca un token JWT
    • Validar que el JWT no esté vencido, lo que PyJWT se encarga por mí lanzando un ExpiredSignatureErrorsi ya no es válido
    • Valide que el JWT es un token válido, del cual PyJWT también se encarga lanzando un InvalidTokenErrorsi no es válido
    • Si todo es válido, el usuario asociado es consultado desde la base de datos y regresado a la función que el decorador está envolviendo.

    Ahora todo lo que queda es agregar el decorador al create_survey(...)método de esta manera:

    """
    api.py
    - provides the API endpoints for consuming and producing 
      REST requests and responses
    """
    
    #
    # omitting inputs and other functions
    #
    
    @api.route('/surveys/', methods=('POST',))
    @token_required
    def create_survey(current_user):
        data = request.get_json()
        survey = Survey(name=data['name'])
        questions = []
        for q in data['questions']:
            question = Question(text=q['question'])
            question.choices = [Choice(text=c) for c in q['choices']]
            questions.append(question)
        survey.questions = questions
        survey.creator = current_user
        db.session.add(survey)
        db.session.commit()
        return jsonify(survey.to_dict()), 201
    

    Implementación de la autenticación JWT en Vue.js SPA

    Con el lado del back-end, la ecuación de autenticación completa, ahora necesito abrochar el lado del cliente implementando la autenticación JWT en Vue.js. Empiezo creando un nuevo módulo dentro de la aplicación llamado «utils» dentro del directorio src y colocando un archivo index.js dentro de la carpeta utils. Este módulo contendrá dos cosas:

    • Un bus de eventos que puedo usar para enviar mensajes alrededor de la aplicación cuando suceden ciertas cosas, como autenticación fallida en el caso de un JWT caducado
    • Una función para verificar un JWT para ver si todavía es válido o no

    Estas dos cosas se implementan así:

    // utils/index.js
    
    import Vue from 'vue'
    
    export const EventBus = new Vue()
    
    export function isValidJwt (jwt) {
      if (!jwt || jwt.split('.').length < 3) {
        return false
      }
      const data = JSON.parse(atob(jwt.split('.')[1]))
      const exp = new Date(data.exp * 1000) // JS deals with dates in milliseconds since epoch
      const now = new Date()
      return now < exp
    }
    

    La EventBusvariable es solo una instancia del objeto Vue. Puedo utilizar el hecho de que el objeto Vue tiene un $emity un par de métodos $on/ $off, que se utilizan para emitir eventos, así como para registrar y anular el registro de eventos.

    El isValid(jwt)functino es lo que usaré para determinar si un usuario está autenticado en base a la información en el JWT. Recuerde de la explicación básica anterior de los JWT que un conjunto estándar de propiedades reside en un objeto JSON codificado de la forma «[HEADER]. [PAYLOAD]. [SIGNATURE]». Por ejemplo, decir que tengo la siguiente JWT ‘eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJleGFtcGxlQG1haWwuY29tIiwiaWF0IjoxNTIyMzI2NzMyLCJleHAiOjE1MjIzMjg1MzJ9.1n9fx0vL9GumDGatwm2vfUqQl3yZ7Kl4t5NWMvW-pgw’. Puedo decodificar la sección del cuerpo medio para inspeccionar su contenido usando el siguiente JavaScript:

    const token = 'eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJleGFtcGxlQG1haWwuY29tIiwiaWF0IjoxNTIyMzI2NzMyLCJleHAiOjE1MjIzMjg1MzJ9.1n9fx0vL9GumDGatwm2vfUqQl3yZ7Kl4t5NWMvW-pgw'
    const tokenParts = token.split('.')
    const body = JSON.parse(atob(tokenParts[1]))
    console.log(body)   // {sub: "[email protected]", iat: 1522326732, exp: 1522328532}
    

    Aquí el contenido del cuerpo del token es sub, que representa el correo electrónico del suscriptor, iatque se emite en la marca de tiempo en segundos, y expque es el tiempo en el que el token caducará en segundos desde la época (la cantidad de segundos que han transcurrido desde 1 de enero de 1970 (medianoche UTC / GMT), sin contar los segundos intercalares (en ISO 8601: 1970-01-01T00: 00: 00Z)). Como puede ver, estoy usando el expvalor en la isValidJwt(jwt)función para determinar si el JWT está vencido o no.

    El siguiente paso es agregar un par de nuevas funciones AJAX para hacer llamadas a la API REST de Flask para registrar nuevos usuarios e iniciar sesión en los existentes, además, necesitaré modificar la postNewSurvey(...)función para incluir un encabezado que contenga un JWT.

    
    // api/index.js
    
    //
    // omitting stuff ... skipping to the bottom of the file
    //
    
    export function postNewSurvey (survey, jwt) {
      return axios.post(`${API_URL}/surveys/`, survey, { headers: { Authorization: `Bearer: ${jwt}` } })
    }
    
    export function authenticate (userData) {
      return axios.post(`${API_URL}/login/`, userData)
    }
    
    export function register (userData) {
      return axios.post(`${API_URL}/register/`, userData)
    }
    

    Bien, ahora puedo usar estas cosas en la tienda para administrar el estado requerido para proporcionar la funcionalidad de autenticación adecuada. Para comenzar, importo EventBusy isValidJwt(...)funciono desde el módulo utils, así como las dos nuevas funciones AJAX del módulo api. Luego agregue una definición de un userobjeto y una jwtcadena de token en el objeto de estado de la tienda así:

    // store/index.js
    
    import Vue from 'vue'
    import Vuex from 'vuex'
    
    // imports of AJAX functions will go here
    import { fetchSurveys, fetchSurvey, saveSurveyResponse, postNewSurvey, authenticate, register } from '@/api'
    import { isValidJwt, EventBus } from '@/utils'
    
    Vue.use(Vuex)
    
    const state = {
      // single source of data
      surveys: [],
      currentSurvey: {},
      user: {},
      jwt: ''
    }
    
    //
    // omitting all the other stuff below
    //
    

    A continuación, tengo que añadir en un par de métodos de acción que se llame, ya sea el register(...)o authenticate(...)funciones AJAX que acabamos de definir. Nombro al responsable de autenticar a un usuario login(...), que llama a la authenticate(...)función AJAX y cuando devuelve una respuesta exitosa que contiene un nuevo JWT, comete una mutación que nombraré setJwtToken, que debe agregarse al objeto de mutaciones. En el caso de una solicitud de autenticación fallida, encadeno un catchmétodo a la cadena de promesa para detectar el error y utilizar EventBuspara emitir un evento que notifique a los suscriptores que la autenticación falló.

    El register(...)método de acción es bastante similar al que login(...), de hecho, utiliza login(...). También estoy mostrando una pequeña modificación al submitNewSurvey(...)método de acción que pasa el token JWT como parámetro adicional a la postNewSurvey(...)llamada AJAX.

    const actions = {
      // asynchronous operations
    
      //
      // omitting the other action methods...
      //
    
      login (context, userData) {
        context.commit('setUserData', { userData })
        return authenticate(userData)
          .then(response => context.commit('setJwtToken', { jwt: response.data }))
          .catch(error => {
            console.log('Error Authenticating: ', error)
            EventBus.$emit('failedAuthentication', error)
          })
      },
      register (context, userData) {
        context.commit('setUserData', { userData })
        return register(userData)
          .then(context.dispatch('login', userData))
          .catch(error => {
            console.log('Error Registering: ', error)
            EventBus.$emit('failedRegistering: ', error)
          })
      },
      submitNewSurvey (context, survey) {
        return postNewSurvey(survey, context.state.jwt.token)
      }
    }
    

    Como se mencionó anteriormente, necesito agregar una nueva mutación que establezca explícitamente el JWT y los datos del usuario.

    const mutations = {
      // isolated data mutations
    
      //
      // omitting the other mutation methods...
      //
    
      setUserData (state, payload) {
        console.log('setUserData payload = ', payload)
        state.userData = payload.userData
      },
      setJwtToken (state, payload) {
        console.log('setJwtToken payload = ', payload)
        localStorage.token = payload.jwt.token
        state.jwt = payload.jwt
      }
    }
    

    Lo último que me gustaría hacer en la tienda es agregar un método de obtención que se llamará en un par de otros lugares de la aplicación que indicará si el usuario actual está autenticado o no. Lo logro llamando a la isValidJwt(jwt)función desde el módulo utils dentro del getter así:

    const getters = {
      // reusable data accessors
      isAuthenticated (state) {
        return isValidJwt(state.jwt.token)
      }
    }
    

    Ok, me estoy acercando. Necesito agregar un nuevo componente Vue.js para una página de inicio de sesión / registro en la aplicación. Creo un archivo llamado Login.vue en el directorio de componentes. En la sección de plantilla le doy dos campos de entrada, uno para un correo electrónico, que servirá como nombre de usuario, y otro para la contraseña. Debajo hay dos botones, uno para iniciar sesión si ya es un usuario registrado y otro para registrarse.

    <!-- components/Login.vue -->
    <template>
      <div>
        <section class="hero is-primary">
          <div class="hero-body">
            <div class="container has-text-centered">
              <h2 class="title">Login or Register</h2>
              <p class="subtitle error-msg">{{ errorMsg }}</p>
            </div>
          </div>
        </section>
        <section class="section">
          <div class="container">
            <div class="field">
              <label class="label is-large" for="email">Email:</label>
              <div class="control">
                <input type="email" class="input is-large" id="email" v-model="email">
              </div>
            </div>
            <div class="field">
              <label class="label is-large" for="password">Password:</label>
              <div class="control">
                <input type="password" class="input is-large" id="password" v-model="password">
              </div>
            </div>
    
            <div class="control">
              <a class="button is-large is-primary" @click="authenticate">Login</a>
              <a class="button is-large is-success" @click="register">Register</a>
            </div>
    
          </div>
        </section>
    
      </div>
    </template>
    

    Obviamente, este componente necesitará algún estado local asociado con un usuario según lo indicado por mi uso de v-modelen los campos de entrada, así que lo agrego en la propiedad de datos del componente a continuación. También agrego una errorMsgpropiedad de datos que contendrá cualquier mensaje emitido por el EventBusen caso de un registro o autenticación fallidos. Para utilizar, EventBusme suscribo a los eventos ‘failureRegistering’ y ‘failedAuthentication’ en la mountedetapa del ciclo de vida del componente Vue.js y anule el registro en la beforeDestroyetapa. Otra cosa a tener en cuenta es el uso de @clickcontroladores de eventos que se llaman al hacer clic en los botones Iniciar sesión y Registrarse. Estos se implementarán como métodos de componentes, authenticate()y register().

    <!-- components/Login.vue -->
    <script>
    export default {
      data () {
        return {
          email: '',
          password: '',
          errorMsg: ''
        }
      },
      methods: {
        authenticate () {
          this.$store.dispatch('login', { email: this.email, password: this.password })
            .then(() => this.$router.push("https://Pharos.sh.com/"))
        },
        register () {
          this.$store.dispatch('register', { email: this.email, password: this.password })
            .then(() => this.$router.push("https://Pharos.sh.com/"))
        }
      },
      mounted () {
        EventBus.$on('failedRegistering', (msg) => {
          this.errorMsg = msg
        })
        EventBus.$on('failedAuthentication', (msg) => {
          this.errorMsg = msg
        })
      },
      beforeDestroy () {
        EventBus.$off('failedRegistering')
        EventBus.$off('failedAuthentication')
      }
    }
    </script>
    

    Bien, ahora solo necesito que el resto de la aplicación sepa que existe el componente de inicio de sesión. Hago esto importándolo en el módulo del enrutador y definiendo su ruta. Mientras estoy en el módulo del enrutador, necesito hacer un cambio adicional en la NewSurveyruta del componente para proteger su acceso solo a usuarios autenticados, como se muestra a continuación:

    // router/index.js
    import Vue from 'vue'
    import Router from 'vue-router'
    import Home from '@/components/Home'
    import Survey from '@/components/Survey'
    import NewSurvey from '@/components/NewSurvey'
    import Login from '@/components/Login'
    import store from '@/store'
    
    Vue.use(Router)
    
    export default new Router({
      routes: [
        {
          path: "https://Pharos.sh.com/",
          name: 'Home',
          component: Home
        }, {
          path: '/surveys/:id',
          name: 'Survey',
          component: Survey
        }, {
          path: '/surveys',
          name: 'NewSurvey',
          component: NewSurvey,
          beforeEnter (to, from, next) {
            if (!store.getters.isAuthenticated) {
              next('/login')
            } else {
              next()
            }
          }
        }, {
          path: '/login',
          name: 'Login',
          component: Login
        }
      ]
    })
    
    

    Vale la pena mencionar aquí que estoy utilizando la protección de ruta de vue-router beforeEnterpara verificar si el usuario actual está autenticado a través del isAuthenticatedgetter de la tienda. Si isAuthenticateddevuelve falso, redirijo la aplicación a la página de inicio de sesión.

    Con el componente de inicio de sesión codificado y su ruta definida, puedo proporcionar acceso a él a través de un componente de enlace de enrutador en el componente de encabezado dentro de components / Header.vue. Condicionalmente muestro el enlace al NewSurveycomponente o al Logincomponente utilizando el isAuthenticatedcaptador de tienda una vez más dentro de una propiedad calculada en el Headercomponente al que hacen referencia las v-ifdirectivas de esta manera:

    <!-- components/Header.vue -->
    <template>
    <nav class="navbar is-light" role="navigation" aria-label="main navigation">
      <div class="navbar-menu">
        <div class="navbar-start">
          <router-link to="/" class="navbar-item">
            Home
          </router-link>
          <router-link v-if="isAuthenticated" to="/surveys" class="navbar-item">
            Create Survey
          </router-link>
          <router-link v-if="!isAuthenticated" to="/login" class="navbar-item">
            Login / Register
          </router-link>
        </div>
      </div>
    </nav>
    </template>
    
    <script>
    export default {
      computed: {
        isAuthenticated () {
          return this.$store.getters.isAuthenticated
        }
      }
    }
    </script>
    
    <style>
    
    </style>
    

    ¡Excelente! Ahora finalmente puedo iniciar los servidores de desarrollo para la aplicación Flask y la aplicación Vue.js y probar para ver si puedo registrar e iniciar sesión como usuario.

    Primero inicio el servidor de desarrollo de Flask.

    (venv) $ python appserver.py
    

    Luego, el servidor de desarrollo webpack para compilar y servir la aplicación Vue.js.

    $ npm run dev
    

    En mi navegador, visito http://localhost:8080(o cualquier puerto que indique el servidor de desarrollo webpack) y me aseguro de que la barra de navegación ahora muestre «Iniciar sesión / Registrarse» en el lugar de «Crear encuesta» como se muestra a continuación:

    A continuación, hago clic en el enlace «Iniciar sesión / Registrarse» y completo las entradas para un correo electrónico y una contraseña, luego hago clic en registrarse para asegurarme de que funcione como se esperaba y me redirigen de nuevo a la página de inicio y veo el enlace «Crear encuesta» que se muestra en su lugar. del «Iniciar sesión / Registrarse» que estaba allí antes de registrarse.

    Muy bien, mi trabajo está terminado. Lo único que queda por hacer es agregar un pequeño manejo de errores al submitSurvey(...)método Vue.js del NewSurveycomponente para manejar el evento donde un token expira mientras el usuario está creando una nueva encuesta como esta:

    <script>
    import NewQuestion from '@/components/NewQuestion'
    
    export default {
      components: { NewQuestion },
      data () {
        return {
          step: 'name',
          name: '',
          questions: []
        }
      },
      methods: {
    
        //
        // omitting other methods
        //
    
        submitSurvey () {
          this.$store.dispatch('submitNewSurvey', {
            name: this.name,
            questions: this.questions
          })
            .then(() => this.$router.push("https://Pharos.sh.com/"))
            .catch((error) => {
              console.log('Error creating survey', error)
              this.$router.push("https://Pharos.sh.com/")
            })
        }
      }
    }
    </script>
    

     

    Conclusión

    En esta publicación, demostré cómo implementar la autenticación JWT en la aplicación de encuestas usando Vue.js y Flask. JWT es un método popular y robusto para proporcionar autenticación dentro de aplicaciones SPA, y espero que después de leer esta publicación se sienta cómodo usando estas tecnologías para proteger sus aplicaciones.

    Como siempre, gracias por leer y no dude en comentar o criticar a continuación.

    Rate this post
    Etiquetas:

    Deja una respuesta

    Tu dirección de correo electrónico no será publicada. Los campos obligatorios están marcados con *