Very Simple Admin Panel With Python Flask (Free Download)

Welcome to a tutorial on how to create a simple admin panel in Python Flask. So you need an admin panel, but don’t want to go through all of those “crazy packages”? Well, here is a simple version of mine – Read on!

 

 

TABLE OF CONTENTS

 

DOWNLOAD & NOTES

Here is the download link to the example code, so you don’t have to copy-paste everything.

 

EXAMPLE CODE DOWNLOAD

Source code on GitHub Gist

Just click on “download zip” or do a git clone. I have released it under the MIT license, so feel free to build on top of it or use it in your own project.

 

SORRY FOR THE ADS...

But someone has to pay the bills, and sponsors are paying for it. I insist on not turning Code Boxx into a "paid scripts" business, and I don't "block people with Adblock". Every little bit of support helps.

Buy Me A Coffee Code Boxx eBooks

 

PYTHON FLASK ADMIN PANEL

All right, let us now into the simple admin panel. Not going to explain everything line-by-line, but here’s a quick walkthrough.

 

QUICK SETUP

  • Create a virtual environment virtualenv venv and activate it – venv\Scripts\activate (Windows) venv/bin/activate (Linux/Mac)
  • Install required libraries – pip install install flask pyjwt bcrypt
  • For those who are new, the default Flask folders are –
    • static Public files (JS/CSS/images/videos/audio)
    • templates HTML pages

 

PART 1) HTML TEMPLATE

1A) THE HTML

templates/1-admin.html
<!DOCTYPE html>
<html>
  <head>
    <title>{{ title }}</title>
    <meta charset="utf-8">
    <meta name="robots" content="noindex">
    <meta name="viewport" content="width=device-width, initial-scale=1.0, maximum-scale=1.5">
    <link rel="stylesheet" href="static/1-admin.css">
    <script src="static/1-admin.js"></script>
    {% block header %} {% endblock %}
  </head>
  <body>
    <!-- (A) SIDEBAR -->
    <div id="pgside">
      <!-- (A1) BRANDING OR USER -->
      <div id="pguser" onclick="if(confirm('Sign Off?')){logout();}">
        <input type="hidden" name="logout" value="1">
        <img src="static/pic.png" id="pguserimg">
        <div class="txt">
          <div id="pgusername">{{ user }}</div>
          <div id="pguseracct">account | logoff</div>
        </div>
      </div>
 
      <!-- (A2) MENU ITEMS -->
      <a href="#" class="current">
        <i class="ico">&#9733;</i>
        <i class="txt">Section A</i>
      </a>
      <a href="#">
        <i class="ico">&#9728;</i>
        <i class="txt">Section B</i>
      </a>
      <a href="#">
        <i class="ico">&#9737;</i>
        <i class="txt">Section C</i>
      </a>
    </div>

    <!-- (B) MAIN -->
    <main id="pgmain">{% block content %} {% endblock %}</main>
  </body>
</html>

  • First, let us deal with the HTML template. No need to panic, this is only a simple 2-column layout.
    • <div id="pgside"> Sidebar menu.
    • <main id="pgmain"> Main contents area.
  • The variables and blocks:
    • title Title of the page.
    • header Use this to insert your own CSS, JS, and meta.
    • user The current user.
    • content Page content.

 

 

1B) THE JAVASCRIPT

static/1-admin.js
function logout () {
  fetch("/out", { method:"post" })
  .then(res => res.text())
  .then(txt => {
    if (txt=="OK") { location.href = "../login"; }
    else { alert(txt); }
  })
  .catch(err => {
    console.error(err);
    alert("Error - " + err.message);
  });
  return false;
}

A tiny bit of Javascript to process logout requests, send a POST request to the /out endpoint.

 

PART 2) DUMMY HOME PAGE

templates/2-home.html
{% extends "1-admin.html" %}
 
{% block content %}
<h1>It Works!</h1>
<p>You are in the protected admin page.</p>
{% endblock %}>

Yep, this is just a dummy home page. You guys who are new, do your own studies on “Python Flask HTML template”… It’s too much to explain here.

 

 

PART 3) LOGIN PAGE

3A) THE HTML

templates/3-login.html
<form method="post" id="login" onsubmit="return login()">
  <h1>ADMIN LOGIN</h1>
  <label>Email</label>
  <input type="email" name="email" required value="joy@doe.com">
  <label>Password</label>
  <input type="password" name="password" required value="12345">
  <input type="submit" value="Login">
</form>

Next, a simple login page – Remember to remove the email and password in your own project.

 

3B) THE JAVASCRIPT

static/3-login.js
function login () {
  // (A) GET EMAIL + PASSWORD
  var data = new FormData(document.getElementById("login"));

  // (B) AJAX REQUEST
  fetch("/in", { method:"POST", body:data })
  .then(res => res.text())
  .then(txt => {
    if (txt=="OK") { location.href = "../"; }
    else { alert(txt); }
  })
  .catch(err => {
    console.error(err);
  alert("Error - " + err.message);
  });
  return false;
}

Send the email and password to the /in endpoint, and process the login. Take note, we are using JSON Web Token here to drive the user session.

 

 

PART 4) PYTHON FLASK SERVER

