Add support for multiple OIDC providers
Namespace per-provider session state as oidc:{provider}:... so several providers can share one session, and add an OIDCProviders mixin that wires multiple configs onto distinct /auth/{provider}/... routes. Logging out of one provider no longer signs the user out of the others.
This commit is contained in:
6 files changed
+527
-30
No files matched your search
@@ -40,6 +40,72 @@ async def profile(ctx: HttpContext):
|
||||
`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}:...`, so a user
|
||||
can be logged into several providers at once. Logging out of one provider only
|
||||
removes that provider's state and keeps the shared session alive while another
|
||||
provider is still logged in. Make sure each config's `redirect_uri` points at
|
||||
that provider's derived callback path.
|
||||
|
||||
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`:
|
||||
|
||||
```python
|
||||
google = OIDCMixin(google_config, session=session)
|
||||
github = OIDCMixin(github_config, session=session)
|
||||
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
|
||||
@@ -50,6 +116,7 @@ also works because the app applies mixin dependencies automatically.
|
||||
- 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)
|
||||
- Composable with any other `KayaMixin` (RSGI, MCP, etc.)
|
||||
|
||||
## Security notes
|
||||
|
||||
Reference in new issue
Block a user