OIDC Discovery Setup
Agent77 verifies user JWTs by fetching your public keys via standard OIDC discovery. You only need to serve two static-ish JSON endpoints — no full OIDC provider required.
1. OpenID Configuration
Serve a JSON response at
https://<your-domain>/.well-known/openid-configuration:
{
"issuer": "https://app.example.com",
"jwks_uri": "https://app.example.com/.well-known/jwks.json",
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported": ["id_token"],
"subject_types_supported": ["public"]
}
The critical field is jwks_uri — Agent77 uses it to find
your public keys. The issuer must match the
iss claim in your JWTs.
2. JWKS Endpoint
Serve your public key(s) at the URL specified in jwks_uri:
{
"keys": [
{
"kty": "RSA",
"kid": "my-key-1",
"use": "sig",
"alg": "RS256",
"n": "<base64url-encoded modulus>",
"e": "AQAB"
}
]
}
The kid must match the kid header in JWTs
issued by your token endpoint. You can include multiple keys to support
rotation.
RSA Key Pair Generation
Python
# generate_keys.py — run once
from cryptography.hazmat.primitives.asymmetric import rsa
from cryptography.hazmat.primitives import serialization
private_key = rsa.generate_private_key(public_exponent=65537, key_size=2048)
# Save private key (keep secret!)
with open("private.pem", "wb") as f:
f.write(private_key.private_bytes(
serialization.Encoding.PEM,
serialization.PrivateFormat.PKCS8,
serialization.NoEncryption(),
))
# Save public key
with open("public.pem", "wb") as f:
f.write(private_key.public_key().public_bytes(
serialization.Encoding.PEM,
serialization.SubjectPublicKeyInfo,
))
print("Keys written to private.pem and public.pem") Node.js
// generate-keys.js — run once
const crypto = require("crypto");
const fs = require("fs");
const { publicKey, privateKey } = crypto.generateKeyPairSync("rsa", {
modulusLength: 2048,
publicKeyEncoding: { type: "spki", format: "pem" },
privateKeyEncoding: { type: "pkcs8", format: "pem" },
});
fs.writeFileSync("private.pem", privateKey);
fs.writeFileSync("public.pem", publicKey);
console.log("Keys written to private.pem and public.pem"); Django Example
# urls.py
from django.urls import path
from . import views
urlpatterns = [
path(".well-known/openid-configuration", views.openid_configuration),
path(".well-known/jwks.json", views.jwks),
] # views.py
import json, base64
from django.conf import settings
from django.http import JsonResponse
from cryptography.hazmat.primitives.serialization import load_pem_public_key
def openid_configuration(request):
return JsonResponse({
"issuer": settings.SITE_URL,
"jwks_uri": f"{settings.SITE_URL}/.well-known/jwks.json",
"id_token_signing_alg_values_supported": ["RS256"],
"response_types_supported": ["id_token"],
"subject_types_supported": ["public"],
})
def _b64url(n: int, length: int) -> str:
return base64.urlsafe_b64encode(
n.to_bytes(length, "big")
).rstrip(b"=").decode()
def jwks(request):
pub = load_pem_public_key(settings.CHATBOT_PUBLIC_KEY.encode())
numbers = pub.public_numbers()
n_bytes = (numbers.n.bit_length() + 7) // 8
return JsonResponse({
"keys": [{
"kty": "RSA",
"kid": settings.CHATBOT_KEY_ID,
"use": "sig",
"alg": "RS256",
"n": _b64url(numbers.n, n_bytes),
"e": _b64url(numbers.e, 3),
}]
}) Express Example
// routes/oidc.js
const crypto = require("crypto");
const fs = require("fs");
const PUBLIC_KEY = fs.readFileSync("./keys/public.pem");
const KEY_ID = process.env.CHATBOT_KEY_ID;
const SITE_URL = process.env.SITE_URL;
function openidConfiguration(req, res) {
res.json({
issuer: SITE_URL,
jwks_uri: `${SITE_URL}/.well-known/jwks.json`,
id_token_signing_alg_values_supported: ["RS256"],
response_types_supported: ["id_token"],
subject_types_supported: ["public"],
});
}
function jwks(req, res) {
const key = crypto.createPublicKey(PUBLIC_KEY);
const { n, e } = key.export({ format: "jwk" });
res.json({
keys: [{ kty: "RSA", kid: KEY_ID, use: "sig", alg: "RS256", n, e }],
});
}
module.exports = { openidConfiguration, jwks };