Jednostránkové aplikace s Vue.js a Flask:JWT Authentication

Ověření JWT

Vítejte u šestého pokračování této vícedílné série výukových programů o komplexním vývoji webu pomocí Vue.js a Flask. V tomto příspěvku ukážu způsob, jak používat ověřování JSON Web Token (JWT).

Kód pro tento příspěvek najdete na mém účtu GitHub pod pobočkou SixthPost.

Obsah seriálu

  1. Nastavení a seznámení s VueJS
  2. Navigace Vue Router
  3. Správa státu se společností Vuex
  4. RESTful API s Flask
  5. Integrace AJAX s REST API
  6. Ověření JWT (jste zde)
  7. Nasazení na virtuální privátní server

Základní úvod do ověřování JWT

Podobně jako u některých dalších příspěvků v této sérii nebudu zabíhat do významných podrobností o teorii fungování JWT. Místo toho budu zaujmout pragmatický přístup a demonstrovat jeho implementační specifika pomocí technologií, které mě zajímají v rámci Flask a Vue.js. Pokud máte zájem o hlubší porozumění JWT, odkazuji vás na vynikající příspěvek Scotta Robinsona zde na StackAbuse, kde vysvětluje detaily této techniky na nízké úrovni.

V základním smyslu je JWT zakódovaný objekt JSON používaný k přenosu informací mezi dvěma systémy, který se skládá ze záhlaví, užitečného zatížení a podpisu ve tvaru [HEADER].[PAYLOAD].[SIGNATURE] vše je obsaženo v HTTP hlavičce jako "Autorizace:Nositel [HLAVIČKA].[PAYLOAD].[SIGNATURE]". Proces začíná tím, že se klient (žádající systém) autentizuje na serveru (služba s požadovaným zdrojem), který generuje JWT, který je platný pouze po určitou dobu. Server to poté vrátí jako podepsaný a zakódovaný token, který si klient může uložit a použít pro ověření v pozdější komunikaci.

Autentizace JWT funguje docela dobře pro SPA aplikace, jako je ta, která je vyvíjena v této sérii, a získala značnou oblibu mezi vývojáři, kteří je implementují.

Implementace ověřování JWT v rozhraní Flask RESTful API

Na straně Flask budu používat balíček Python PyJWT ke zpracování některých podrobností kolem vytváření, analýzy a ověřování JWT.

(venv) $ pip install PyJWT

S nainstalovaným balíčkem PyJWT mohu přejít k implementaci částí nezbytných pro autentizaci a ověření v aplikaci Flask. Pro začátek dám aplikaci možnost vytvářet nové registrované uživatele, kteří budou reprezentováni User třída. Stejně jako u všech ostatních tříd v této aplikaci User class bude umístěn v modulu models.py.

První věcí, kterou musíte udělat, je importovat několik funkcí, generate_password_hash a check_password_hash z security balíčku werkzeug modul, který budu používat ke generování a ověřování hashovaných hesel. Tento balíček není třeba instalovat, protože je dodáván s Flask automaticky.

"""
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()

Přímo pod výše uvedeným kódem definuji User třída, která dědí z SQLAlchemy Model třídy podobné ostatním definovaným v předchozích příspěvcích. Toto User class musí obsahovat automaticky generované celočíselné pole třídy primárního klíče s názvem id pak dvě řetězcová pole nazvaná email a password s e-mailem nakonfigurovaným jako jedinečný. Také dávám této třídě relationship pole pro přiřazení jakýchkoli průzkumů, které uživatel může vytvořit. Na druhou stranu této rovnice jsem přidal creator_id cizí klíč na Survey třída k propojení uživatelů s průzkumy, které vytvářejí.

Přepisuji __init__(...) metoda, abych mohl heslo zahašovat při vytváření instance nového User objekt. Poté mu dám metodu třídy authenticate , dotázat se uživatele e-mailem a zkontrolovat, zda se zadaný hash hesla shoduje s heslem uloženým v databázi. Pokud se shodují, vrátím ověřeného uživatele. V neposlední řadě jsem zvolil to_dict() metoda, která vám pomůže se serializací uživatelských objektů.

"""
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])

Dalším krokem je vygenerování nové migrace a aktualizace databáze pomocí ní, aby spárovala User Třída Python s uživatelskou databázovou tabulkou sqlite. K tomu spouštím následující příkazy ve stejném adresáři jako můj modul manage.py.

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

Dobře, je čas přeskočit na modul api.py a implementovat funkci pro registraci a ověřování uživatelů spolu s ověřovací funkcí pro ochranu vytváření nových průzkumů. Koneckonců nechci, aby moji úžasnou průzkumnou aplikaci znečišťovali žádní hanební weboví roboti nebo jiní špatní herci.

