Upgrade to Pro — share decks privately, control downloads, hide ads and more …

2017 - Everything You Ever Wanted to Know About Web Authentication in Python

PyBay
August 21, 2017

2017 - Everything You Ever Wanted to Know About Web Authentication in Python

Description

Want to learn how web authentication works? How your login information is transmitted from a web browser to a web server, and what happens from that point onwards? How authentication protocols work behind the scenes?

By the end of this talk, you’ll be intimately familiar with web authentication in Python.

Abstract

Want to learn how web authentication works? How your login information is transmitted from a web browser to a web server, and what happens from that point onwards? How authentication protocols work behind the scenes?

In this talk, Randall Degges, Developer Advocate at Okta, will walk you through the entire web authentication flow, covering:

Credential transmission
Cookies
Sessions
Databases
Best practices
By the end of this talk, you’ll be intimately familiar with web authentication in Python.

Bio

Randall Degges leads Developer Advocacy at Okta, previously Stormpath, where he builds open source security libraries and helps make the internet a little safer. In a prior life, Randall was the CTO of OpenCNAM, the largest Caller ID API service.

In his free time, Randall geeks out on web best practices, explores new technologies, and spends an inordinate amount of time writing Python, Node, and Go projects. As a fun fact, Randall runs ipify.org, one of the largest IP lookup APIs which serves over 25 billion requests per month.

https://www.youtube.com/watch?v=IdysobPKa6g

PyBay

August 21, 2017
Tweet

More Decks by PyBay

Other Decks in Programming

