diff --git a/app.py b/app.py index b87c86c..8d4266d 100644 --- a/app.py +++ b/app.py @@ -1,83 +1,122 @@ import requests +from authlib.integrations.base_client import OAuthError from authlib.integrations.flask_client import OAuth -from flask import Flask, request, redirect, session, render_template, url_for + +from flask import Flask, request, redirect, session, render_template, url_for, make_response app = Flask(__name__) app.config['SECRET_KEY'] = 'onelogindemopytoolkit' issuer="https://id.vilanet.fr/realms/vilanet" -clientId="client-oidc" -clientSecret="BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC" +client_id="client-oidc" +client_secret="BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC" +# To manage OIDC flow for UI client oauth = OAuth(app=app) oauth.register( name="keycloak", - client_id=clientId, - client_secret=clientSecret, + client_id=client_id, + client_secret=client_secret, server_metadata_url=f'{issuer}/.well-known/openid-configuration', client_kwargs={ - 'scope': 'openid email profile', + 'scope': 'openid', 'code_challenge_method': 'S256', } ) -@app.route("/userinfo") -def userinfo(): - if not 'tokenResponse' in session: - return "Unauthorized", 401 - tokenResponse = session['tokenResponse'] - access_token = tokenResponse['access_token'] - userInfoEndpoint = f'{issuer}/protocol/openid-connect/userinfo' - userInfoResponse = requests.post(userInfoEndpoint, - headers={'Authorization': f'Bearer {access_token}', 'Accept': 'application/json'}) - return userInfoResponse.text, 200 +@app.route("/api") +def api(): + not_auth_warn = True + # is it OK to use access token to check API authorization on server side + # it is not OK to use ID token to check API authorization on server side + if 'accessToken' in request.args: + access_token = request.args['accessToken'] + #claims = oidcclient.validate_jwt(access_token) + not_auth_warn = False + # TODO verify token and check role + return render_template( + 'api.html', + not_auth_warn=not_auth_warn, + ) @app.route("/auth") def auth(): - tokenResponse = oauth.keycloak.authorize_access_token() - idToken = oauth.keycloak.parse_id_token(tokenResponse, None) - if idToken: - #session['tokenResponse'] = tokenResponse - session['user'] = idToken - return redirect('/') + try: + token_response = oauth.keycloak.authorize_access_token() + except OAuthError as e: + return redirect('/?error=access_denied') + response = make_response(redirect('/')) + access_token = token_response['access_token'] + if access_token: + response.set_cookie('accessToken', access_token, httponly=True) + refresh_token = token_response['refresh_token'] + if refresh_token: + response.set_cookie('refreshToken', refresh_token, httponly=True) + id_token = token_response['id_token'] + if id_token: + response.set_cookie('idToken', id_token, httponly=True) + if token_response['userinfo']: + session['name'] = token_response['userinfo']['name'] + session['email'] = token_response['userinfo']['email'] + return response @app.route("/", methods=['GET', 'POST']) def index(): attributes = False + access_token = False paint_logout = False + not_auth_warn = False + user_name = False + user_email = False if 'sso' in request.args: redirect_uri = url_for('auth', _external=True) return oauth.keycloak.authorize_redirect(redirect_uri) elif 'slo' in request.args: - tokenResponse = session.get('tokenResponse') - if tokenResponse is not None: + if 'refresh_token' in request.cookies: # propagate logout to Keycloak - refreshToken = tokenResponse['refresh_token'] - endSessionEndpoint = f'{issuer}/protocol/openid-connect/logout' - requests.post(endSessionEndpoint, data={ - "client_id": clientId, - "client_secret": clientSecret, - "refresh_token": refreshToken, + refresh_token = request.cookies['refresh_token'] + requests.post(f'{issuer}/protocol/openid-connect/logout', data={ + "client_id": client_id, + "client_secret": client_secret, + "refresh_token": refresh_token, }) - session.pop('user', None) - #session.pop('tokenResponse', None) - return redirect('/') + response = make_response(redirect('/')) + response.set_cookie('accessToken', '', expires=0) + response.set_cookie('refreshToken', '', expires=0) + response.set_cookie('idToken', '', expires=0) + del session['name'] + del session['email'] + return response + elif 'error' in request.args: + not_auth_warn = True - if 'user' in session: + # 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 'idToken' in request.cookies: paint_logout = True - strDict = {} - for k in session['user']: - strDict[k] = [ str(session['user'][k]) ] - attributes = strDict.items() + attributes = {'idToken': [request.cookies['idToken']]}.items() + + if 'name' in session: + user_name = session['name'] + + if 'email' in session: + user_email = session['email'] + + if 'accessToken' in request.cookies: + access_token = request.cookies['accessToken'] return render_template( 'index.html', attributes=attributes, - paint_logout=paint_logout + access_token=access_token, + not_auth_warn=not_auth_warn, + paint_logout=paint_logout, + user_name=user_name, + user_email=user_email, ) diff --git a/requirements.txt b/requirements.txt index adad3fd..f315456 100644 --- a/requirements.txt +++ b/requirements.txt @@ -1,2 +1,2 @@ authlib -flask \ No newline at end of file +flask diff --git a/templates/api.html b/templates/api.html new file mode 100644 index 0000000..7813c32 --- /dev/null +++ b/templates/api.html @@ -0,0 +1,12 @@ +{% 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 7341bd3..b5548ab 100644 --- a/templates/index.html +++ b/templates/index.html @@ -2,6 +2,14 @@ {% block content %} +{% if not_auth_warn %} +You have the following attributes:
@@ -27,6 +35,11 @@ {% else %} Login {% endif %} -Userinfo + +{% if access_token %} + Call protected API +{% else %} + Call protected API +{% endif %} {% endblock %} \ No newline at end of file