full version (only miss access token validation on API)

This commit is contained in:
kriss 2024-06-22 23:47:30 +02:00
parent 92e6e65022
commit e2db6cf56b
4 changed files with 105 additions and 41 deletions

117
app.py
View File

@ -1,83 +1,122 @@
import requests import requests
from authlib.integrations.base_client import OAuthError
from authlib.integrations.flask_client import OAuth 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 = Flask(__name__)
app.config['SECRET_KEY'] = 'onelogindemopytoolkit' app.config['SECRET_KEY'] = 'onelogindemopytoolkit'
issuer="https://id.vilanet.fr/realms/vilanet" issuer="https://id.vilanet.fr/realms/vilanet"
clientId="client-oidc" client_id="client-oidc"
clientSecret="BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC" client_secret="BqWWnuj5JkgZZWEaXuR8bprEx53lqGxC"
# To manage OIDC flow for UI client
oauth = OAuth(app=app) oauth = OAuth(app=app)
oauth.register( oauth.register(
name="keycloak", name="keycloak",
client_id=clientId, client_id=client_id,
client_secret=clientSecret, client_secret=client_secret,
server_metadata_url=f'{issuer}/.well-known/openid-configuration', server_metadata_url=f'{issuer}/.well-known/openid-configuration',
client_kwargs={ client_kwargs={
'scope': 'openid email profile', 'scope': 'openid',
'code_challenge_method': 'S256', 'code_challenge_method': 'S256',
} }
) )
@app.route("/userinfo") @app.route("/api")
def userinfo(): def api():
if not 'tokenResponse' in session: not_auth_warn = True
return "Unauthorized", 401 # is it OK to use access token to check API authorization on server side
tokenResponse = session['tokenResponse'] # it is not OK to use ID token to check API authorization on server side
access_token = tokenResponse['access_token'] if 'accessToken' in request.args:
userInfoEndpoint = f'{issuer}/protocol/openid-connect/userinfo' access_token = request.args['accessToken']
userInfoResponse = requests.post(userInfoEndpoint, #claims = oidcclient.validate_jwt(access_token)
headers={'Authorization': f'Bearer {access_token}', 'Accept': 'application/json'}) not_auth_warn = False
return userInfoResponse.text, 200 # TODO verify token and check role
return render_template(
'api.html',
not_auth_warn=not_auth_warn,
)
@app.route("/auth") @app.route("/auth")
def auth(): def auth():
tokenResponse = oauth.keycloak.authorize_access_token() try:
idToken = oauth.keycloak.parse_id_token(tokenResponse, None) token_response = oauth.keycloak.authorize_access_token()
if idToken: except OAuthError as e:
#session['tokenResponse'] = tokenResponse return redirect('/?error=access_denied')
session['user'] = idToken response = make_response(redirect('/'))
return 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']) @app.route("/", methods=['GET', 'POST'])
def index(): def index():
attributes = False attributes = False
access_token = False
paint_logout = False paint_logout = False
not_auth_warn = False
user_name = False
user_email = False
if 'sso' in request.args: if 'sso' in request.args:
redirect_uri = url_for('auth', _external=True) redirect_uri = url_for('auth', _external=True)
return oauth.keycloak.authorize_redirect(redirect_uri) return oauth.keycloak.authorize_redirect(redirect_uri)
elif 'slo' in request.args: elif 'slo' in request.args:
tokenResponse = session.get('tokenResponse') if 'refresh_token' in request.cookies:
if tokenResponse is not None:
# propagate logout to Keycloak # propagate logout to Keycloak
refreshToken = tokenResponse['refresh_token'] refresh_token = request.cookies['refresh_token']
endSessionEndpoint = f'{issuer}/protocol/openid-connect/logout' requests.post(f'{issuer}/protocol/openid-connect/logout', data={
requests.post(endSessionEndpoint, data={ "client_id": client_id,
"client_id": clientId, "client_secret": client_secret,
"client_secret": clientSecret, "refresh_token": refresh_token,
"refresh_token": refreshToken,
}) })
session.pop('user', None) response = make_response(redirect('/'))
#session.pop('tokenResponse', None) response.set_cookie('accessToken', '', expires=0)
return redirect('/') 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 paint_logout = True
strDict = {} attributes = {'idToken': [request.cookies['idToken']]}.items()
for k in session['user']:
strDict[k] = [ str(session['user'][k]) ] if 'name' in session:
attributes = strDict.items() 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( return render_template(
'index.html', 'index.html',
attributes=attributes, 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,
) )

12
templates/api.html Normal file
View File

@ -0,0 +1,12 @@
{% extends "base.html" %}
{% block content %}
{% if not_auth_warn %}
<div class="alert alert-danger" role="alert">Not authorized</div>
{% else %}
<p>The API content</p>
{% endif %}
<a href="/" class="btn btn-dark">Back</a>
{% endblock %}

View File

@ -2,6 +2,14 @@
{% block content %} {% block content %}
{% if not_auth_warn %}
<div class="alert alert-danger" role="alert">Not authenticated</div>
{% endif %}
{% if user_name and user_email %}
<div class="alert alert-success" role="alert">Welcome {{ user_name }} ({{ user_email }}) !</div>
{% endif %}
{% if paint_logout %} {% if paint_logout %}
{% if attributes %} {% if attributes %}
<p>You have the following attributes:</p> <p>You have the following attributes:</p>
@ -27,6 +35,11 @@
{% else %} {% else %}
<a href="?sso" class="btn btn-primary">Login</a> <a href="?sso" class="btn btn-primary">Login</a>
{% endif %} {% endif %}
<a href="/userinfo" class="btn btn-info">Userinfo</a>
{% if access_token %}
<a href="/api?accessToken={{ access_token }}" class="btn btn-secondary">Call protected API</a>
{% else %}
<a href="/api" class="btn btn-secondary">Call protected API</a>
{% endif %}
{% endblock %} {% endblock %}