4A) INITIALIZE

4-server.py
# (A) INIT
# (A1) LOAD REQUIRED PACKAGES
from flask import Flask, render_template, make_response, request, redirect, url_for
from werkzeug.datastructures import ImmutableMultiDict
import bcrypt, jwt, time, random
 
# (A2) FLASK INIT
app = Flask(__name__)
# app.debug = True
 
# (A3) SETTINGS
HOST_NAME = "localhost"
HOST_PORT = 80
JWT_KEY = "YOUR-SECRET-KEY"
JWT_ISS = "YOUR-NAME"
JWT_ALGO = "HS512"

The first section of the server-side script should be self-explanatory. Load the required packages and define the settings – A gentle reminder to change those settings to your own.

  • HOST_NAME and HOST_PORT where you want to deploy this project.
  • JWT_KEY Generate your own random secret key for the JSON Web Token, and NEVER expose it.
  • JWT_ISS The issuer is usually set to your company or domain name.

 

4B) THE USERS

4-server.py
# (B) USERS - AT LEAST HASH THE PASSWORD!
# password = "12345"
# print(bcrypt.hashpw(password.encode("utf-8"), bcrypt.gensalt()))
USERS = {
  "joy@doe.com" : b'$2b$12$3kcEc8qxnrHGCBHM8Bh0V.gWEFpsxpsxbkCfmk4BDcjBkGsVLut8i'
}

Keep all the users in a dictionary, this is the lazy way to implement a user/login system without a database.

P.S. I am just keeping things simple here. Feel free to add your own users and implement your own database.

 

 

4C) JSON WEB TOKEN

4-server.py
# (C) JSON WEB TOKEN
# (C1) GENERATE JWT
def jwtSign(email):
  # https://stackoverflow.com/questions/2511222/efficiently-generate-a-16-character-alphanumeric-string
  rnd = "".join(random.choice("0123456789ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz~!@#$%^_-") for i in range(24))
  now = int(time.time())
  return jwt.encode({
    "iat" : now, # ISSUED AT - TIME WHEN TOKEN IS GENERATED
    "nbf" : now, # NOT BEFORE - WHEN THIS TOKEN IS CONSIDERED VALID
    "exp" : now + 3600, # EXPIRY - 1 HR (3600 SECS) FROM NOW IN THIS EXAMPLE
    "jti" : rnd, # RANDOM JSON TOKEN ID
    "iss" : JWT_ISS, # ISSUER
    # WHATEVER ELSE YOU WANT TO PUT
    "data" : { "email" : email }
  }, JWT_KEY, algorithm=JWT_ALGO)
 
# (C2) VERIFY JWT
def jwtVerify(cookies):
  try:
    user = jwt.decode(cookies.get("JWT"), JWT_KEY, algorithms=[JWT_ALGO])
    return user["data"]
  except:
    return False

If you have not heard of JSON Web Token (JWT), it’s basically just an encrypted cookie:

  • (C1) On user login, jwtSign() will generate an encrypted JWT cookie.
  • (C2) On subsequent visits to protected admin pages, we use jwtVerify() to decode the JWT cookie. Allow access only if it is a valid token.

 

4D) SERVE HTML PAGES & ENDPOINTS

4-server.py
# (D) ROUTES
# (D1) ADMIN PAGE
@app.route("/")
def index():
  user = jwtVerify(request.cookies)
  if user == False:
    return redirect(url_for("login"))
  else:
    return render_template("2-home.html", title="Home Page", user=user["email"])
 
# (D2) LOGIN PAGE
@app.route("/login")
def login():
  if jwtVerify(request.cookies):
    return redirect(url_for("index"))
  else:
    return render_template("3-login.html")
 
# (D3) LOGIN ENDPOINT
@app.route("/in", methods=["POST"])
def lin():
  data = dict(request.form)
  valid = data["email"] in USERS
  if valid:
    valid = bcrypt.checkpw(data["password"].encode("utf-8"), USERS[data["email"]])
  msg = "OK" if valid else "Invalid email/password"
  res = make_response(msg, 200)
  if valid:
    res.set_cookie("JWT", jwtSign(data["email"]))
  return res
 
# (D4) LOGOUT ENDPOINT
@app.route("/out", methods=["POST"])
def lout():
  res = make_response("OK", 200)
  res.delete_cookie("JWT")
  return res

To serve the HTML pages and “endpoints”:

  • / The dummy home page, only for users who are signed in.
  • /login The login page.
  • /in Login process. Send the email/password, and this will set a JWT cookie on validation.
  • /out Logout process. Simply removes the JWT cookie.

 

 

4E) GO!

4-server.py
# (E) START!
if __name__ == "__main__":
  app.run(HOST_NAME, HOST_PORT)

Don’t think this needs any explanation.

 

EXTRAS

That’s all for the tutorial, and here is a small section on some extras and links that may be useful to you.

 

WHAT’S NEXT?

This is a working example out of the box. Just add your own users, pages, and update the HTML templates.

 

LINKS & REFERENCES

 

THE END

Thank you for reading, and we have come to the end. I hope that it has helped you to better understand, and if you want to share anything with this guide, please feel free to comment below. Good luck and happy coding!