Skip to content

Commit 92f3083

Browse files
authored
Merge pull request #11 from Annyv2/oidc
Update sample
2 parents 94f468b + d9d2f27 commit 92f3083

2 files changed

Lines changed: 96 additions & 56 deletions

File tree

00-Starter-Seed/README.md

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,4 +20,4 @@ Once you've set those 2 enviroment variables:
2020
2. Start the server with `python server.py`
2121
3. Try calling [http://localhost:3001/ping](http://localhost:3001/ping)
2222

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
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.

00-Starter-Seed/server.py

Lines changed: 95 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -1,106 +1,146 @@
1+
"""Python Flask API Auth0 integration example
2+
"""
3+
4+
from functools import wraps
15
import json
26
from os import environ as env, path
37
import urllib
48

59
from dotenv import load_dotenv
6-
from functools import wraps
710
from flask import Flask, request, jsonify, _app_ctx_stack
811
from flask_cors import cross_origin
912
from jose import jwt
1013

11-
load_dotenv(path.join(path.dirname(__file__), '.env'))
12-
auth0_domain = env['AUTH0_DOMAIN']
13-
api_audience = env['API_ID']
14+
load_dotenv(path.join(path.dirname(__file__), ".env"))
15+
AUTH0_DOMAIN = env["AUTH0_DOMAIN"]
16+
API_AUDIENCE = env["API_ID"]
1417

15-
app = Flask(__name__)
18+
APP = Flask(__name__)
1619

1720

1821
# Format error response and append status code.
1922
def handle_error(error, status_code):
23+
"""Handles the errors
24+
"""
2025
resp = jsonify(error)
2126
resp.status_code = status_code
2227
return resp
2328

29+
def get_token_auth_header():
30+
"""Obtains the access token from the Authorization Header
31+
"""
32+
auth = request.headers.get("Authorization", None)
33+
if not auth:
34+
return handle_error({"code": "authorization_header_missing",
35+
"description":
36+
"Authorization header is expected"}, 401)
37+
38+
parts = auth.split()
39+
40+
if parts[0].lower() != "bearer":
41+
return handle_error({"code": "invalid_header",
42+
"description":
43+
"Authorization header must start with"
44+
"Bearer"}, 401)
45+
elif len(parts) == 1:
46+
return handle_error({"code": "invalid_header",
47+
"description": "Token not found"}, 401)
48+
elif len(parts) > 2:
49+
return handle_error({"code": "invalid_header",
50+
"description": "Authorization header must be"
51+
"Bearer token"}, 401)
52+
53+
token = parts[1]
54+
return token
55+
56+
def requires_scope(required_scope):
57+
"""Determines if the required scope is present in the access token
58+
Args:
59+
required_scope (str): The scope required to access the resource
60+
"""
61+
token = get_token_auth_header()
62+
unverified_claims = jwt.get_unverified_claims(token)
63+
token_scopes = unverified_claims["scope"].split()
64+
for token_scope in token_scopes:
65+
if token_scope == required_scope:
66+
return True
67+
return False
2468

2569
def requires_auth(f):
70+
"""Determines if the access token is valid
71+
"""
2672
@wraps(f)
2773
def decorated(*args, **kwargs):
28-
auth = request.headers.get('Authorization', None)
29-
if not auth:
30-
return handle_error({'code': 'authorization_header_missing',
31-
'description':
32-
'Authorization header is expected'}, 401)
33-
34-
parts = auth.split()
35-
36-
if parts[0].lower() != 'bearer':
37-
return handle_error({'code': 'invalid_header',
38-
'description':
39-
'Authorization header must start with'
40-
'Bearer'}, 401)
41-
elif len(parts) == 1:
42-
return handle_error({'code': 'invalid_header',
43-
'description': 'Token not found'}, 401)
44-
elif len(parts) > 2:
45-
return handle_error({'code': 'invalid_header',
46-
'description': 'Authorization header must be'
47-
'Bearer + \s + token'}, 401)
48-
49-
token = parts[1]
50-
jsonurl = urllib.urlopen('https://'+auth0_domain+'/.well-known/jwks.json')
74+
token = get_token_auth_header()
75+
jsonurl = urllib.urlopen("https://"+AUTH0_DOMAIN+"/.well-known/jwks.json")
5176
jwks = json.loads(jsonurl.read())
5277
unverified_header = jwt.get_unverified_header(token)
5378
rsa_key = {}
54-
for key in jwks['keys']:
55-
if key['kid'] == unverified_header['kid']:
79+
for key in jwks["keys"]:
80+
if key["kid"] == unverified_header["kid"]:
5681
rsa_key = {
57-
'kty': key['kty'],
58-
'kid': key['kid'],
59-
'use': key['use'],
60-
'n': key['n'],
61-
'e': key['e']
82+
"kty": key["kty"],
83+
"kid": key["kid"],
84+
"use": key["use"],
85+
"n": key["n"],
86+
"e": key["e"]
6287
}
6388
if rsa_key:
6489
try:
6590
payload = jwt.decode(
6691
token,
6792
rsa_key,
68-
algorithms=unverified_header['alg'],
69-
audience=api_audience,
70-
issuer='https://'+auth0_domain+'/'
93+
algorithms=unverified_header["alg"],
94+
audience=API_AUDIENCE,
95+
issuer="https://"+AUTH0_DOMAIN+"/"
7196
)
7297
except jwt.ExpiredSignatureError:
73-
return handle_error({'code': 'token_expired',
74-
'description': 'token is expired'}, 401)
98+
return handle_error({"code": "token_expired",
99+
"description": "token is expired"}, 401)
75100
except jwt.JWTClaimsError:
76-
return handle_error({'code': 'invalid_claims',
77-
'description': 'incorrect claims, please check the audience and issuer'}, 401)
101+
return handle_error({"code": "invalid_claims",
102+
"description": "incorrect claims,"
103+
"please check the audience and issuer"}, 401)
78104
except Exception:
79-
return handle_error({'code': 'invalid_header',
80-
'description': 'Unable to parse authentication'
81-
' token.'}, 400)
105+
return handle_error({"code": "invalid_header",
106+
"description": "Unable to parse authentication"
107+
"token."}, 400)
82108

83109
_app_ctx_stack.top.current_user = payload
84110
return f(*args, **kwargs)
85-
return handle_error({'code': 'invalid_header',
86-
'description': 'Unable to find appropriate key'}, 400)
111+
return handle_error({"code": "invalid_header",
112+
"description": "Unable to find appropriate key"}, 400)
87113
return decorated
88114

89-
90115
# Controllers API
91-
@app.route("/ping")
92-
@cross_origin(headers=['Content-Type', 'Authorization'])
116+
@APP.route("/ping")
117+
@cross_origin(headers=["Content-Type", "Authorization"])
93118
def ping():
119+
"""No access token required to access this route
120+
"""
94121
return "All good. You don't need to be authenticated to call this"
95122

96123

97-
@app.route("/secured/ping")
98-
@cross_origin(headers=['Content-Type', 'Authorization'])
99-
@cross_origin(headers=['Access-Control-Allow-Origin', '*'])
124+
@APP.route("/secured/ping")
125+
@cross_origin(headers=["Content-Type", "Authorization"])
126+
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])
100127
@requires_auth
101-
def securedPing():
128+
def secured_ping():
129+
"""A valid access token is required to access this route
130+
"""
102131
return "All good. You only get this message if you're authenticated"
103132

133+
@APP.route("/secured/private/ping")
134+
@cross_origin(headers=["Content-Type", "Authorization"])
135+
@cross_origin(headers=["Access-Control-Allow-Origin", "*"])
136+
@requires_auth
137+
def secured_private_ping():
138+
"""A valid access token and an appropriate scope are required to access this route
139+
"""
140+
if requires_scope("read:agenda"):
141+
return "All good. You're authenticated and the access token has the appropriate scope"
142+
return "You don't have access to this resource"
143+
104144

105145
if __name__ == "__main__":
106-
app.run(host='0.0.0.0', port=env.get('PORT', 3001))
146+
APP.run(host="0.0.0.0", port=env.get("PORT", 3001))

0 commit comments

Comments
 (0)