-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathserver.py
More file actions
117 lines (92 loc) · 3.13 KB
/
server.py
File metadata and controls
117 lines (92 loc) · 3.13 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
# app/server.py
from flask import Flask, session, redirect, request, jsonify, url_for
from client import OIDCClient
from authlib.common.security import generate_token
import base64
import hashlib
from dotenv import load_dotenv
import os
server_base_url = "http://localhost:5002"
keycloack_base_url = "http://localhost:8080"
keycloack_realm_name = "master"
app = Flask(__name__)
app.secret_key = "your-app-secret-here"
# -------------------------------
# Configuración del IdP
# -------------------------------
load_dotenv()
OIDC = OIDCClient(
issuer=os.getenv("ISSUER"),
client_id=os.getenv("CLIENT_ID"),
client_secret=os.getenv("CLIENT_SECRET"),
redirect_uri=f"{server_base_url}/callback"
)
# -------------------------------
# Generación PKCE
# -------------------------------
def generate_pkce():
code_verifier = generate_token(64)
code_challenge = base64.urlsafe_b64encode(
hashlib.sha256(code_verifier.encode()).digest()
).rstrip(b"=").decode("ascii")
return code_verifier, code_challenge
# -------------------------------
# Endpoints de la app (RP)
# -------------------------------
@app.route("/")
def home():
return "<a href='/login'>Login with OpenID Connect</a>"
@app.route("/login")
def login():
state = generate_token(32)
code_verifier, code_challenge = generate_pkce()
session["state"] = state
session["code_verifier"] = code_verifier
auth_url = OIDC.create_auth_url(state, code_challenge)
return redirect(auth_url)
@app.route("/callback")
def callback():
if request.args.get("state") != session.get("state"):
return "Invalid state", 400
code = request.args.get("code")
token_set = OIDC.exchange_code(code, session["code_verifier"])
# Validar ID Token
claims = OIDC.validate_id_token(token_set["id_token"])
# Guardar sesión del usuario
session["user"] = {
"claims": claims,
"userinfo": OIDC.get_userinfo(token_set["access_token"])
}
session["id_token"] = token_set["id_token"]
return redirect(url_for("profile"))
@app.route("/profile")
def profile():
if "user" not in session:
return redirect("/login")
return jsonify(session["user"])
@app.route("/logout")
def logout():
session.clear()
return "Sesión cerrada. <a href='/'>Volver</a>"
# Logout cerrando sesion tambien en el servidor OIDC usando enfoque "Front-channel". Tambien
# podria implementarse back-channel logout en una arquitectura enterprise
@app.route("/logout_full")
def logout_full():
id_token = session.get("id_token")
# Limpieza de sesión local
session.clear()
# Endpoint de logout de Keycloak
keycloak_logout_url = (
f"{keycloack_base_url}/realms/{keycloack_realm_name}/protocol/openid-connect/logout"
)
params = {
"id_token_hint": id_token,
"post_logout_redirect_uri": server_base_url,
"client_id": OIDC.client_id
}
from urllib.parse import urlencode
logout_url = f"{keycloak_logout_url}?{urlencode(params)}"
return redirect(logout_url)
return "Sesión cerrada. <a href='/'>Volver</a>"
if __name__ == "__main__":
app.run(host="0.0.0.0", port=5002, debug=True)