Pro začátek přidám User třídy do seznamu importů z modulu models.py směrem k horní části modulu api.py. Zatímco tam budu, budu pokračovat a přidám několik dalších importů, které použiji později.

"""
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

Nyní, když mám importovány všechny nástroje, které potřebuji, mohu implementovat sadu funkcí zobrazení registrace a přihlášení do modulu api.py.

Začnu s register() funkce zobrazení, která očekává odeslání e-mailu a hesla v JSON v těle požadavku POST. Uživatel je jednoduše vytvořen pomocí čehokoli, co je zadáno pro e-mail a heslo, a já s radostí vracím odpověď JSON (což není nutně nejlepší přístup, ale v tuto chvíli to bude fungovat).

"""
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

Chladný. Backend je schopen vytvářet nové uživatele, kteří touží vytvářet hromady průzkumů, takže je lepší přidat nějakou funkcionalitu pro jejich ověření a nechat je pokračovat ve vytváření svých průzkumů.

Funkce přihlášení používá User.authenticate(...) třídy, která se pokusí najít a ověřit uživatele. Pokud je nalezen uživatel, který odpovídá danému e-mailu a heslu, přihlašovací funkce postoupí k vytvoření tokenu JWT, jinak None je vráceno, což má za následek, že přihlašovací funkce vrátí zprávu „selhání ověření“ s příslušným stavovým kódem HTTP 401.

Token JWT vytvářím pomocí PyJWT (jako jwt) kódováním slovníku obsahujícího následující:

  • sub – předmět jwt, což je v tomto případě e-mail uživatele
  • iat – čas vydání jwt
  • exp – je okamžik, kdy by měla vypršet platnost jwt, což je v tomto případě 30 minut po vydání
"""
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') })

Proces kódování využívá hodnotu BaseConfig třídy SECRET_KEY vlastnost definovaná v config.py a uložená v current_app vlastnost config po vytvoření aplikace Flask.

Dále bych chtěl rozdělit funkce GET a POST, které se v současnosti nacházejí ve špatně pojmenované funkci zobrazení s názvem fetch_survey(...) zobrazený níže v původním stavu. Místo toho nechám fetch_surveys(...) mít na starosti pouze načítání všech průzkumů, když požadujete "/api/surveys/" s požadavkem GET. Na druhou stranu vytváření průzkumu, ke kterému dochází, když je stejná adresa URL zasažena požadavkem POST, bude nyní spočívat v nové funkci nazvané create_survey(...) .

Takže tohle...

"""
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 stane tímto...

"""
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])

Skutečným klíčem je nyní ochrana create_survey(...) funkce zobrazení, takže pouze ověření uživatelé mohou vytvářet nové průzkumy. Jinak řečeno, pokud je požadavek POST zadán proti "/api/surveys", aplikace by měla zkontrolovat, zda to provádí platný a ověřený uživatel.

Přichází šikovný dekoratér Python! K zabalení create_survey(...) použiji dekoratér funkci view, která zkontroluje, zda žadatel obsahuje v záhlaví platný token JWT, a odmítne všechny požadavky, které jej nemají. Tomuto dekoratérovi budu říkat token_required a implementujte jej nad všechny ostatní funkce zobrazení v api.py takto:

"""
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

Primární logika tohoto dekorátoru je:

  1. Ujistěte se, že obsahuje záhlaví „Authorization“ s řetězcem, který vypadá jako token JWT
  2. Ověřit, že platnost JWT nevypršela, o což se PyJWT postará za mě tím, že hodí ExpiredSignatureError pokud již není platný
  3. Ověřte, že JWT je platný token, o což se PyJWT také postará tím, že hodí InvalidTokenError pokud není platný
  4. Pokud je vše platné, je z databáze dotazován přidružený uživatel a vrácen do funkce, kterou dekorátor zabaluje

Nyní zbývá pouze přidat dekoratér do create_survey(...) metoda takto:

