why

This blog post may be helpful to you if it fits the following key words

  • Flask applications without using factory functions
  • Flask applications without blueprints
  • Flask cross-domain configuration
  • Token-based login status management
  • Flask+Vue
  • Vue route interception
  • Axios hooks

Applicable scenario

This is a personal blog post and a simple “reference” for Flask and Vue, and the final code will include features commonly used in Web development. (Not complete, only used relatively frequently)

The environment

  • System: irrelevant
  • Flask(Python3)
  • Vue(Node.js)

reference

Flask Web Development Vue

background

There are so many personal blog solutions, why would I build another one myself? In fact, the purpose of building a personal blog is not to blog… Otherwise, I will directly use WordPress. My personal blog is just to practice what I have learned. At the same time, considering that load balancing, clustering and other technologies may be added in the future, leading to major structural changes, or trying to implement new gameplay such as voice control, the operation feasibility of the code line by line will be better.

Code function

Blog function is not perfect, only to achieve the following basic functions of the front end: login registration, blog creation (Markdown editor), the home page pull all articles, creating a blog requires login status. Back end: view functions required by the above services, configuring cross-domain, token management and authentication, database management.

For record sharing purposes, the code that implements login state management is summarized below

Implementation approach

The idea of token-based login state management is as follows

  1. The front-end submits the account password to the background
  2. Background validation, by which token is returned
  3. The front-end sets the token in the request header before each request (using the AXIos hook)
  4. The backend gets the token of the request header when the protected view function is called, validates the token, and allows the call if there is no problem

This is the general idea. Subsequent calls to the view function part of the handguard can be implemented as needed, regardless of what the front and back ends do. The following sections show the main code in the order described above, and the finished code will be posted at the end.

Specific steps

Flask is configured across domains

Cross-domain configuration is preferred for the separation of front and back ends. Here, the back-end solution is adopted and flask_CORS library is used. The code is as follows:

Since the front-end will set the token in the header of each HTTP request after obtaining the token, I named it ‘token’. If you use another name, you need to replace it with ‘access-Control-allow-headers’

from flask_cors import CORS

CORS(app,supports_credentials=True)
@app.after_request
def after_request(resp):
	resp = make_response(resp)
	resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080'
	resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'
	resp.headers['Access-Control-Allow-Headers'] = 'content-type,token'
	return resp
Copy the code

Vue makes a login request to Flask via Axios

The front-end passes the obtained account password to the back-end and writes the obtained token into the Vuex. (Vuex will write the token to localStorage)

let _this = this
axios.post('http://127.0.0.1:5000/login',{
	username:this.username,
	password:this.password,
})
.then(function(response){
	let token = response.data
	_this.changeLogin({Authorization: token})
})
.catch(function(error){
})
Copy the code

Flask implements the view function

The view function authenticates the user information with the user name and password, generates the token, and returns the token.

# Routes
@app.route('/login',methods=['POST'])
def login():
	json = request.get_json()
	user = User.query.filter_by(username = json['username']).first()
	if user.verify_password(json['password']):
		g.currnet_user = user
		token = user.generate_auth_token(expiration=3600)
		return token
	return "wrong password"
Copy the code

Vue configures Axios hooks

Configure the Axios hook to add the token to the header of each HTTP request

axios.interceptors.request.use(
	config => {
		let token = localStorage.getItem('Authorization');
		if(token){
			config.headers.common['token'] = token
		}
		return config
	},
	err => {
		return Promise.reject(err);
	});
Copy the code

Implement HTTPBasicAuth

The flask_httpAuth module implements very little. The core of the flask_httpAuth module is that we need to implement the @auth.verify_password callback, which is executed when the @auth.login_required view function is accessed. In the callback function, the TOKEN in the HTTP header is retrieved and verified to be valid, allowing access if it is.

from flask_httpauth import HTTPBasicAuth
auth = HTTPBasicAuth()

@auth.verify_password
def verify_password(username_token):
	username_token = request.headers.get('Token')
	if username_token == ' ':
		return False
	else:
		g.currnet_user = User.verify_auth_token(username_token)
		g.token_used = True
		return g.currnet_user is not None


@auth.login_required
@app.route('/creatpost',methods=['POST'])
def new_post():
	json = request.get_json()
	newpost = Post(title=json['title'],content=json['content'])
	db.session.add(newpost)
	db.session.commit()
	return "200 OK"
Copy the code

note

The above part is the core part of the token-based management code, read the above code to know the idea, because it also calls functions such as ORM, so only the above part of the code function is not sound, please refer to the simplified complete code below.

