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
- The front-end submits the account password to the background
- Background validation, by which token is returned
- The front-end sets the token in the request header before each request (using the AXIos hook)
- 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: