# kaya-oidc OpenID Connect authentication for the Kaya web framework. Built on top of `kaya-session` and implements the OIDC **Authorization Code Flow with PKCE**. ## Usage ```python import os from kaya.core import HttpContext, KayaApp from kaya.session import SessionMixin, InMemorySessionStore from kaya.oidc import OIDCConfig, OIDCMixin session = SessionMixin(InMemorySessionStore()) oidc = OIDCMixin( OIDCConfig( issuer=os.environ['OIDC_ISSUER'], client_id=os.environ['OIDC_CLIENT_ID'], client_secret=os.environ.get('OIDC_CLIENT_SECRET'), redirect_uri='http://localhost:8000/auth/callback', fetch_userinfo=True, ), session=session, ) app = KayaApp(mixins=[session, oidc]) @app.GET('/') async def home(ctx: HttpContext): await ctx.send_str(200, 'public home') @app.GET('/profile') @oidc.require_auth async def profile(ctx: HttpContext): user = oidc.get_user(ctx) await ctx.send_str(200, f'Hello {user.email or user.sub}') ``` `OIDCMixin` depends on `SessionMixin`; passing only `oidc` to `KayaApp(mixins=...)` also works because the app applies mixin dependencies automatically. ## Multiple providers Use `OIDCProviders` to run several OIDC providers in one application. Each config's `provider` becomes a route segment under `base_path` (`/auth/{provider}/login`, `/auth/{provider}/callback` and `/auth/{provider}/logout`), and all providers share a single `SessionMixin`: ```python from kaya.oidc import OIDCConfig, OIDCProviders session = SessionMixin(InMemorySessionStore()) oidc = OIDCProviders( [ OIDCConfig( issuer='https://accounts.google.com', client_id=os.environ['GOOGLE_CLIENT_ID'], client_secret=os.environ['GOOGLE_CLIENT_SECRET'], redirect_uri='http://localhost:8000/auth/google/callback', provider='google', ), OIDCConfig( issuer='https://github.com/login/oauth', # or any OIDC issuer client_id=os.environ['GITHUB_CLIENT_ID'], client_secret=os.environ['GITHUB_CLIENT_SECRET'], redirect_uri='http://localhost:8000/auth/github/callback', provider='github', ), ], session=session, base_path='/auth', # optional ) app = KayaApp(mixins=[session, oidc]) @app.GET('/google-profile') @oidc.require_auth('google') async def google_profile(ctx: HttpContext): user = oidc.get_user(ctx, 'google') await ctx.send_str(200, f"Hello {user.email or user.sub}") @app.GET('/who') async def who(ctx: HttpContext): users = oidc.get_users(ctx) # {provider: OIDCUser} providers = oidc.logged_in_providers(ctx) # ['google', ...] await ctx.send_str(200, ', '.join(users)) ``` Each provider's session state is namespaced as `oidc:{provider}:...`. Make sure each config's `redirect_uri` points at that provider's derived callback path. ### Identity strategy `strategy` controls how many identities may coexist in the shared session: - `'single-identity'` (default): completing a login evicts every other provider's identity and tokens, so the session holds exactly one user — last login wins, like Spring Security. After a GitHub login, `get_user(ctx, 'google')` returns `None` and `require_auth('google')` redirects to the Google login. - `'multi-identity'`: each provider stays logged in independently. Use it for linked accounts, aggregating several provider APIs, or step-up flows. Logging out of one provider keeps the shared session alive while another provider is still logged in. ```python oidc = OIDCProviders([google_config, github_config], session=session, strategy='multi-identity') ``` Ephemeral flow keys (`state`, `nonce`, `code_verifier`) are never evicted, so logins already in flight can still complete; under `'single-identity'` the last callback to finish wins. Under either strategy, `get_users(ctx)` only reports providers actually present in the session. The composable alternative is to create one `OIDCMixin` per provider, pointing each at distinct `login_path`/`callback_path`/`logout_path` values and sharing the same `SessionMixin`. The same `strategy` keyword is accepted and defaults to `'single-identity'`: ```python google = OIDCMixin(google_config, session=session) github = OIDCMixin(github_config, session=session, strategy='multi-identity') app = KayaApp(mixins=[session, google, github]) ``` > **Upgrading:** session keys are now prefixed with `oidc:{provider}:` (the > default provider is `default`). Existing logged-in users are signed out once > and simply re-authenticate. ## Features - Generic OIDC discovery - Authorization Code Flow with PKCE (S256) - ID token signature validation with JWKS - `state` and `nonce` protection - Session fixation defense via `regenerate_id()` after login - Optional userinfo endpoint fetch - Refresh token support - RP-initiated logout (when provider advertises `end_session_endpoint`) - Multiple providers in one app via `OIDCProviders` (or several `OIDCMixin` instances) - `single-identity` (default) or `multi-identity` session strategy - Composable with any other `KayaMixin` (RSGI, MCP, etc.) ## Security notes - The `none` signing algorithm is rejected by default. - Only algorithms listed in `OIDCConfig.allowed_id_token_algorithms` are accepted. - Always use HTTPS for `redirect_uri` in production. ## Supported flows Only the Authorization Code Flow with PKCE is supported. Implicit and Hybrid flows are intentionally not implemented. ## Supported algorithms ID token signature verification supports: `RS256`, `RS384`, `RS512`, `PS256`, `PS384`, `PS512`, `ES256`, `ES384`, `ES512`, and `EdDSA`. HMAC algorithms (`HS*`) are disabled by default and can be enabled by adding them to `allowed_id_token_algorithms` if your provider uses them.