Skip to content

Commit 79a918b

Browse files
committed
Update Flask API sample
1 parent f9bea71 commit 79a918b

4 files changed

Lines changed: 76 additions & 29 deletions

File tree

00-Starter-Seed/.gitignore

Lines changed: 3 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1 +1,3 @@
1-
.env
1+
.env
2+
*.iml
3+
.idea

00-Starter-Seed/README.md

Lines changed: 36 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,12 +1,17 @@
11
# Auth0 + Python + Flask API Seed
2-
This is the seed project you need to use if you're going to create a Python + Flask API. If you just want to create a Regular Python WebApp, please check [this other seed project](https://github.com/auth0-samples/auth0-python-web-app/tree/master/00-Starter-Seed)
2+
This is the seed project you need to use if you're going to create a Python + Flask API.
3+
If you just want to create a Regular Python WebApp, please
4+
check [this other seed project](https://github.com/auth0-samples/auth0-python-web-app/tree/master/01-Login)
35

46
# Running the example
57
In order to run the example you need to have `python` and `pip` installed.
68

7-
You also need to set your Auth0 Domain and the API's audience as environment variables with the following names respectively: `AUTH0_DOMAIN` and `API_ID`, which is the audience of your API. You can find an example in the `env.example` file.
9+
You also need to set your Auth0 Domain and the API's audience as environment variables with the following names
10+
respectively: `AUTH0_DOMAIN` and `API_ID`, which is the audience of your API. You can find an example in the
11+
`env.example` file.
812

9-
For that, if you just create a file named `.env` in the directory and set the values like the following, the app will just work:
13+
For that, if you just create a file named `.env` in the directory and set the values like the following,
14+
the app will just work:
1015

1116
```bash
1217
# .env file
@@ -20,4 +25,31 @@ Once you've set those 2 enviroment variables:
2025
2. Start the server with `python server.py`
2126
3. Try calling [http://localhost:3001/ping](http://localhost:3001/ping)
2227

23-
You can then try to do a GET to [http://localhost:3001/secured/ping](http://localhost:3001/secured/ping) which will throw an error if you don't send an access token signed with RS256 with the appropriate issuer and audience in the Authorization header. You can also try to do a GET to [http://localhost:3001/secured/private/ping](http://localhost:3001/secured/private/ping) which will throw an error if you don't send an access token with the scope `read:agenda` signed with RS256 with the appropriate issuer and audience in the Authorization header.
28+
# Running the example with Docker
29+
30+
In order to run the example you need to have `docker` installed.
31+
32+
You also need to set your Auth0 Domain and the API's audience as environment variables with the following names
33+
respectively: `AUTH0_DOMAIN` and `API_ID`, which is the audience of your API. You can find an example in the
34+
`env.example` file.
35+
36+
For that, if you just create a file named `.env` in the directory and set the values like the following,
37+
the app will just work:
38+
39+
```bash
40+
# .env file
41+
AUTH0_DOMAIN=example.auth0.com
42+
API_ID=YOUR_API_AUDIENCE
43+
```
44+
45+
Once you've set those 2 enviroment variables:
46+
47+
1. Execute in command line `sh exec.sh` to run the Docker in Linux, or `.\exec.ps1` to run the Docker in Windows.
48+
2. Try calling [http://localhost:3001/ping](http://localhost:3001/ping)
49+
50+
You can then try to do a GET to [http://localhost:3001/secured/ping](http://localhost:3001/secured/ping) which will
51+
throw an error if you don't send an access token signed with RS256 with the appropriate issuer and audience in the
52+
Authorization header. You can also try to do a GET to
53+
[http://localhost:3001/secured/private/ping](http://localhost:3001/secured/private/ping) which will throw an error if
54+
you don't send an access token with the scope `read:agenda` signed with RS256 with the appropriate issuer and audience
55+
in the Authorization header.

00-Starter-Seed/requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,3 +2,4 @@ flask
22
python-dotenv
33
python-jose
44
flask-cors
5+
six

00-Starter-Seed/server.py

Lines changed: 36 additions & 24 deletions
Original file line numberDiff line numberDiff line change
@@ -3,56 +3,65 @@
33

44
from functools import wraps
55
import json
6-
from os import environ as env, path
7-
import urllib
6+
from os import environ as env
7+
from six.moves.urllib.request import urlopen
88

9-
from dotenv import load_dotenv
9+
from dotenv import load_dotenv, find_dotenv
1010
from flask import Flask, request, jsonify, _app_ctx_stack
1111
from flask_cors import cross_origin
1212
from jose import jwt
1313

14-
load_dotenv(path.join(path.dirname(__file__), ".env"))
15-
AUTH0_DOMAIN = env["AUTH0_DOMAIN"]
16-
API_AUDIENCE = env["API_ID"]
14+
ENV_FILE = find_dotenv()
15+
if ENV_FILE:
16+
load_dotenv(ENV_FILE)
17+
AUTH0_DOMAIN = env.get("AUTH0_DOMAIN")
18+
API_AUDIENCE = env.get("API_ID")
1719
ALGORITHMS = ["RS256"]
1820
APP = Flask(__name__)
1921

2022

2123
# Format error response and append status code.
22-
def handle_error(error, status_code):
23-
"""Handles the errors
24-
"""
25-
resp = jsonify(error)
26-
resp.status_code = status_code
27-
return resp
24+
class AuthError(Exception):
25+
def __init__(self, error, status_code):
26+
self.error = error
27+
self.status_code = status_code
28+
29+
30+
@APP.errorhandler(AuthError)
31+
def handle_auth_error(ex):
32+
response = jsonify(ex.error)
33+
response.status_code = ex.status_code
34+
return response
35+
2836

2937
def get_token_auth_header():
3038
"""Obtains the access token from the Authorization Header
3139
"""
3240
auth = request.headers.get("Authorization", None)
3341
if not auth:
34-
return handle_error({"code": "authorization_header_missing",
42+
raise AuthError({"code": "authorization_header_missing",
3543
"description":
3644
"Authorization header is expected"}, 401)
3745

3846
parts = auth.split()
3947

4048
if parts[0].lower() != "bearer":
41-
return handle_error({"code": "invalid_header",
49+
raise AuthError({"code": "invalid_header",
4250
"description":
4351
"Authorization header must start with"
4452
"Bearer"}, 401)
4553
elif len(parts) == 1:
46-
return handle_error({"code": "invalid_header",
54+
raise AuthError({"code": "invalid_header",
4755
"description": "Token not found"}, 401)
4856
elif len(parts) > 2:
49-
return handle_error({"code": "invalid_header",
57+
raise AuthError({"code": "invalid_header",
5058
"description": "Authorization header must be"
51-
"Bearer token"}, 401)
59+
" Bearer token"}, 401)
5260

5361
token = parts[1]
5462
return token
5563

64+
5665
def requires_scope(required_scope):
5766
"""Determines if the required scope is present in the access token
5867
Args:
@@ -66,22 +75,23 @@ def requires_scope(required_scope):
6675
return True
6776
return False
6877

78+
6979
def requires_auth(f):
7080
"""Determines if the access token is valid
7181
"""
7282
@wraps(f)
7383
def decorated(*args, **kwargs):
7484
token = get_token_auth_header()
75-
jsonurl = urllib.urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
85+
jsonurl = urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
7686
jwks = json.loads(jsonurl.read())
7787
try:
7888
unverified_header = jwt.get_unverified_header(token)
7989
except jwt.JWTError:
80-
return handle_error({"code": "invalid_header",
90+
raise AuthError({"code": "invalid_header",
8191
"description": "Invalid header. "
8292
"Use an RS256 signed JWT Access Token"}, 401)
8393
if unverified_header["alg"] == "HS256":
84-
return handle_error({"code": "invalid_header",
94+
raise AuthError({"code": "invalid_header",
8595
"description": "Invalid header. "
8696
"Use an RS256 signed JWT Access Token"}, 401)
8797
rsa_key = {}
@@ -104,23 +114,24 @@ def decorated(*args, **kwargs):
104114
issuer="https://"+AUTH0_DOMAIN+"/"
105115
)
106116
except jwt.ExpiredSignatureError:
107-
return handle_error({"code": "token_expired",
117+
raise AuthError({"code": "token_expired",
108118
"description": "token is expired"}, 401)
109119
except jwt.JWTClaimsError:
110-
return handle_error({"code": "invalid_claims",
120+
raise AuthError({"code": "invalid_claims",
111121
"description": "incorrect claims,"
112122
" please check the audience and issuer"}, 401)
113123
except Exception:
114-
return handle_error({"code": "invalid_header",
124+
raise AuthError({"code": "invalid_header",
115125
"description": "Unable to parse authentication"
116126
" token."}, 400)
117127

118128
_app_ctx_stack.top.current_user = payload
119129
return f(*args, **kwargs)
120-
return handle_error({"code": "invalid_header",
130+
raise AuthError({"code": "invalid_header",
121131
"description": "Unable to find appropriate key"}, 400)
122132
return decorated
123133

134+
124135
# Controllers API
125136
@APP.route("/ping")
126137
@cross_origin(headers=["Content-Type", "Authorization"])
@@ -139,6 +150,7 @@ def secured_ping():
139150
"""
140151
return "All good. You only get this message if you're authenticated"
141152

153+
142154
@APP.route("/secured/private/ping")
143155
@cross_origin(headers=["Content-Type", "Authorization"])
144156
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])

0 commit comments

Comments
 (0)