If you've want to add authentication to your backend, you would use token-based authentication like JWT
. JWT
stands for jsonwebtoken
and is an encrypted token which can contain some payload data like the user id
which can be used to perform actions as that user.
The thing with JWT is that you will have to handle expiration, refreshing and blacklisting. Well, there are some libraries like express-jwt
link (for node backends) that make it easier, but, authentication is a supported/built in part of a url!
Don't know what I mean? Well, if you've worked with MongoDB Atlas, your database access URL will be something like:
mongodb+srv://username:password@...
So you see, you send in your username and password along with the URL.
Well, but how do I authenticate the user that way?
I hear you ask? Well, that's the point of this tutorial.
Project setup
Let's setup our flask project.
First, let's create a folder for our app:
mkdir flask-auth-test && cd flask-auth-test
# Add to .gitignore just in case you want to push
echo "backend/venv" >> .gitignore
Now, we need to set up our flask app.
# Create a virtual environment
# pip3 install venv
# OR
# sudo apt install python3-venv
# for debian/ubuntu systems if the below command fails
python3 -m venv venv
# Activate the venv
source venv/bin/activate
# FOR FISH: source venv/bin/activate.fish
# Windows: .\venv\Scripts\activate
pip install flask flask-sqlalchemy flask-httpauth flask-cors python-dotenv
touch app.py
Setup app
First, let's finish with the boiler-platey code.
from flask import Flask, jsonify, request, g
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
CORS(app)
@app.cli.command("migrate")
def migrate_cmd():
db.create_all()
@app.route('/')
def index():
return jsonify("works!")
Now, let's run the app with:
export FLASK_DEBUG=1
python3 -m flask run
Then, if we perform a GET
request to http://localhost:5000/
then, you will get this output:
$ curl http://localhost:5000
"works"
Now, let's do the database.
# ...
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
username = db.Column(db.String)
password = db.Column(db.String)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
Alright! We can create our database!
python3 -m flask migrate
You should now see a new file called database.db
in the folder. Nice!
Adding authentication
Let's now add authentication to our app.
# add this import at the top
from flask_httpauth import HTTPBasicAuth
# ...
# now add this under Cors(App)
auth = HTTPBasicAuth()
# This is used to verify if the password is correct
@auth.verify_password
def verify_password(email: str, password: str):
user = User.query.filter_by(email=email)
if not user:
return False
# Add the user to global variables
g.user = user
return True
Now that the authentication is secure, let's add a register route.
#...
@app.route("/register", methods=["POST"])
def register():
email = request.json.get("email").lower()
password = request.json.get("password")
if not email or not password:
return jsonify({"success": False, "message": "One or more fields empty or not present"}), 400
if not re.match(pattern=r"[\w_.]{3,}@\w{3,}\.\w{2,}", string=email):
return jsonify({"success": False, "message": "Invalid email"}), 400
user = User.query.filter_by(email=email)
if user:
return jsonify({"success": False, "message": "Account already exists! Choose a different email or username"}), 400
u = User(email=email, password=password)
u.save()
return jsonify({"success": True})
We don't need a login route because flask-httpauth
handles that for us. Now, let's add a protected route.
@app.route("/secret")
@auth.login_required
def secret():
return jsonify("never gonna give you up")
The /secret
route is now protected and can not be accessed by an unauthorized user. Now, let's try registering:
curl -X POST -H "Content-Type: application/json" -d '{"email: "aaaaa@aaaa.aaa", "password": "a"}' http://localhost:5000/register
And you should get {"success": True}
!
Nice!
Now, if you want to access the /secret
route, you just add the user like this:
# email:password
curl -u aaaaa@aaaa.aaa:a http://localhost:5000
And if everything went well, you should get rickrolled!
Using fetch
But how do I use it in my frontend?
If your frontend usesjavascript
(which it most likely does), you canfetch
the backend like so:
fetch("http://localhost:5000/secret", {
headers: {
# The btoa method encodes the auth string and is available in all browsers.
Authorization: `Basic ${btoa("aaaaa@aaaa.aaa:a")`
}
}).then(() => {/*...*/});
Full code
from flask import Flask, jsonify, request, g
from flask_httpauth import HTTPBasicAuth
from flask_sqlalchemy import SQLAlchemy
from flask_cors import CORS
app = Flask(__name__)
app.config["SQLALCHEMY_DATABASE_URI"] = "sqlite:///database.db"
app.config["SQLALCHEMY_TRACK_MODIFICATIONS"] = False
db = SQLAlchemy(app)
CORS(app)
auth = HTTPBasicAuth()
@app.cli.command("migrate")
def migrate_cmd():
db.create_all()
# This is used to verify if the password is correct
@auth.verify_password
def verify_password(email: str, password: str):
user = User.query.filter_by(email=email)
if not user:
return False
# Add the user to global variables
g.user = user
return True
# DB
class User(db.Model):
__tablename__ = "users"
id = db.Column(db.Integer, primary_key=True)
email = db.Column(db.String)
username = db.Column(db.String)
password = db.Column(db.String)
def save(self):
db.session.add(self)
db.session.commit()
def delete(self):
db.session.delete(self)
db.session.commit()
# Routes
@app.route("/register", methods=["POST"])
def register():
email = request.json.get("email").lower()
password = request.json.get("password")
if not email or not password:
return jsonify({"success": False, "message": "One or more fields empty or not present"}), 400
if not re.match(pattern=r"[\w_.]{3,}@\w{3,}\.\w{2,}", string=email):
return jsonify({"success": False, "message": "Invalid email"}), 400
user = User.query.filter_by(email=email)
if user:
return jsonify({"success": False, "message": "Account already exists! Choose a different email or username"}), 400
u = User(email=email, password=password)
u.save()
return jsonify({"success": True})
@app.route('/')
def index():
return jsonify("works!")
@app.route("/secret")
@auth.login_required
def secret():
return jsonify("never gonna give you up")
Conclusion
This method of authentication is much easier than using JWT. Hope you've learned something, and of course, correct my mistakes in the comments! :P
Top comments (0)