The complete code

Note: For simplicity, the following code is the most basic code to implement functionality and does not follow specifications.

Flask

import os
from flask import Flask,make_response,render_template,redirect,url_for,jsonify,g,current_app,request,session
from flask_cors import CORS
from flask_sqlalchemy import SQLAlchemy
from flask_httpauth import HTTPBasicAuth
from flask_login import login_user,UserMixin,LoginManager,login_required
from werkzeug.security import generate_password_hash,check_password_hash
from itsdangerous import TimedJSONWebSignatureSerializer as Serializer

basedir = os.path.abspath(os.path.dirname(__file__))

# SQLite
app = Flask(__name__)
app.config['SECRET_KEY'] = 'secret-key'
app.config['SQLALCHEMY_DATABASE_URI'] = 'sqlite:///' + os.path.join(basedir,'data.sqlite')
app.config['SQLALCHEMY_TRACK_MODIFICATIONS'] = False

db = SQLAlchemy(app)

# CORS
CORS(app,supports_credentials=True)
@app.after_request
def after_request(resp):
	resp = make_response(resp)
	resp.headers['Access-Control-Allow-Origin'] = 'http://127.0.0.1:8080'
	resp.headers['Access-Control-Allow-Methods'] = 'GET,POST'
	resp.headers['Access-Control-Allow-Headers'] = 'content-type,token'
	return resp

# Http auth
auth = HTTPBasicAuth()

@auth.verify_password
def verify_password(username_token):
	username_token = request.headers.get('Token')
	if username_token == ' ':
		return False
	else:
		g.currnet_user = User.verify_auth_token(username_token)
		g.token_used = True
		return g.currnet_user is not None

@auth.error_handler
def auth_error():
	return unauthorized('Invalid credentials')

# Routes
@app.route('/login',methods=['POST'])
def login():
	json = request.get_json()
	user = User.query.filter_by(username = json['username']).first()
	if user.verify_password(json['password']):
		g.currnet_user = user
		token = user.generate_auth_token(expiration=3600)
		return token
	return "wrong password"

@app.route('/register',methods=['POST'])
def register():
	json = request.get_json()
	email = json['username'] + '@email.com'
	user = User(email=email,username=json['username'],password=json['password'])
	db.session.add(user)
	db.session.commit()
	return "200 OK register"


@app.route('/postlist')
def article():
	ptemp = Post.query.all()
	return jsonify({
			'posts': [post.to_json() for post in ptemp],
		})

@auth.login_required
@app.route('/creatpost',methods=['POST'])
def new_post():
	json = request.get_json()
	newpost = Post(title=json['title'],content=json['content'])
	db.session.add(newpost)
	db.session.commit()
	return "200 OK"

def unauthorized(message):
    response = jsonify({'error': 'unauthorized'.'message': message})
    response.status_code = 401
    return response

# ORM
class User(UserMixin,db.Model):
	__tablename__ = 'users'
	id = db.Column(db.Integer, primary_key=True)
	email = db.Column(db.String(64),unique=True,index=True)
	username = db.Column(db.String(64),unique=True,index=True)
	password_hash = db.Column(db.String(128))

	@property
	def password(self):
		raise AttributeError('password is not a readable attribute')

	@password.setter
	def password(self,password):
		self.password_hash = generate_password_hash(password)

	def verify_password(self,password):
		return check_password_hash(self.password_hash,password)

	def generate_auth_token(self,expiration):
		s = Serializer(current_app.config['SECRET_KEY'],expires_in = expiration)
		return  s.dumps({'id':self.id}).decode('utf-8')

	@staticmethod
	def verify_auth_token(token):
		s = Serializer(current_app.config['SECRET_KEY'])
		try:
			data = s.loads(token)
		except:
			return None
		return User.query.get(data['id'])

class Post(db.Model):
	__tablename__ = 'posts'
	id = db.Column(db.Integer, primary_key=True)
	title = db.Column(db.String(64),unique=True,index=True)
	content = db.Column(db.String(64))

	def to_json(self):
		json_post = {
			'title': self.title,
			'content': self.content,
		}
		return json_post

if __name__ == '__main__':
	db.drop_all()
	db.create_all()
	app.run()
Copy the code

Vue — main.js

import Vue from 'vue';
import App from './App.vue';
import VueRouter from 'vue-router';
import router from './router';
import iView from 'iview';
import 'iview/dist/styles/iview.css';
import axios from 'axios';
import vueAxios from 'vue-axios';
import store from './store';
import Vuex from 'vuex'

