I've made a login page using Python Flask which works with MySQL. I started learning Flask 2 days ago and it was fun, so I came up with this:

from flask import Flask, session, redirect, url_for, escape, request, render_template
from hashlib import md5
import MySQLdb

#######################
#   DATABASE CONFIG   #
#######################

db = MySQLdb.connect(host="localhost", user="root", passwd="", db="test")
cur = db.cursor()

@app.route('/')
def index():

error = None
return redirect(url_for('index'))
if request.method == 'POST':
cur.execute("SELECT COUNT(1) FROM users WHERE name = %s;", [username_form]) # CHECKS IF USERNAME EXSIST
if cur.fetchone()[0]:
cur.execute("SELECT pass FROM users WHERE name = %s;", [username_form]) # FETCH THE HASHED PASSWORD
for row in cur.fetchall():
return redirect(url_for('index'))
else:
error = "Invalid Credential"
else:
error = "Invalid Credential"

@app.route('/logout')
def logout():
return redirect(url_for('index'))

app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

if __name__ == '__main__':
app.run(debug=True)


index.html

<!DOCTYPE html>
<html>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<script src="{{url_for('static', filename='js/bootstrap.min.js')}}"></script>
<body>
<div class="container" style="margin-top:50px;">
<div class="row">
<div class="col-md-6 col-md-offset-3 text-center">
{% if session_user_name %}
<p>Hello <b>{{ session_user_name }}</b></p>
{% endif %}
<a href="{{ url_for('logout') }}">Logout</a>
</div>
</div>
</div>
</body>
</html>


<!DOCTYPE html>
<html>
<link rel="stylesheet" type="text/css" href="{{ url_for('static', filename='css/bootstrap.min.css') }}">
<script src="{{url_for('static', filename='js/bootstrap.min.js')}}"></script>
<body>
<div class="container" style="margin-top:50px;">
<div class="row">
<div class="col-md-6 col-md-offset-3">
<form action="" method="POST">
{% if error %}
<p class=error><strong>Error:</strong> {{ error }}
{% endif %}
<div class="input-group">
</div>
<br>
<div class="input-group">
</div>
<br>
<input type="Submit" value="Login" class="btn btn-default btn-sm">
</form>
</div>
</div>
</div>
</body>
</html>


# Style

Python has a style guide. It's called PEP8, and normally if you follow it you get easier to read code.
It's a fairly small read, and you can learn some new things from it.

1. It's recommended in PEP8 to limit the character width of scripts to 79 characters. It has a few benefits:

• People on StackExchange don't need to use the horizontal scroll.

• It's easier to read. For the same reason that newspapers use two, or more, columns.

• On a wide-screen computer you can have 3 scripts open at the same time. (Depending on font size)

You may think it's silly, but I and others find it nice.

2. 'Box' comments, see below, are quite ugly, and don't add much to the code.

#######################
#   DATABASE CONFIG   #
#######################

3. Using a not to swap the if and else can improve readability. login's if cur.fetchone()[0]: block is quite large, and can lead to the person reading to forget their place, and need to re-read the if to understand their place in the code.

Whilst, it's definitely not a big deal here, it can easily become one.

# An example of how to avoid this:
if not cur.fetchone()[0]:
error = "Invalid Credential"
else:
...


Overall your style is really good.

# Improvements

1. You may want to move all your global settings into one area. If they grow more than the few you have, you have 3 options to hold them.

1. Make a class defined at the top of your code to hold them.

2. Make a module to hold them. Careful of putting your secret key there! As people will be able to import it.

3. Put them in a database, you can use your MySQL database, or just a json or csv file.

2. Normally you want to keep globals to a minimum. Also I would say that your db is dangerously placed. For example what if I were to do the following, assuming that your python file is called main.py:

import main

main.cur.execute('DROP DATABASE')

3. There is an anti-pattern called the 'arrow-anti-pattern', it leads to heavily indented code, that has an arrow-head shape. It normally occurs due to people not using 'break clauses'.

For example in index:

# Leads to an arrow
def index():
# So is this the main page you're meant to go to?

# Avoids the arrow, and wondering what the main page is.
def index():


4. You use a variable 'error' in login. Python has errors, and there easily extendable.

Also Python follows EAFP more than LBYL. And so using exceptions is the norm.

# Define a new error
class ServerError(Exception):pass

# Use error
if not cur.fetchone()[0]:
raise ServerError('Invalid Credential')

# Catch and handle error
error = None
try:
...
except ServerError as e:
error = str(e)

5. Python discourages the use of '%s' % 'yo' in-favour for the new str.format. Whilst there are some places where you have to use %, this is not one of them.

The new micro language, is safer, and better than the old one. For example, you can index arguments in it, do string formatting, and change the type! But for this, we'll just use the substitution.

'SELECT COUNT(1) FROM users WHERE name = {};'.format(username_form)


This opens you up to SQL injection attacks, and so this is not desirable. The current way is definitely preferred.

6. Your for loop, is re-allocating error for every failed password. This is the equivalent of making an is prime function, and restating that you have not proven it's not prime every number you check.

Instead you can just move it out of the for loop.

for row in cur.fetchall():
return redirect(url_for(index))

7. I think your 'errors' are not very descriptive.
Earlier today/yesterday I forgot my password for SO. So I was trying password after password, where the thought crossed my mind, 'maybe I'm using the wrong email', to which I tried my old email. And was nicely told that account didn't exist.

It's a small change to the system, that I'm thankful for. And so you may want to change the messages, to be like that.

As a high-level design change, you could try REST, I don't know how much flask has support for it to be a seamless experience, but it's really nice, as it allows the option to not reload the page when a user tries to log in. Which leads to a reduction in band-width.

Also, implementing your own password handling scheme may be a very bad idea. Yours doesn't salt the password, and IIRC that's an old technique. I would encourage using a standard such as OAuth or OpenID. (I'm not a security person, so this technology may be out of date.)

I didn't review your HTML, but from a flask template point of view it seems fine.

If I were to re-write your code, I would get the following:

from flask import Flask, session, redirect, url_for, escape, request, render_template
from hashlib import md5
import MySQLdb

if __name__ == '__main__':
db = MySQLdb.connect(host="localhost", user="root", passwd="", db="test")
cur = db.cursor()
app.secret_key = 'A0Zr98j/3yX R~XHH!jmN]LWX/,?RT'

class ServerError(Exception):pass

@app.route('/')
def index():

return redirect(url_for('index'))

error = None
try:
if request.method == 'POST':
cur.execute("SELECT COUNT(1) FROM users WHERE name = {};"

if not cur.fetchone()[0]:

cur.execute("SELECT pass FROM users WHERE name = {};"

for row in cur.fetchall():
return redirect(url_for('index'))

except ServerError as e:
error = str(e)