|
| 1 | +import jwt |
| 2 | +import base64 |
| 3 | +import os |
| 4 | + |
| 5 | +from functools import wraps |
| 6 | +from flask import Flask, request, jsonify, _request_ctx_stack |
| 7 | +from werkzeug.local import LocalProxy |
| 8 | +from dotenv import Dotenv |
| 9 | +from flask.ext.cors import cross_origin |
| 10 | + |
| 11 | +env = None |
| 12 | + |
| 13 | +try: |
| 14 | + env = Dotenv('./.env') |
| 15 | + client_id = env["AUTH0_CLIENT_ID"] |
| 16 | + client_secret = env["AUTH0_CLIENT_SECRET"] |
| 17 | +except IOError: |
| 18 | + env = os.environ |
| 19 | + |
| 20 | +app = Flask(__name__) |
| 21 | + |
| 22 | +# Authentication annotation |
| 23 | +current_user = LocalProxy(lambda: _request_ctx_stack.top.current_user) |
| 24 | + |
| 25 | +# Authentication attribute/annotation |
| 26 | +def authenticate(error): |
| 27 | + resp = jsonify(error) |
| 28 | + resp.status_code = 401 |
| 29 | + return resp |
| 30 | + |
| 31 | +def requires_auth(f): |
| 32 | + @wraps(f) |
| 33 | + def decorated(*args, **kwargs): |
| 34 | + auth = request.headers.get('Authorization', None) |
| 35 | + if not auth: |
| 36 | + return authenticate({'code': 'authorization_header_missing', 'description': 'Authorization header is expected'}) |
| 37 | + |
| 38 | + parts = auth.split() |
| 39 | + |
| 40 | + if parts[0].lower() != 'bearer': |
| 41 | + return {'code': 'invalid_header', 'description': 'Authorization header must start with Bearer'} |
| 42 | + elif len(parts) == 1: |
| 43 | + return {'code': 'invalid_header', 'description': 'Token not found'} |
| 44 | + elif len(parts) > 2: |
| 45 | + return {'code': 'invalid_header', 'description': 'Authorization header must be Bearer + \s + token'} |
| 46 | + |
| 47 | + token = parts[1] |
| 48 | + try: |
| 49 | + payload = jwt.decode( |
| 50 | + token, |
| 51 | + base64.b64decode(client_secret.replace("_","/").replace("-","+")), |
| 52 | + audience=client_id |
| 53 | + ) |
| 54 | + except jwt.ExpiredSignature: |
| 55 | + return authenticate({'code': 'token_expired', 'description': 'token is expired'}) |
| 56 | + except jwt.InvalidAudienceError: |
| 57 | + return authenticate({'code': 'invalid_audience', 'description': 'incorrect audience, expected: ' + client_id}) |
| 58 | + except jwt.DecodeError: |
| 59 | + return authenticate({'code': 'token_invalid_signature', 'description': 'token signature is invalid'}) |
| 60 | + |
| 61 | + _request_ctx_stack.top.current_user = user = payload |
| 62 | + return f(*args, **kwargs) |
| 63 | + |
| 64 | + return decorated |
| 65 | + |
| 66 | +# Controllers API |
| 67 | +@app.route("/ping") |
| 68 | +@cross_origin(headers=['Content-Type', 'Authorization']) |
| 69 | +def ping(): |
| 70 | + return "All good. You don't need to be authenticated to call this" |
| 71 | + |
| 72 | +@app.route("/secured/ping") |
| 73 | +@cross_origin(headers=['Content-Type', 'Authorization']) |
| 74 | +@requires_auth |
| 75 | +def securedPing(): |
| 76 | + return "All good. You only get this message if you're authenticated" |
| 77 | + |
| 78 | +if __name__ == "__main__": |
| 79 | + app.run(host='0.0.0.0', port = int(os.environ.get('PORT', 3001))) |
0 commit comments