full version (only miss access token validation on API)
This commit is contained in:
parent
92e6e65022
commit
e2db6cf56b
117
app.py
117
app.py
@ -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
12
templates/api.html
Normal 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 %}
|
@ -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 %}
|
Loading…
Reference in New Issue
Block a user