Skip to main content

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.

ValueWhere it comes from
The issuer, or the discovery URLThe provider's General tab. The issuer is https://accounts.example.com; the discovery document is at /.well-known/openid-configuration.
The client IDShown after you register the application.
The client secretGenerate Client Secret on the application. Server-side applications only.
The redirect URIYour app's callback URL, registered on the application exactly as your app sends it.
The scopesWhat 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.

Two common problems
  • 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.

Two things to get right
  • Keep the client secret in an environment variable or an environment file, never in the source code.
  • Link your app's records to the sub claim, 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.

info

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:

SymptomCause and fix
Redirect URI mismatchThe 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 allowedThe app asked for a scope the application's policy prohibits. Change the scope policy or the request.
Discovery failsThe issuer URL must be the bare domain URL, without /.well-known/.... The identity provider service must also be active on that domain.