Securing Django and Flask APIs using JWT

About Me

Md. Shahbaz Alam

Full Stack Developer

Auth0 Ambassador

Mozilla Reps Mentor

GDG Ranchi Organizer

 

@mdsbzalam

 

Agenda

1. JSON Web Tokens ( JWT )

2. Python Frameworks

3. Flask

4. Django

5. Conclusion

JWT

What is JSON Web Tokens?

  • Based on Web Standard ( RFC 7519 )
  • securely communicate JSON Objects
  • A way to encode information
  • Allow shared parties to verify information is legit
  • Secret-based Verification 
  • When secret changes then information is no more legit
  • Consists of a header, payload and signature
  • Self-contained

JWT

JSON Web Token

http://bit.ly/pyconid-jwt-token

JWT

The JWT Header

The header is a JSON Object usually consisting of the type( typ ) , which is JWT, and the algorithm used for encrypting the JWT (alg ):

{
  "alg": "HS256",
  "typ": "JWT"
}

JWT

The JWT Payload

The Payload is a JSON object that consists of user defined attributes ( called public claims ) . Some attributes are defined in the standard ( these are called reserved claims ).

{
    // reserved claim
    "iss": "https://myapi.com", 
    // public claim
    "user": "mdsbzalam" 
}

JWT

The JWT Signature

The Signature is the encoded header and payload, signed with a secret.

HMACSHA256(
  base64UrlEncode(header) + "." +
  base64UrlEncode(payload),
  secret
)

This accomplishes several tasks at once, including:

  • Proves the identity of the sender
  • Ensures the message has not changed

JWT

The JWT Token

A finished token looks like [encoded header].[encoded payload].[signature] :

JWT

The JWT Token

http://bit.ly/pyconid-jwt-token

JWT

Advantages:

Easy to parse

Easy to Sign

Compact

compared to XML

compared to XML

compared to ...

You get it.

JWT

Implementing JWT in Python

$ pip install jwt
$ python 
>>> import jwt

>>> jwt.encode({'jwt':'JSON Web Token'},'secret')

JWT

decoding JWT in Python

$
......

>>> token = jwt.encode({'py':'Python'},'secret')

>>> jwt.decode(token , 'secret')

{'py':'Python'}

Python Frameworks

Why Python?

  • Fastest growing programming language
  • Huge Community base is improving every aspect of the language
  • More & more open source libraries are being released to address AI, ML, and Web Development.
  • https://docs.python.org/3/ for new adopters to learn its essence fast

Python Frameworks

Full-stack frameworks:

Python Frameworks

Microframeworks:

Python Frameworks

Asynchronous frameworks:

other:

Django

Flask

Django is older, more mature, and a little bit more popular.

On GitHub, this framework has around 37k stars, 1.6k contributors, ~200 releases, and more than 16k forks.

On StackOverflow, roughly 1.2% of questions asked in a given month are related to Django.

Flask, although less popular, is not far behind.

 

On GitHub, Flask has almost 39k stars, ~480 contributors, ~26 releases, and more than 11k forks.

 

On StackOverflow, up to 0.2% of questions asked in a given month are related to Flask.

Flask App

Dependencies:

  • Python 2 or 3
  • Flask
  • jsonify
  • request
  • make_response
  • jwt
  • datetime
  • wraps

Flask App

Basic Flask App

from flask import Flask

app = Flask(__name__)

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

Flask App

Lets modify few things..

from flask import Flask
from flask import jsonify
from flask import request
from flask import make_response

...
...

Flask App

Now add Authentication to it

