OIDC Project — Build a Complete OpenID Connect Authentication System
In this tutorial, you will learn about OIDC Project. We cover key concepts, practical examples, and best practices to help you master this topic.
Build a complete Openid Connect authentication system that supports login with multiple providers, ID token verification, UserInfo fetching, session management, and profile display using Python Flask and JWT libraries.
What You'll Build
- Flask app with multi-provider OIDC login
- ID token verification with JWKS
- Session management with secure cookies
- User profile from ID token and UserInfo
- Provider discovery and dynamic configuration
Why This Project Matters
Most applications need authentication, but implementing OIDC correctly requires handling many edge cases: token verification, state/nonce management, key rotation, error handling, and session security. This project gives you a reusable, production-ready OIDC auth system.
flowchart LR
User["User"] --> App["Flask App"]
App -->|"Login with Google"| Google["Google OIDC"]
App -->|"Login with GitHub"| GitHub["GitHub OIDC"]
App -->|"Login with Microsoft"| MS["Microsoft OIDC"]
App -->|"Verify"| JWKS["JWKS Endpoint"]
App -->|"Store"| Session["Session Store"]
App -->|"Profile"| UI["UserInfo Endpoint"]
style App fill:#dbeafe,stroke:#2563eb
Project Structure
oidc-auth-system/
app.py
config.py
oidc/
client.py
provider.py
verification.py
templates/
login.html
profile.html
home.html
requirements.txt
Step 1: Provider Configuration
# config.py
PROVIDERS = {
"google": {
"issuer": "https://accounts.google.com",
"client_id": "google-client-id",
"client_secret": "google-client-secret",
"scope": "openid profile email",
},
"microsoft": {
"issuer": "https://login.microsoftonline.com/common/v2.0",
"client_id": "ms-client-id",
"client_secret": "ms-client-secret",
"scope": "openid profile email",
},
"github": {
"issuer": "https://token.actions.githubusercontent.com",
"client_id": "gh-client-id",
"client_secret": "gh-client-secret",
"scope": "openid email",
},
}
SECRET_KEY = "your-secret-key-change-in-production"
SESSION_TYPE = "filesystem"
Step 2: OIDC Client
# oidc/client.py
import requests
import secrets
import urllib.parse
class OIDCClient:
def __init__(self, provider_config):
self.config = provider_config
self.discovery = self._discover()
def _discover(self):
url = f"{self.config['issuer'].rstrip('/')}/.well-known/openid-configuration"
resp = requests.get(url, timeout=10)
resp.raise_for_status()
return resp.json()
def get_auth_url(self, redirect_uri):
state = secrets.token_urlsafe(32)
nonce = secrets.token_urlsafe(16)
params = {
"client_id": self.config["client_id"],
"response_type": "code",
"redirect_uri": redirect_uri,
"scope": self.config["scope"],
"state": state,
"nonce": nonce,
}
url = f"{self.discovery['authorization_endpoint']}?{urllib.parse.urlencode(params)}"
return url, state, nonce
def exchange_code(self, code, redirect_uri, client_secret):
resp = requests.post(self.discovery["token_endpoint"], data={
"code": code,
"client_id": self.config["client_id"],
"client_secret": client_secret,
"redirect_uri": redirect_uri,
"grant_type": "authorization_code",
}, timeout=10)
resp.raise_for_status()
return resp.json()
def get_userinfo(self, access_token):
resp = requests.get(
self.discovery["userinfo_endpoint"],
headers={"Authorization": f"Bearer {access_token}"},
timeout=10
)
resp.raise_for_status()
return resp.json()
Step 3: Token Verification
# oidc/verification.py
import jwt
from jwt import PyJWKClient
class TokenVerifier:
def __init__(self, provider_config, discovery):
self.issuer = provider_config["issuer"]
self.client_id = provider_config["client_id"]
self.jwks_uri = discovery["jwks_uri"]
self.jwks_client = PyJWKClient(self.jwks_uri)
def verify_id_token(self, id_token, nonce=None):
signing_key = self.jwks_client.get_signing_key_from_jwt(id_token)
claims = jwt.decode(
id_token,
signing_key.key,
algorithms=["RS256"],
audience=self.client_id,
issuer=self.issuer,
options={
"verify_exp": True,
"verify_iat": True,
"require": ["iss", "sub", "aud", "exp"],
}
)
if nonce and claims.get("nonce") != nonce:
raise ValueError("Nonce mismatch")
return claims
Step 4: Flask Application
# app.py
from flask import Flask, request, redirect, session, render_template
from oidc.client import OIDCClient
from oidc.verification import TokenVerifier
from config import PROVIDERS, SECRET_KEY
app = Flask(__name__)
app.secret_key = SECRET_KEY
@app.route("/")
def home():
user = session.get("user")
return render_template("home.html", user=user)
@app.route("/login/<provider>")
def login(provider):
if provider not in PROVIDERS:
return "Unknown provider", 400
client = OIDCClient(PROVIDERS[provider])
auth_url, state, nonce = client.get_auth_url(
redirect_uri=f"https://{request.host}/callback/{provider}"
)
session["oauth_state"] = state
session["oauth_nonce"] = nonce
session["oauth_provider"] = provider
return redirect(auth_url)
@app.route("/callback/<provider>")
def callback(provider):
if request.args.get("state") != session.get("oauth_state"):
return "Invalid state", 400
if provider != session.get("oauth_provider"):
return "Provider mismatch", 400
client = OIDCClient(PROVIDERS[provider])
verifier = TokenVerifier(PROVIDERS[provider], client.discovery)
tokens = client.exchange_code(
code=request.args["code"],
redirect_uri=f"https://{request.host}/callback/{provider}",
client_secret=PROVIDERS[provider]["client_secret"],
)
claims = verifier.verify_id_token(
tokens["id_token"],
nonce=session.get("oauth_nonce")
)
userinfo = client.get_userinfo(tokens["access_token"])
session["user"] = {
"id": claims["sub"],
"provider": provider,
"name": claims.get("name") or userinfo.get("name"),
"email": claims.get("email") or userinfo.get("email"),
"picture": claims.get("picture") or userinfo.get("picture"),
}
session.permanent = True
return redirect("/")
@app.route("/logout")
def logout():
session.clear()
return redirect("/")
Step 5: Testing
# Run the app
flask run --host=0.0.0.0 --port=5000
# Test login flow (requires registered providers)
# Visit http://localhost:5000/login/google
# Login with Google account
# Check profile and session
Common Mistakes
1. Not Handling Token Expiry
Sessions should expire when the access/refresh token expires. Implement token refresh or force re-login.
2. Storing Sensitive Data in Session
Do not store tokens in client-side sessions. Store a session ID server-side and keep tokens in server memory/database.
3. Not Validating iss Claim
Without issuer validation, a token from a malicious provider can authenticate on your app.
4. Confusing Multiple Provider Sessions
If a user logs in with Google then Microsoft, handle session merging or use separate identities.
5. No Error Page for Failed Authentication
When OIDC fails (user denies consent, network error), show a user-friendly error page instead of a raw traceback.
Practice Questions
- Why should tokens not be stored in client-side sessions?
- How does the project handle multiple OIDC providers?
- What is the purpose of the
issuerfield in provider config? - Why is PKCE not used in this server-side implementation?
- How would you add token refresh to this project?
Answers:
- Client-side sessions are stored in browser cookies and can be decoded or manipulated. Store session IDs only and keep tokens server-side.
- Each provider has its own config entry. The
/login/<provider>route dynamically selects the correct OIDC client. - The
issueris used to construct the discovery URL and to validate theissclaim in ID tokens. - Server-side apps can securely store a client secret. PKCE is needed for public clients like SPAs and mobile apps.
- Store the refresh token server-side. On token expiry, call the token endpoint with
grant_type=refresh_token.
Challenge: Extend this project to support token refresh, remember-me functionality (30-day sessions), and account linking where one user can connect multiple providers.
FAQ
Mini Project
Complete the OIDC authentication system with at least two providers (Google and Microsoft). Implement user session management, ID token verification with JWKS, UserInfo fetching, and a profile page. Add error handling for all failure modes.
What's Next
Review the OIDC vs OAuth2 Differences to reinforce core concepts, or explore API Gateway Complete Guide for integrating OIDC with gateway authentication.
Built by the developers of DodaTech
Doda Browser, DodaZIP & Durga Antivirus Pro