Repository navigation
v0.4.0 — Security hardening
Security hardening
EasyOIDC 0.4.0 closes gaps that every Keycloak-backed app had to patch by hand: PKCE, ID token validation, client roles, Keycloak 18+ logout, token refresh, and the OAuth state being reused as the session id.
uv add "easyoidc>=0.4.0" # pulls joserfc (already required by Authlib 1.7+)The public API is backwards compatible — existing code runs unchanged. Some defaults change (PKCE, nonce, ID token validation); each one can be turned off per Config. Please read Behaviour changes before upgrading.
Access policy and Keycloak client roles
auth = FastAPIOIDClient(
app,
auth_config=Config('.env', roles_source='client'), # resource_access.<client_id>.roles
session_storage=SessionHandler(mode='redis'),
required_roles=['my-app-user', 'my-app-admin'], # at least one
require_email_verified=True,
validate_session_on_each_request=False, # trust the store, refresh on expiry
)Available on the FastAPI, Flask, NiceGUI and Taipy clients. A user who authenticates but fails the policy gets a 403 page with an IdP logout link (access_denied_handler= to customise). Being authenticated in a realm with self-registration no longer means "allowed".
Added
- PKCE S256 on the authorization code flow (
use_pkce, default on). - Nonce + ID token validation (
use_nonce,validate_id_token, default on): signature against the provider JWKS,iss,aud,exp,nonce,azp,subconsistency with userinfo. OIDClient.begin_login()/complete_login()for low-level integrations.required_roles,require_email_verified,access_denied_handler,auth_error_handler.roles_source(realm/client/realm+client),roles_claims_from(userinfo/id_token/access_token),roles_client_id.roles_getter=now also on Flask and NiceGUI.- Token refresh: expired access tokens are refreshed and persisted; userinfo and the policy are re-checked, so a role removed in the IdP takes effect at the next refresh.
?next=/pathon the login route (local paths only),get_id_claims(),http_timeout(10 s),login_timeout_seconds(600 s).AccessDeniedError,IDTokenValidationError,__version__exported fromEasyOIDC.
Fixed
- Keycloak 18+ logout:
client_idalways sent;id_token_hintURL-encoded and omitted when absent (it was the literal stringNone). - IdP callback errors (
?error=...) render an error page instead of a redirect loop to/login. - Open redirect on the post-login redirect.
- The cookie now carries a random session id, rotated after login, instead of the OAuth
state(which travels in URLs and logs). - Flask: crashes on
get_user_roles()/is_authenticated()/get_userinfo()for anonymous users; referrer cookie is nowHttpOnly/SameSite=Lax. - NiceGUI: blocking OIDC calls moved to a threadpool.
StreamlitODICClientdid not inherit fromOIDClient.TaipyOIDClientoverwroteunrestricted_routesand crashed withauth_config=None.token_revoke()without a refresh token;dump_configuration()leakingcookie_secret_key.
Behaviour changes (read before upgrading)
| Change | Who is affected | Keep 0.3.x behaviour |
|---|---|---|
| PKCE S256 sent by default | Providers that reject code_challenge (rare; Keycloak, Auth0, Google, Entra accept it) |
use_pkce=False |
nonce sent and checked |
Providers that don't echo nonce in the ID token |
use_nonce=False |
| ID token validated | Providers whose iss/aud don't match discovery / client_id, or that omit id_token with the openid scope |
validate_id_token=False |
| Cookie holds a random session id, not the OAuth state | Code that used session['easyoidc-session-state'] as the OAuth state |
Use get_userinfo() / get_current_token() |
Store entries gain login, id_claims, roles keys |
Code comparing store entries for equality | Read the keys you need. 0.3.x sessions survive the upgrade. |
?error= callback returns HTTP 400 instead of redirecting to /login |
Apps relying on the redirect | auth_error_handler= |
403 for users without email_verified / required role |
Only if you opt in | Don't pass required_roles / require_email_verified |
Unchanged: default roles source (realm_access.roles from userinfo), route names, SESSION_STATE_VAR_NAME, require_roles, validate_session_on_each_request=True, SessionHandler.
auth_server_login() / get_token() are kept and do not add PKCE or nonce (they can't return the per-login secrets). Low-level integrations should move to begin_login() / complete_login().
Upgrade checklist
uv add "easyoidc>=0.4.0".- Keycloak clients with Full scope allowed off: set
roles_source='client'(addroles_claims_from='userinfo,access_token'if the client roles mapper only targets the access token). - Replace hand-written "check role /
email_verifiedafter login" code withrequired_roles=/require_email_verified=. - Remove subclasses that only added PKCE,
?next=or a logout fix. - Test login and logout once against your IdP. On an ID token error, check
issuerandclient_idfirst.
Full details: CHANGELOG.md · PR #6