"""
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

Implementace ověřování JWT ve Vue.js SPA

Po dokončení autentizační rovnice na straně back-endu nyní potřebuji zapnout stranu klienta implementací autentizace JWT ve Vue.js. Začnu vytvořením nového modulu v aplikaci s názvem „utils“ v adresáři src a umístěním souboru index.js do složky utils. Tento modul bude obsahovat dvě věci:

Zdarma e-kniha:Git Essentials

Prohlédněte si našeho praktického průvodce učením Git s osvědčenými postupy, průmyslově uznávanými standardy a přiloženým cheat sheetem. Přestaňte používat příkazy Google Git a skutečně se naučte to!

  1. Sběrnice událostí, kterou mohu použít k odesílání zpráv do aplikace, když se stanou určité věci, jako je neúspěšné ověření v případě vypršení platnosti JWT
  2. Funkce pro kontrolu JWT, zda je stále platný nebo ne

Tyto dvě věci jsou implementovány takto:

// 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
}

EventBus proměnná je pouze instancí objektu Vue. Mohu využít skutečnost, že objekt Vue má obě $emit a pár $on / $off metody, které se používají k vysílání událostí a také k registraci a odregistrování událostí.

isValid(jwt) Funkce je to, co použiji k určení, zda je uživatel ověřen na základě informací v JWT. Připomeňme si z dřívějšího základního vysvětlení JWT, že standardní sada vlastností je umístěna v zakódovaném objektu JSON ve tvaru "[HEADER].[PAYLOAD].[SIGNATURE]". Řekněme například, že mám následující JWT:

eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdWIiOiJleGFtcGxlQG1haWwuY29tIiwiaWF0IjoxNTIyMzI2NzMyLCJleHAiOjE1MjIzMjg1MzJ9.1n9fx0vL9GumDGatwm2vfUqQl3yZ7Kl4t5NWMvW-pgw

Mohu dekódovat střední část těla a zkontrolovat její obsah pomocí následujícího JavaScriptu:

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}

Zde je obsah těla tokenu sub , představující e-mail předplatitele, iat , která je vydána v časovém razítku v sekundách, a exp , což je čas, za který token vyprší jako sekundy od epochy (počet sekund, které uplynuly od 1. ledna 1970 (o půlnoci UTC/GMT), nepočítaje přestupné sekundy (v ISO 8601:1970-01-01T00:00:00 Z)). Jak vidíte, používám exp hodnotu v isValidJwt(jwt) funkce k určení, zda JWT vypršela nebo ne.

Dále je třeba přidat několik nových funkcí AJAX pro volání do Flask REST API za účelem registrace nových uživatelů a přihlášení stávajících a navíc budu muset upravit postNewSurvey(...) zahrnout záhlaví obsahující 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)
}

Dobře, nyní mohu tyto věci použít v obchodě ke správě stavu potřebného k poskytování správné funkce ověřování. Pro začátek importuji EventBus a isValidJwt(...) funkce z modulu utils a také dvě nové funkce AJAX z modulu api. Poté přidejte definici user objekt a jwt řetězec tokenu v objektu stavu obchodu takto:

// 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
//

Dále musím přidat několik metod akcí, které budou volat buď register(...) nebo authenticate(...) AJAX funkce, které jsme právě definovali. Jmenuji toho, kdo je zodpovědný za ověření uživatele login(...) , který volá authenticate(...) Funkce AJAX a když vrátí úspěšnou odpověď obsahující nový JWT, provede mutaci, kterou pojmenuji setJwtToken , který je třeba přidat do objektu mutace. V případě neúspěšného požadavku na autentizaci řetězím catch metodu do řetězce slibů k zachycení chyby a použijte EventBus vygenerovat událost oznamující všem účastníkům, že ověření selhalo.

register(...) akční metoda je velmi podobná login(...) ve skutečnosti používá login(...) . Zobrazuji také malou úpravu submitNewSurvey(...) akční metoda, která předá token JWT jako další parametr do postNewSurvey(...) AJAX volání.

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)
  }
}

Jak již bylo zmíněno dříve, musím přidat novou mutaci, která explicitně nastaví JWT a uživatelská data.

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
  }
}

Poslední věc, kterou bych chtěl v obchodě udělat, je přidat metodu getter, která bude volána na několika dalších místech v aplikaci, která bude indikovat, zda je aktuální uživatel ověřen nebo ne. Dosahuji toho voláním isValidJwt(jwt) funkce z modulu utils v rámci getteru takto:

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

Dobře, už se blížím. Potřebuji přidat novou komponentu Vue.js pro přihlašovací / registrační stránku v aplikaci. Vytvořím soubor s názvem Login.vue v adresáři komponent. V sekci šablony mu dávám dvě vstupní pole, jedno pro e-mail, který bude sloužit jako uživatelské jméno, a druhé pro heslo. Pod nimi jsou dvě tlačítka, jedno pro přihlášení, pokud jste již registrovaný uživatel, a druhé pro registraci.

<!-- 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>

Je zřejmé, že tato komponenta bude potřebovat nějaký místní stav spojený s uživatelem, jak je naznačeno mým použitím v-model ve vstupních polích, takže to přidám do vlastnosti data komponenty. Také přidávám errorMsg vlastnost data, která bude obsahovat všechny zprávy vysílané EventBus v případě neúspěšné registrace nebo autentizace. Chcete-li použít EventBus Přihlašuji se k odběru událostí 'failedRegistering' a 'failedAuthentication' v mounted fázi životního cyklu komponenty Vue.js a zrušte jejich registraci v beforeDestroy etapa. Další věc, kterou je třeba poznamenat, je použití @click obsluhy událostí jsou volány po kliknutí na tlačítka Přihlásit a Registrovat. Ty mají být implementovány jako dílčí metody, authenticate() a 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('/'))
    },
    register () {
      this.$store.dispatch('register', { email: this.email, password: this.password })
        .then(() => this.$router.push('/'))
    }
  },
  mounted () {
    EventBus.$on('failedRegistering', (msg) => {
      this.errorMsg = msg
    })
    EventBus.$on('failedAuthentication', (msg) => {
      this.errorMsg = msg
    })
  },
  beforeDestroy () {
    EventBus.$off('failedRegistering')
    EventBus.$off('failedAuthentication')
  }
}
</script>

Dobře, teď jen musím dát zbytku aplikace vědět, že komponenta Login existuje. Dělám to tak, že jej importuji do modulu routeru a definuji jeho trasu. Zatímco jsem v modulu routeru, musím provést další změnu v NewSurvey cesta komponenty k ochraně jejího přístupu pouze pro ověřené uživatele, jak je uvedeno níže:

// 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: '/',
      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
    }
  ]
})

Zde stojí za zmínku, že používám vue-router's route guard beforeEnter zkontrolovat, zda je aktuální uživatel ověřen pomocí isAuthenticated dostat z obchodu. Pokud isAuthenticated vrátí false, pak přesměruji aplikaci na přihlašovací stránku.

S nakódovanou komponentou Login a definovanou její cestou k ní mohu poskytnout přístup prostřednictvím komponenty router-link v komponentě Header v komponentách/Header.vue. Podmíněně ukážu buď odkaz na NewSurvey komponentu nebo Login komponentu pomocí isAuthenticated uložte getter ještě jednou v rámci vypočítané vlastnosti v Header komponenta, na kterou odkazuje v-if direktivy jako takto:

<!-- 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>

Vynikající! Nyní mohu konečně spustit dev servery pro aplikaci Flask a aplikaci Vue.js a otestovat, zda mohu zaregistrovat a přihlásit uživatele.

Nejprve spustím server Flask dev.

(venv) $ python appserver.py

Poté webpack dev server kompiluje a obsluhuje aplikaci Vue.js.

$ npm run dev

V prohlížeči navštívím http://localhost:8080 (nebo jakýkoli port, který webpack dev server označuje) a ujistěte se, že navigační lišta nyní zobrazuje "Login / Register" na místě "Create Survey", jak je znázorněno níže:

Dále kliknu na odkaz "Přihlásit se / Zaregistrujte se" a vyplním vstupy pro e-mail a heslo, poté kliknu na tlačítko Registrovat, abych se ujistil, že funguje podle očekávání, a budu přesměrován zpět na domovskou stránku a místo toho se zobrazí odkaz "Vytvořit průzkum". "Přihlášení / Registrace", který tam byl před registrací.

Dobře, moje práce je z velké části hotová. Jediné, co zbývá udělat, je přidat malé zpracování chyb do submitSurvey(...) Metoda Vue.js NewSurvey komponenta pro zpracování události, kdy token náhodou vyprší, zatímco uživatel vytváří nový průzkum:

<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('/'))
        .catch((error) => {
          console.log('Error creating survey', error)
          this.$router.push('/')
        })
    }
  }
}
</script>

Zdroje

Chcete se dozvědět více o různých rámcích používaných v tomto článku? Zkuste se podívat na některé z následujících zdrojů, abyste se mohli hlouběji ponořit do používání Vue.js nebo vytváření back-endových rozhraní API v Pythonu:

  • Rozhraní REST API s Flask a Python
  • Vue.js 2 – Kompletní průvodce
  • Vývojářský kurz Ultimate Vue JS 2

Závěr

V tomto příspěvku jsem demonstroval, jak implementovat autentizaci JWT v aplikaci průzkumu pomocí Vue.js a Flask. JWT je populární a robustní metoda pro poskytování autentizace v aplikacích SPA a doufám, že po přečtení tohoto příspěvku se cítíte pohodlně používat tyto technologie k zabezpečení svých aplikací. Nicméně doporučuji navštívit Scottův článek StackAbuse pro hlubší pochopení toho, jak a proč JWT funguje.

Jako vždy děkuji za přečtení a nestyďte se níže komentovat nebo kritizovat.