Vue.config.productionTip = falseUse (vueAxios,axios) vuue. Use (Vuex) router. AfterEach (route=>{window.scroll(0,0); }) router.beforeEach((to,from,next)=>{let token = localStorage.getItem('Authorization');
	if(! to.meta.isLogin){ next() }else{
		let token = localStorage.getItem('Authorization');
		if(token == null || token == ' '){
			next('/')}else{
			next()
		}
	}
})

axios.interceptors.request.use(
	config => {
		let token = localStorage.getItem('Authorization');
		if(token){
			config.headers.common['token'] = token
		}
		return config
	},
	err => {
		return Promise.reject(err);
	});


new Vue({
  el:'#app',
  render: h => h(App),
  router,
  store,
})

Copy the code

Vue — Vuex

import Vue from 'vue';
import Vuex from 'vuex';
import store from './index';

Vue.use(Vuex);

export default new Vuex.Store({
	state:{
		Authorization: localStorage.getItem('Authorization')?localStorage.getItem('Authorization') : ' '
	},
	mutations:{
		changeLogin (state, user) {
			state.Authorization = user.Authorization;
			localStorage.setItem('Authorization', user.Authorization); }}})Copy the code

Vue — router

import Vue from 'vue'
import Router from 'vue-router'

import home from '.. /components/home.vue'
import articleDetail from '.. /components/articleDetail'
import createPost from '.. /components/createPost'

Vue.use(Router)
export default new Router({
	mode:'history',
	routes:[
		{
			path:'/',
			component:home,
			name:'home',
			meta:{
				isLogin:false
			}
		},
		{
			path:'/article',
			component:articleDetail,
			name:'article',
			meta:{
				isLogin:false
			}
		},
		{
			path:'/createpost',
			component:createPost,
			name:'createpost',
			meta:{
				isLogin:true}}},])Copy the code

Vue — Components — home.vue

<template>
	<div class="super">
		<div class="header">
			<div class="buttomDiv">
				<Button type="success" class="loginButton" @click="showLoginModal">Login</Button>
				<Button type="primary" class="loginButton" @click="showRegisterModal">Register</Button>
			</div>
		</div>

		<div class = "content">
			<div class="contentLeft">
				<div
					v-for = "post in blogList"
					>
					<thumbnail 
						v-bind:title=post.title
						v-bind:content=post.content
					></thumbnail>
				</div>
			</div>
			<div class="contentRight"></div>
			
		</div>

		<Modal v-model="registerModalStatus" @on-ok="registerEvent">
			<p>Register</p>
			<Input v-model="username" placeholder="Username" style="width: 300px" />
			<Input v-model="password" placeholder="Password" style="width: 300px" />
		</Modal>

		<Modal v-model="loginModalStatus" @on-ok="loginEvent">
			<p>Login</p>
			<Input v-model="username" placeholder="Username" style="width: 300px" />
			<Input v-model="password" placeholder="Password" style="width: 300px" />
		</Modal>

	</div>
</template>

<script>
	import axios from 'axios'
	import {mapMutations} from 'vuex'
	import store from '.. /store'
	import thumbnail from './articleThumbnail.vue'

	export default{
		name: 'home',
		data:function() {return {
				loginModalStatus:false,
				registerModalStatus:false,
				username:' ',
				password:' ',
				blogList:' ',
			}
		},
		components:{
			thumbnail:thumbnail,
		},
		created() {localStorage.removeItem("Authorization"."")
			let _this = this
			axios.get('http://127.0.0.1:5000/postlist')
					.then(function(response){
						_this.blogList = response.data.posts
					})
					.catch(function(error){ }) }, methods:{ ... mapMutations(['changeLogin'
			]),
			showRegisterModal:function(){
				this.registerModalStatus = true;
			},
			showLoginModal:function(){
				this.loginModalStatus = true;
			},
			registerEvent:function() {let that = this
				axios.post('http://127.0.0.1:5000/register',{
					username:this.username,
					password:this.password,
					})
				.then(function(res){

				})
				.catch(function(error){

				})
			},
			loginEvent:function() {let _this = this
				axios.post('http://127.0.0.1:5000/login',{
							username:this.username,
							password:this.password,
						})
					.then(function(response){
						let token = response.data
						_this.changeLogin({Authorization: token})
					})
					.catch(function(error){
					})
			},
			navigator:function(){
				this.$router.push("/article")
			},

		},
	}
</script>

<style scoped>

</style>

Copy the code

Afterword.

Complete code github address haythamBlog haythamBlog_flask

For more original Haytham articles, please follow the public account xu Julong: