Skip to content
This repository has been archived by the owner on Mar 8, 2021. It is now read-only.

Commit

Permalink
fix: auth0 verify issue: adds dotenv package -- working auth roles te…
Browse files Browse the repository at this point in the history
…sting
  • Loading branch information
gaurangrshah committed Apr 21, 2020
1 parent bf92607 commit 1a51e02
Show file tree
Hide file tree
Showing 9 changed files with 2,122 additions and 24 deletions.
1 change: 0 additions & 1 deletion api.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,7 +3,6 @@
from models import db_drop_and_create_all, setup_db, db, Actor, Movie
from auth import AuthError, requires_auth


casting_blueprint = Blueprint('gsprod-api', __name__)


Expand Down
2 changes: 1 addition & 1 deletion app.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@
)


def create_app(test_config=None): # 🚧
def create_app(test_config=None):
app = Flask(__name__)
app.register_blueprint(casting_blueprint, url_prefix='/api')
app.register_error_handler(422, unprocessable)
Expand Down
44 changes: 26 additions & 18 deletions auth.py
Original file line number Diff line number Diff line change
Expand Up @@ -4,13 +4,20 @@
from jose import jwt
from functools import wraps
from urllib.request import urlopen
from dotenv import load_dotenv
# basedir = os.path.abspath(os.path.dirname(__file__))

basedir = os.path.abspath(os.path.dirname(__file__))
load_dotenv()

AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN')
ALGORITHMS = os.getenv('AUTH0_ALGORITHMS')
API_AUDIENCE = os.getenv('AUTH0_AUDIENCE')

AUTH0_DOMAIN = 'gs-prod.auth0.com'
AUTH0_ALGORITHMS = ['RS256']
AUTH0_AUDIENCE = 'casting'
# AUTH0_DOMAIN = os.environ['AUTH0_DOMAIN']
# AUTH0_ALGORITHMS = os.environ['AUTH0_ALGORITHMS']
# AUTH0_AUDIENCE = os.environ['AUTH0_AUDIENCE']

print('❌ os.environ', os.getenv('AUTH0_AUDIENCE'))

# AuthError Exception
'''
Expand Down Expand Up @@ -41,7 +48,7 @@ def get_token_auth_header():
"""Grab Access Token from the Authorization Header in request"""
auth = request.headers.get('Authorization', None)
if auth:
print('🚩request.headers:', request.headers)
print('🚩auth exists in headers:')
if not auth: # ensures auth has a truthy value
print('authorization_header_missing')
raise AuthError({
Expand Down Expand Up @@ -74,7 +81,7 @@ def get_token_auth_header():

token = parts[1] # return only the token from parts
if token:
print('✅ found token in header:', token)
print('✅ found token in header:')
return token


Expand All @@ -95,13 +102,14 @@ def get_token_auth_header():

def verify_decode_jwt(token):
"""Uses the Auth0 secret to decode then verify the provided token"""
print('verifier')
jsonurl = urlopen(f'https://{AUTH0_DOMAIN}/.well-known/jwks.json')
print('jsonurl', jsonurl)
# auth0url = 'https://{}/.well-known/jwks.json'.format(AUTH0_DOMAIN)
# print('verifying...')
jsonurl = urlopen("https://{}/.well-known/jwks.json".format(AUTH0_DOMAIN))
# print('jsonurl', jsonurl)
jwks = json.loads(jsonurl.read())
print('jwks', jwks)
# print('jwks', jwks)
unverified_header = jwt.get_unverified_header(token)
print('unverified_header', unverified_header)
# print('unverified_header', unverified_header)
rsa_key = {}
if 'kid' not in unverified_header:
print("'kid' not in unverified_header")
Expand All @@ -121,15 +129,15 @@ def verify_decode_jwt(token):
}
if rsa_key:
try:
print('checking rsa key', rsa_key)
# print('checking rsa key', rsa_key)
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
algorithms=AUTH0_ALGORITHMS,
audience=AUTH0_AUDIENCE,
issuer='https://' + AUTH0_DOMAIN + '/'
)
print('✅ rsa key found', payload)
# print('✅ rsa key found', payload)
return payload

except jwt.ExpiredSignatureError:
Expand Down Expand Up @@ -208,11 +216,11 @@ def requires_auth_decorator(f): # wraps auth decorator
@wraps(f)
def wrapper(*args, **kwargs):
# validate token
print('🚧 validating token in header')
# print('🚧 validating token in header')
token = get_token_auth_header()
print('verifying header payload')
# print('verifying header payload')
payload = verify_decode_jwt(token)
print('payload', payload)
# print('payload', payload)
# check permissions
print('🧐 checking permission for', permission)
check_permissions(permission, payload)
Expand Down
129 changes: 129 additions & 0 deletions auth2.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,129 @@
import os
import json
import ssl

from flask import request, _request_ctx_stack, abort
from jose import jwt
from functools import wraps
from urllib.request import urlopen

AUTH0_DOMAIN = os.getenv('AUTH0_DOMAIN')
ALGORITHMS = os.getenv('AUTH0_ALGORITHMS')
API_AUDIENCE = os.getenv('AUTH0_AUDIENCE')

class AuthError(Exception):
def __init__(self, error, status_code):
self.error = error
self.status_code = status_code


# Auth Header
def get_token_auth_header():
"""Obtains the Access Token from the Authorization Header"""
auth = request.headers.get('Authorization', None)
if not auth:
raise AuthError({
'code': 'authorization_header_missing',
'description': 'Authorization header is expected.'
}, 401)
parts = auth.split()
if parts[0].lower() != 'bearer':
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must start with "Bearer".'
}, 401)
elif len(parts) == 1:
raise AuthError({
'code': 'invalid_header',
'description': 'Token not found.'
}, 401)
elif len(parts) > 2:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization header must be bearer token.'
}, 401)
token = parts[1]
return token


def verify_decode_jwt(token):
jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
jwks = json.loads(jsonurl.read())
unverified_header = jwt.get_unverified_header(token)
rsa_key = {}
if 'kid' not in unverified_header:
raise AuthError({
'code': 'invalid_header',
'description': 'Authorization malformed.'
}, 401)

for key in jwks['keys']:
if key['kid'] == unverified_header['kid']:
rsa_key = {
'kty': key['kty'],
'kid': key['kid'],
'use': key['use'],
'n': key['n'],
'e': key['e']
}

if rsa_key:
try:
payload = jwt.decode(
token,
rsa_key,
algorithms=ALGORITHMS,
audience=API_AUDIENCE,
issuer='https://' + AUTH0_DOMAIN + '/'
)
return payload
except jwt.ExpiredSignatureError:
raise AuthError({
'code': 'token_expired',
'description': 'Token expired.'
}, 401)
except jwt.JWTClaimsError:
raise AuthError({
'code': 'invalid_claims',
'description': 'Incorrect claims. check the\
audience and issuer.'
}, 401)
except Exception:
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to parse authentication token.'
}, 400)
raise AuthError({
'code': 'invalid_header',
'description': 'Unable to find the appropriate key.'
}, 400)


def check_permissions(permission, payload):
if 'permissions' not in payload:
raise AuthError({
'code': 'invalid_claims',
'description': 'Permissions not included in JWT.'
}, 400)
if permission not in payload['permissions']:
raise AuthError({
'code': 'unauthorized',
'description': 'Permission not found.'
}, 401)
return True


def requires_auth(permission=''):
def requires_auth_decorator(f):
@wraps(f)
def wrapper(*args, **kwargs):
token = get_token_auth_header()
try:
payload = verify_decode_jwt(token)
except BaseException:
print("could not verify_decode_jwt")
abort(401)
check_permissions(permission, payload)
return f(payload, *args, **kwargs)
return wrapper
return requires_auth_decorator
3 changes: 3 additions & 0 deletions models.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,9 @@
import dateutil.parser
from flask_migrate import Migrate

basedir = os.path.abspath(os.path.dirname(__file__))


DEBUG = True
SECRET_KEY = os.urandom(32)
SQLALCHEMY_TRACK_MODIFICATIONS = False
Expand Down
5 changes: 5 additions & 0 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,5 +1,7 @@
alembic==1.4.2
autopep8==1.4.4
certifi==2020.4.5.1
chardet==3.0.4
Click==7.0
ecdsa==0.13.2
Flask==1.0.2
Expand All @@ -11,6 +13,7 @@ Flask-SQLAlchemy==2.4.1
Flask-WTF==0.14.3
future==0.17.1
gunicorn==20.0.4
idna==2.9
itsdangerous==1.1.0
Jinja2==2.10.1
Mako==1.1.2
Expand All @@ -24,4 +27,6 @@ python-editor==1.0.4
python-jose-cryptodome==1.3.2
six==1.12.0
SQLAlchemy==1.3.13
urllib3==1.25.9
Werkzeug==0.15.2
WTForms==2.2.1
Loading

0 comments on commit 1a51e02

Please sign in to comment.