oidc example auth
This commit is contained in:
97
app.py
97
app.py
@@ -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)
|
||||
|
||||
Reference in New Issue
Block a user