diff --git a/app.py b/app.py index fa6044c..0abdfdd 100644 --- a/app.py +++ b/app.py @@ -1,20 +1,28 @@ +import urllib.parse + +import jwt + import requests from authlib.integrations.base_client import OAuthError from authlib.integrations.flask_client import OAuth -from flask import Flask, request, redirect, Response, session, render_template, url_for, make_response +from flask import Flask, request, redirect, session, render_template, url_for, make_response # TODO add client export json in project & git app = Flask(__name__) -# TODO still useful ? -app.config['SECRET_KEY'] = 'onelogindemopytoolkit' +# key used to encore session cookie +app.config['SECRET_KEY'] = 'client-oidc' -issuer="https://id.vilanet.fr/realms/vilanet" -client_id="client-oidc" -client_secret="BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC" +idp_url = "https://id.vilanet.fr/realms/vilanet" + +client_url = "http://localhost:5001" +client_id = "client-oidc" +client_secret = "BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC" + +resource_server_url = 'http://localhost:5002' # To manage OIDC flow for UI client oauth = OAuth(app=app) @@ -22,23 +30,14 @@ oauth.register( name="keycloak", client_id=client_id, client_secret=client_secret, - server_metadata_url=f'{issuer}/.well-known/openid-configuration', - client_kwargs={ - 'scope': 'openid', - 'code_challenge_method': 'S256', - } + server_metadata_url=f'{idp_url}/.well-known/openid-configuration', + client_kwargs={'scope': 'openid', 'code_challenge_method': 'S256'} ) @app.route("/api") def api(): - response = '' - if 'access_token' in request.cookies: - access_token = request.cookies['access_token'] - response = requests.get('http://localhost:5002/api?callbackUrl=http%3A%2F%2Flocalhost%3A5001', headers={'Authorization': f'Bearer {access_token}'}) - else: - response = requests.get('http://localhost:5002/api?callbackUrl=http%3A%2F%2Flocalhost%3A5001') - return Response(response=response.text, status=response.status_code, headers=dict(response.headers)) + return make_response(redirect(resource_server_url + "/api?" + urllib.parse.urlencode({'callbackUrl': client_url}))) @app.route("/auth") @@ -46,30 +45,31 @@ def auth(): try: token_response = oauth.keycloak.authorize_access_token() except OAuthError as e: - return redirect('/?error=access_denied') + return redirect('/?' + urllib.parse.urlencode({'error': e})) response = make_response(redirect('/')) access_token = token_response['access_token'] if access_token: - response.set_cookie('access_token', access_token, httponly=True) + response.set_cookie('access_token', access_token, httponly=True, domain='localhost') refresh_token = token_response['refresh_token'] if refresh_token: - response.set_cookie('refresh_token', refresh_token, httponly=True) + response.set_cookie('refresh_token', refresh_token, httponly=True, domain='localhost') id_token = token_response['id_token'] if id_token: - response.set_cookie('id_token', id_token, httponly=True) + response.set_cookie('id_token', id_token, httponly=True, domain='localhost') if token_response['userinfo']: - session['name'] = token_response['userinfo']['name'] - session['email'] = token_response['userinfo']['email'] + if 'name' in token_response['userinfo']: + session['userinfo_name'] = token_response['userinfo']['name'] + if 'email' in token_response['userinfo']: + session['userinfo_email'] = token_response['userinfo']['email'] return response @app.route("/", methods=['GET', 'POST']) def index(): - attributes = False - paint_logout = False - not_auth_warn = False - user_name = False - user_email = False + auth_error = False + userinfo_name = False + userinfo_email = False + token_data = False if 'sso' in request.args: redirect_uri = url_for('auth', _external=True) return oauth.keycloak.authorize_redirect(redirect_uri) @@ -77,7 +77,7 @@ def index(): if 'refresh_token' in request.cookies: # propagate logout to Keycloak refresh_token = request.cookies['refresh_token'] - requests.post(f'{issuer}/protocol/openid-connect/logout', data={ + requests.post(f'{idp_url}/protocol/openid-connect/logout', data={ "client_id": client_id, "client_secret": client_secret, "refresh_token": refresh_token, @@ -86,33 +86,45 @@ def index(): response.set_cookie('access_token', '', expires=0) response.set_cookie('refresh_token', '', expires=0) response.set_cookie('id_token', '', expires=0) - if 'name' in session: - del session['name'] - if 'email' in session: - del session['email'] + if 'userinfo_name' in session: + del session['userinfo_name'] + if 'userinfo_email' in session: + del session['userinfo_email'] return response elif 'error' in request.args: - not_auth_warn = True + auth_error = request.args['error'] + + if 'userinfo_name' in session: + userinfo_name = session['userinfo_name'] + + if 'userinfo_email' in session: + userinfo_email = session['userinfo_email'] # it is OK to use ID token to display user info on client side # is it not OK to use access token on client side if 'id_token' in request.cookies: - paint_logout = True - attributes = {'id_token': [request.cookies['id_token']]}.items() - - if 'name' in session: - user_name = session['name'] - - if 'email' in session: - user_email = session['email'] + id_token = request.cookies['id_token'] + jwks_uri = f"{idp_url}/protocol/openid-connect/certs" + jwks_client = jwt.PyJWKClient(jwks_uri) + key = jwks_client.get_signing_key_from_jwt(id_token) + try: + token_data = jwt.decode( + id_token, + key.key, + algorithms=["RS256"], + audience=client_id, + options={'verify_signature': False, 'verify_aud': True} + ) + auth_error = False + except Exception as e: + auth_error = e return render_template( 'index.html', - attributes=attributes, - not_auth_warn=not_auth_warn, - paint_logout=paint_logout, - user_name=user_name, - user_email=user_email, + auth_error=auth_error, + userinfo_name=userinfo_name, + userinfo_email=userinfo_email, + token_data=token_data, ) diff --git a/requirements.txt b/requirements.txt index eeb50ed..82f7485 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,3 +1,6 @@ authlib flask -requests \ No newline at end of file +requests +urllib.parse +PyJWT +cryptography diff --git a/templates/api.html b/templates/api.html deleted file mode 100644 index 7813c32..0000000 --- a/templates/api.html +++ /dev/null @@ -1,12 +0,0 @@ -{% extends "base.html" %} - -{% block content %} - -{% if not_auth_warn %} -
The API content
-{% endif %} -Back - -{% endblock %} diff --git a/templates/index.html b/templates/index.html index 658ff4a..86b3272 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,40 +2,32 @@ {% block content %} -{% if not_auth_warn %} -Name | Values | + + + {% for key in token_data %} +
---|---|
{{ key }} | +{{ token_data[key] }} | +
You have the following attributes:
-Name | Values | - - - {% for attr in attributes %} -
---|---|
{{ attr.0 }} | -
|