...
@app.route('/login')
def login():
    auth = request.authorization

    if auth and auth.password == 'pyconid':
	return jsonify({'message' : 'logged in})

    return make_response('Could not verify!', 401,
           {'WWW-Authenticate' : 
             'Basic realm:"Login Required"'})
...

Flask App

Lets add two more Routes

...
@app.route('/unprotected')
def unprotected():
    return jsonify({'message' : 
           'Anyone can view this.'})

@app.route('/protected')
def protected():
    return jsonify({'message' : 
    'Only available to people 
               with valid tokens.'})
...

Flask App

It's time to import JWT

...
import jwt
import datetime

...
...

Flask App

Modifying Login method to set token

...
app.config['SECRET_KEY'] = 'indonesia'
...
if auth and auth.password == 'pyconid':
    token = jwt.encode({'user': auth.username,
            'exp': datetime.datetime.utcnow() 
            + datetime.timedelta(seconds=50)},
            app.config['SECRET_KEY'])
    return jsonify({'message' : 'logged in})
...
...

Flask App

Creating decorator for protected route

...
def token_required(f):
  @wraps(f)
  def decorated(*args, **kwargs):
    token = request.args.get('token')
    # http://127.0.0.1:5000/protected?token=sqibsq12egvdyw
    if not token:
      return jsonify({'message' : 'Token is missing'}), 403
      try:
        data = jwt.decode(token, app.config['SECRET_KEY'])
      except:
        return jsonify({'message' : 'Token is invalid!'}), 403
    return f(*args, **kwargs)
  return decorated
...

Flask App

Adding decorator to protected route

...

@app.route('/protected')

def protected():
    return jsonify({'message' : 
    'Only available to people 
               with valid tokens.'})
...

Flask App

Adding decorator to protected route

...

@app.route('/protected')
@token_required
def protected():
    return jsonify({'message' : 
    'Only available to people 
               with valid tokens.'})
...

Flask App : complete

from flask import Flask, jsonify, request, make_response
import jwt
import datetime
from functools import wraps

app = Flask(__name__)

app.config['SECRET_KEY'] = 'indonesia'

# Token Decorator
def token_required(f):
	@wraps(f)
	def decorated(*args, **kwargs):
		token = request.args.get('token')

		if not token:
			return jsonify({'message' : 'Token is missing'}), 403

		try:
			data = jwt.decode(token, app.config['SECRET_KEY'])
		except:
			return jsonify({'message' : 'Token is invalid!'}), 403

		return f(*args, **kwargs)

	return decorated

# Unprotected Route and function
@app.route('/unprotected')
def unprotected():
	return jsonify({'message' : 'Anyone can view this.'})

# Protected Route and function
@app.route('/protected')
@token_required
def protected():
	return jsonify({'message' : 'Only available to people with valid tokens.'})

# Login Route and function
@app.route('/login')
def login():
	auth = request.authorization

	if auth and auth.password == 'pyconid':
		token = jwt.encode({'user': auth.username, 'exp': datetime.datetime.utcnow() + datetime.timedelta(seconds=40)}, app.config['SECRET_KEY'])
		return jsonify({'token' : token})

	return make_response('Could not verify!', 401, {'WWW-Authenticate' : 'Basic realm:"Login Required"'})

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

Flask App

Try it yourself

http://bit.ly/pyconid-jwt-flask-simple

[~]$ python flaskdemo.py

Flask App

Auth0 way

http://bit.ly/pyconid-jwt-flask

http://bit.ly/pyconid-jwt-flask-auth0

http://bit.ly/pyconid-slide

Django App

Dependencies:

  • django
  • djangorestframework
  • djangorestframework-jwt
  • cryptography
  • pyjwt
  • python-dotenv

Django App

$ pip install django
$ pip install djangorestframework
$ pip install djangorestframework_simplejwt

Install:

Django App

$ django-admin startproject apiexample
$ cd apiexample
$ python manage.py startapp pyconid

Start project:

Django App

REST_FRAMEWORK = {
    ...
    'DEFAULT_AUTHENTICATION_CLASSES': (
        ...
        'rest_framework_simplejwt.authentication.JWTAuthentication',
    )
    ...
}

In settings.py, add

Django App

from rest_framework_simplejwt.views import (
    TokenObtainPairView,
    TokenRefreshView,
)

urlpatterns = [
    ...
    url(r'^api/token/$', TokenObtainPairView.as_view(), name='token_obtain_pair'),
    url(r'^api/token/refresh/$', TokenRefreshView.as_view(), name='token_refresh'),
    ...
]

In urls.py, add

Django App

curl \
  -X POST \
  -H "Content-Type: application/json" \
  -d '{"username": "mdsbzalam", "password": "donttellanyone"}' \
  http://localhost:8000/api/token/

...

Test:

{
"access":"eyJh.wiY29.NH7lU", 
"refresh":"eyJh.CIsIm.aERh4"
}

Django App

Auth0 way

http://bit.ly/pyconid-jwt-django

http://bit.ly/pyconid-jwt-django-auth0

http://bit.ly/pyconid-slide

Resources

General JWT Resources

jwt.io 

Overview of JWT Signing Algorithms

http://bit.ly/jwt-alg

JWT Handbook

http://bit.ly/jwt-book

JWT Token

http://bit.ly/pyconid-jwt-token

Connect with me

Facebook

facebook.com/mdsbzalam

Twitter

@mdsbzalam

Instagram

@mdsbzalam

LinkedIn

https://in.linkedin.com/in/mdsbzalam

E-mail

mdsbzalam@gmail.com

Slide

http://bit.ly/pyconid-slide

Thank you

@mdsbzalam

Securing Django and Flask APIs using JWT

By Mohammad Shahbaz Alam

Securing Django and Flask APIs using JWT

Securing Django and Flask APIs using JWT

  • 1,018