Transcript

  1. Prepare the App $ touch server.py $ mkdir templates $

    touch templates/base.html $ touch templates/index.html $ touch templates/register.html $ touch templates/login.html $ touch templates/dashboard.html
  2. <html> <head> <title>Flask Simple Auth: {{ title }}</title> </head> <body>

    {% block body %}{% endblock %} </body> </html> ➔ templates/base.html Home | Login | Register More HTML
  3. {% extends "base.html" %} {% set title = 'Home' %}

    {% block body %} <h1>Flask Simple Auth!</h1> <p> Welcome to Flask Simple Auth! Please <a href="/login">login</a> or <a href="/register">register</a> to continue. </p> {% endblock %} ➔ templates/index.html
  4. {% extends "base.html" %} {% set title = 'Register' %}

    {% block body %} <h1>Create an Account</h1> <form method="post"> <span>First Name:</span> <input type="text" name="firstName" required=true> <br> <span>Last Name:</span> <input type="text" name="lastName" required=true> <br> <span>Email:</span> <input type="email" name="email" required=true> <br> <span>Password:</span> <input type="password" name="password" required=true> <br> <input type="submit"> </form> {% endblock %} ➔ templates/register.html
  5. {% extends "base.html" %} {% set title = 'Login' %}

    {% block body %} <h1>Log Into Your Account</h1> <form method="post"> <span>Email:</span> <input type="email" name="email" required=true> <br> <span>Password:</span> <input type="password" name="password" required=true> <br> <input type="submit"> </form> {% endblock %} ➔ templates/login.html
  6. {% extends "base.html" %} {% set title = 'Dashboard' %}

    {% block body %} <h1>Dashboard</h1> <p>Welcome to your dashboard! You are now logged in.</p> {% endblock %} ➔ templates/dashboard.html
  7. from flask import Flask from flask import render_template app =

    Flask(__name__) @app.route("/") def index(): return render_template('index.html') @app.route("/login", methods=['GET', 'POST']) def login(): return render_template('login.html') @app.route("/register", methods=['GET', 'POST']) def register(): return render_template('register.html') @app.route("/logout") def logout(): pass @app.route("/dashboard") def dashboard(): return render_template('dashboard.html') ➔ server.py
  8. <form method="post"> First Name: <input type="text" name="firstName" required> Last Name:

    <input type="text" name="lastName" required> Email: <input type="email" name="email" required> Password: <input type="password" name="password" required> <input type="submit"> </form> You know... HTML!
  9. from flask import jsonify from flask import request # ...

    @app.route("/register", methods=['GET', 'POST']) def register(): if request.method == 'POST': return jsonify(request.form) return render_template('register.html') Form Data
  10. > use test; switched to db test > show collections;

    > db.users.insert({ email: "[email protected]", password: "woot!" }); WriteResult({ "nInserted" : 1 }) > db.users.find(); { "_id" : ObjectId("59247d0f3dacfc7ef19a41d2"), "email" : "[email protected]", "password" : "woot!" } > The Basics
  11. from flask import redirect from flask import url_for # ...

    @app.route("/register", methods=['GET', 'POST']) def register(): if request.method == 'POST': mongo.db.users.insert_one(request.form.to_dict()) return redirect(url_for('dashboard')) return render_template('register.html') Creating Users
  12. @app.route("/login", methods=['GET', 'POST']) def login(): if request.method == 'POST': user

    = mongo.db.users.find_one({'email': request.form['email']}) if user['password'] == request.form['password']: return redirect(url_for('dashboard')) return render_template('login.html') Authenticating Users
  13. Home Page Dashboard What If... Billing You had to enter

    your credentials over and over again on every page you visited?
  14. Website The Idea Login Unique ID Hey! Remember this value!

    I need you to send it back to me next time you visit so I know who you are! Browser These are sessions.
  15. Using Sessions from flask import session # ... app.config['SECRET_KEY'] =

    'SUPER_SECRET!' # ... @app.route("/login", methods=['GET', 'POST']) def login(): if request.method == 'POST': user = mongo.db.users.find_one({'email': request.form['email']}) if user['password'] == request.form['password']: session['id'] = str(user['_id']) return redirect(url_for('dashboard')) return render_template('login.html')
  16. from bson.objectid import ObjectId # ... @app.route("/dashboard") def dashboard(): if

    'id' not in session: return redirect(url_for('login')) user = mongo.db.users.find_one({'_id': ObjectId(session['id'])}) if user: return render_template('dashboard.html') return redirect(url_for('login')) Improving the Dashboard
  17. Password Hashing! Given a hash, you can never retrieve the

    original password that created it. Hashes are one-way. The same password always generates the same hash.
  18. Hashing • md5 • sha256 • bcrypt • scrypt •

    argon2 • etc. • SHITTY • NOPE! • SAFE • AWESOME • YES!! • NO
  19. bcrypt (pseudo) >>> password = 'hi' >>> hash = bcrypt(password)

    >>> print(hash) '$2a$10$uS.pE0aS0NlsgbvLd6EruO5VDKllinIZLF3C84OYzWHFiyKYfZVXy'
  20. Improving Registration $ pip install bcrypt passlib from passlib.hash import

    bcrypt # ... @app.route("/register", methods=['GET', 'POST']) def register(): if request.method == 'POST': user_data = request.form.to_dict() user_data['password'] = bcrypt.hash(user_data['password']) user = mongo.db.users.insert_one(user_data) session['id'] = str(user['_id']) return redirect(url_for('dashboard')) return render_template('register.html')
  21. Improving Login @app.route("/login", methods=['GET', 'POST']) def login(): if request.method ==

    'POST': user = mongo.db.users.find_one({'email': request.form['email']}) if user and bcrypt.verify(request.form['password'], user['password']): session['id'] = str(user['_id']) return redirect(url_for('dashboard')) return render_template('login.html')
  22. Our Improved User { "_id" : ObjectId("5924ad584f7ddf1eea467408"), "firstName" : "Randall",

    "lastName" : "Degges", "email" : "[email protected]", "password" : "$2a$14$czZ5IJCRsi7TvqwW8InVsOOnzBffT3e19unr1ecFq0XQGiTatN0wm", "__v" : 0 }
  23. from flask import g from werkzeug.local import LocalProxy user =

    LocalProxy(lambda: g.user) # ... @app.before_request def load_user(): if 'id' in session: g.user = mongo.db.users.find_one({'_id': ObjectId(session['id'])}) del g.user['password'] return Smart User Loading
  24. from functools import wraps def login_required(func): @wraps(func) def decorator(*args, **kwargs):

    if not user: return redirect(url_for('login')) return func(*args, **kwargs) return decorator Force Authentication GTFO
  25. Cross Site Request Forgery Hey Randall, Check out this picture

    of my dog! <img src="http://bank.com/withdraw?amount=1000000&amp; toAccount=BadGuy">
  26. Use Cookie Flags from datetime import timedelta # Don't let

    Javascript access cookies app.config['SESSION_COOKIE_HTTPONLY'] = True # Only set the cookie over SSL app.config['SESSION_COOKIE_SECURE'] = True # How long should the session last? app.config['PERMANENT_SESSION_LIFETIME'] = timedelta(minutes=30)
  27. “… In other words, any authentication your application requires can

    be bypassed by a user with local privileges to the machine on which the data is stored. Therefore, it's recommended not to store any sensitive information in local storage.” - OWASP (Open Web Application Security Project)
  28. What ARE JWTs Good For? Website Reset my password. Ok!

    I’ve emailed you a link that has a JWT in the URL which will expire in 30 minutes. Ok! I clicked the link. This JWT looks legit. I suppose I’ll let you reset your password. Ok, your PW has been reset.
  29. In Summary JWTs are not meant to be used for

    persisting state. Making them do the same thing as cookies in a less efficient and secure manner is bad for you. Don’t use them unless you know how.