Verified Ham with Python (Authlib)
Sign-in for a Flask app with Authlib, which reads everything from discovery.
Authlib reads everything it needs from the discovery document,
including the keys to check ID tokens with. This is a complete Flask app, also in the repository as
examples/python.
1. Register your app
- Create a Verified Ham account, if you don't have one, and sign in.
- Go to Register an app.
- Give it a name. People see it when they sign in.
- Set the redirect URI to:
http://localhost:5000/callback - Choose Confidential, and register.
- Copy the client ID and client secret. The secret is shown only once.
You can sign in to your own app straight away, to test. Other people can sign in once you've verified your own call sign.
2. The app
pip install flask authlib requests, save this as app.py, and run
VH_CLIENT_ID=… VH_CLIENT_SECRET=… flask --app app run --port 5000.
"""Sign in with Verified Ham in a Flask app, with Authlib.
pip install -r requirements.txt
VH_CLIENT_ID=... VH_CLIENT_SECRET=... flask --app app run --port 5000
Register the app at https://verifiedham.net/developers/apps/new with the
redirect URI http://localhost:5000/callback, then open http://localhost:5000.
"""
import json
import os
from authlib.integrations.flask_client import OAuth
from flask import Flask, redirect, session, url_for
from markupsafe import escape
app = Flask(__name__)
app.secret_key = os.environ.get("SESSION_SECRET", "change me")
oauth = OAuth(app)
oauth.register(
"verifiedham",
client_id=os.environ["VH_CLIENT_ID"],
client_secret=os.environ["VH_CLIENT_SECRET"],
# Authlib reads the endpoints and signing keys from discovery.
server_metadata_url=os.environ.get("VH_ISSUER", "https://verifiedham.net") + "/.well-known/openid-configuration",
client_kwargs={"scope": "openid callsign", "code_challenge_method": "S256"},
)
@app.route("/login")
def login():
return oauth.verifiedham.authorize_redirect(url_for("callback", _external=True))
@app.route("/callback")
def callback():
token = oauth.verifiedham.authorize_access_token()
# The ID token's claims, checked against the provider's keys.
session["user"] = dict(token["userinfo"])
return redirect("/")
@app.route("/logout")
def logout():
session.clear()
return redirect("/")
@app.route("/")
def home():
user = session.get("user")
if not user:
return '<a href="/login">Sign in with Verified Ham</a>'
# Key your users by sub, and trust the call sign only when callsign_verified is true.
who = f"<strong>{escape(user['callsign'])}</strong>, verified" if user.get("callsign_verified") else "without a verified call sign yet"
return f'<p>Signed in {who}. <a href="/logout">Sign out</a></p><pre>{escape(json.dumps(user, indent=2))}</pre>'
How it works
code_challenge_method: S256turns on PKCE, which Verified Ham requires.authorize_access_token()checks the state, exchanges the code, and validates the ID token againsthttps://verifiedham.net/.well-known/jwks.json.token["userinfo"]holds the ID token's claims:sub,callsignandcallsign_verified.- For Django, Authlib's Django client takes the same
register()arguments.