Connect your application
Every OIDC integration needs the same five values, whether it's a framework library, self-hosted software, or code you write yourself. The application discovers everything else from the issuer.
| Value | Where it comes from |
|---|---|
| The issuer, or the discovery URL | The provider's General tab. The issuer is https://accounts.example.com; the discovery document is at /.well-known/openid-configuration. |
| The client ID | Shown after you register the application. |
| The client secret | Generate Client Secret on the application. Server-side applications only. |
| The redirect URI | Your app's callback URL, registered on the application exactly as your app sends it. |
| The scopes | What you ask for, within the app's scope policy. openid profile email is the usual set. |
Self-hosted software
Software with an "OpenID Connect" or "generic OAuth2" sign-in option takes these values directly. The setting names differ; the shape doesn't.
- If the software asks for individual endpoint URLs instead of an issuer, they're all in the discovery document.
- The software's documentation names the exact callback path. Use it as the redirect URI.
Your own application: a complete example
Take a Python web app on one of your
app servers, served at
https://team.example.com, that only your users may enter.
Register it. On the provider's Applications tab, add "Team app"
with the redirect URI https://team.example.com/auth/callback, and
generate a client secret. The default scope policy already covers
openid profile email.
Connect it. This example uses Flask and Authlib, but any current OIDC library has the same three parts: discover, redirect, callback.
from authlib.integrations.flask_client import OAuth
from flask import Flask, redirect, session, url_for
app = Flask(__name__)
app.secret_key = "replace-me"
oauth = OAuth(app)
oauth.register(
"elpan",
server_metadata_url="https://accounts.example.com/.well-known/openid-configuration",
client_id="YOUR_CLIENT_ID",
client_secret="YOUR_CLIENT_SECRET",
client_kwargs={"scope": "openid profile email"},
)
@app.route("/login")
def login():
return oauth.elpan.authorize_redirect(url_for("callback", _external=True))
@app.route("/auth/callback")
def callback():
token = oauth.elpan.authorize_access_token()
session["user"] = token["userinfo"] # sub, name, email
return redirect("/")
That's the whole integration. /login sends the user to your sign-in
portal, where they authenticate with a password, two-factor authentication,
or a passkey, as your policy requires. They consent on first use and come
back to /auth/callback with their identity. Your app never sees a
password, and it gets every security feature your users already have.
- Keep the client secret in an environment variable or an environment file, never in the source code.
- Link your app's records to the
subclaim, the user's stable identifier. Don't use the email address; it can change.
Ask for a stronger sign-in
An application can ask for a minimum authentication strength and read back what the user actually used. The four levels:
- single-factor: a password
- multi-factor: a password and a code from an authenticator
- phishing-resistant: a passkey
- phishing-resistant with a hardware-bound passkey, such as a security key
A user who signs in with a method that's too weak is asked for more, not refused. They complete the stronger method; if they have nothing stronger, the provider walks them through setting up an authenticator app on the spot.
Your authentication policy sets the minimum for everybody. This lets one application, such as an admin panel, ask for more.
When it doesn't work
The causes of failure are few and specific:
| Symptom | Cause and fix |
|---|---|
| Redirect URI mismatch | The URI your app sends must exactly match a registered one, including scheme and path. The classic causes are http vs https and a missing trailing path. |
| Scope not allowed | The app asked for a scope the application's policy prohibits. Change the scope policy or the request. |
| Discovery fails | The issuer URL must be the bare domain URL, without /.well-known/.... The identity provider service must also be active on that domain. |