oidc example auth

This commit is contained in:
Arian Nasr
2026-02-25 21:27:34 -05:00
parent b7844558ac
commit ea66a750c9
7 changed files with 199 additions and 7 deletions

97
app.py
View File

@@ -1,12 +1,97 @@
from flask import Flask
# Arian Nasr
# 2026-02-25
import os
import functools
from flask import Flask, render_template, request, redirect, url_for, session
from authlib.integrations.flask_client import OAuth # noqa
from dotenv import load_dotenv
load_dotenv()
app = Flask(__name__)
# ── Security ──────────────────────────────────────────────────────────────────
app.secret_key = os.environ.get("SECRET_KEY", "CHANGE_ME_IN_PRODUCTION")
app.config["SESSION_COOKIE_HTTPONLY"] = True
app.config["SESSION_COOKIE_SAMESITE"] = "Lax"
# Set SESSION_COOKIE_SECURE = True when serving over HTTPS
@app.route('/')
def hello_world(): # put application's code here
return 'Hello World!'
# ── Authentik OIDC config ─────────────────────────────────────────────────────
AUTHENTIK_URL = os.environ.get("AUTHENTIK_URL", "https://auth.example.com")
AUTHENTIK_APP_SLUG = os.environ.get("AUTHENTIK_APP_SLUG", "my-app")
oauth = OAuth(app)
oauth.register(
name="authentik",
client_id=os.environ.get("AUTHENTIK_CLIENT_ID"),
client_secret=os.environ.get("AUTHENTIK_CLIENT_SECRET"),
# Authentik exposes a per-application OIDC discovery document
server_metadata_url=(
f"{AUTHENTIK_URL}/application/o/{AUTHENTIK_APP_SLUG}/.well-known/openid-configuration"
),
client_kwargs={"scope": "openid email profile"},
)
if __name__ == '__main__':
app.run()
# ── Helpers ───────────────────────────────────────────────────────────────────
def login_required(f):
"""Decorator that redirects unauthenticated users to /login."""
@functools.wraps(f)
def decorated(*args, **kwargs):
if not session.get("user"):
return redirect(url_for("login", next=request.url))
return f(*args, **kwargs)
return decorated
# ── Routes ────────────────────────────────────────────────────────────────────
@app.route("/")
def index():
user = session.get("user")
return render_template("index.html", user=user)
@app.route("/login")
def login():
redirect_uri = url_for("callback", _external=True)
return oauth.authentik.authorize_redirect(redirect_uri)
@app.route("/callback")
def callback():
token = oauth.authentik.authorize_access_token()
# authlib parses and verifies the ID token automatically;
# userinfo claims are available directly on the token dict.
user = token.get("userinfo")
if user is None:
# Fall back to the userinfo endpoint if claims aren't in the token
user = oauth.authentik.userinfo()
session["user"] = dict(user)
# Honour the 'next' redirect set by login_required, defaulting to home
next_url = request.args.get("next") or url_for("index")
return redirect(next_url)
@app.route("/logout")
def logout():
session.clear()
# Redirect to Authentik's end-session endpoint so the SSO session is
# also terminated. The post_logout_redirect_uri must be registered in
# the Authentik provider settings.
end_session_url = (
f"{AUTHENTIK_URL}/application/o/{AUTHENTIK_APP_SLUG}/end-session/"
)
return redirect(end_session_url)
# ── Protected example ─────────────────────────────────────────────────────────
@app.route("/dashboard")
@login_required
def dashboard():
"""Example of a route that requires authentication."""
return render_template("dashboard.html", user=session["user"])
if __name__ == "__main__":
app.run(debug=True)