Python client SDK for Signward Identity Server — OIDC authentication for FastAPI, Flask, and other Python apps.
Project description
signward-idserver-client
Python client SDK for Signward Identity Server — OIDC authentication for FastAPI, Flask, and other Python apps. Open source, no Microsoft-stack dependency.
Features
- OIDC Authorization Code flow (with optional PKCE)
- Local JWT validation via the server's JWKS (
HS256today, algorithm-agnostic) - Discovery + JWKS caching
- Typed Pydantic v2 user model with built-in and per-tenant custom roles
- Async-first (
httpx.AsyncClient) - First-class FastAPI (
Depends-friendly helpers) and Flask (Blueprint +login_required/role_requireddecorators) integrations - MIT licensed
Install
pip install "signward-idserver-client[fastapi]" # FastAPI integration
pip install "signward-idserver-client[flask]" # Flask integration
Core-only (no framework integration):
pip install signward-idserver-client
Python 3.10+.
Quickstart — FastAPI (protecting an API)
from fastapi import FastAPI, Depends
from idserver import IdServerClient
from idserver.fastapi import IdServerAuth, CurrentUser
app = FastAPI()
idserver = IdServerClient(
authority="https://mytenant.signward.com",
client_id="my-api",
client_secret="...", # if confidential
options=None,
)
auth = IdServerAuth(idserver)
@app.get("/me")
async def me(user: CurrentUser = Depends(auth.current_user)):
return {
"id": user.user_id,
"email": user.email,
"tenant_id": user.tenant_id,
"roles": user.roles,
"custom_roles": user.custom_roles,
}
@app.get("/admin")
async def admin(user: CurrentUser = Depends(auth.require_role("admin"))):
return {"ok": True}
@app.get("/editors-or-admins")
async def editors(
user: CurrentUser = Depends(auth.require_role("admin", "editor")),
):
return {"ok": True}
@app.get("/billing-admin-only")
async def billing(
user: CurrentUser = Depends(
auth.require_role("admin", "billing", require_all=True)
),
):
return {"ok": True}
Clients call your API with a bearer token obtained from Signward:
GET /me
Authorization: Bearer eyJhbGciOi...
Quickstart — Flask (server-side login)
The [flask] extra ships a Blueprint (/login, /callback, /logout) plus
login_required / role_required decorators:
from flask import Flask
from idserver import IdServerClient
from idserver.flask import IdServerAuth, init_app, login_required, role_required
app = Flask(__name__)
app.secret_key = "change-me" # required: tokens + claims live in the session
client = IdServerClient(
authority="https://mytenant.signward.com",
client_id="my-webapp",
client_secret="...",
)
auth = IdServerAuth(client, post_login_redirect="/")
app.register_blueprint(auth.blueprint, url_prefix="/auth")
init_app(app, auth) # required: wires the decorators to this app
@app.route("/profile")
@login_required
def profile():
user = auth.current_user()
return {"id": str(user.user_id), "email": user.email, "roles": user.roles}
@app.route("/admin")
@role_required("admin")
def admin():
return "Admin area"
Register https://myapp.com/auth/callback as a redirect URI on your Signward client.
Quickstart — Server-side login flow
For a Python web app that wants to perform the full OIDC login flow itself:
from idserver import IdServerClient
client = IdServerClient(
authority="https://mytenant.signward.com",
client_id="my-webapp",
client_secret="...",
)
# 1) Redirect the user to the login page:
url, verifier = client.authorize_url_with_pkce(
redirect_uri="https://myapp.com/callback",
state="random-state",
)
# Store `verifier` and `state` in the session, redirect to `url`.
# 2) On callback (?code=...&state=...):
tokens = await client.exchange_code(
code,
redirect_uri="https://myapp.com/callback",
code_verifier=verifier,
)
# 3) Validate the access token locally:
user = await client.validate_token(tokens.access_token)
print(user.email, user.roles)
# 4) Or fetch userinfo remotely:
user = await client.userinfo(tokens.access_token)
# 5) Later, refresh:
new_tokens = await client.refresh_token(tokens.refresh_token)
# 6) Logout URL:
logout = await client.end_session_url(
id_token_hint=tokens.id_token,
post_logout_redirect_uri="https://myapp.com/",
)
User model
class User:
user_id: UUID | None # "sub" claim
email: str | None
email_verified: bool | None
name: str | None
given_name: str | None
family_name: str | None
tenant_id: UUID | None
roles: list[str] # built-in roles
custom_roles: list[str] # per-tenant RBAC roles
claims: dict # raw JWT / userinfo payload
def has_role(self, role: str) -> bool: ...
def has_custom_role(self, role: str) -> bool: ...
def has_any_role(self, *roles: str, include_custom: bool = True) -> bool: ...
def has_all_roles(self, *roles: str, include_custom: bool = True) -> bool: ...
Configuration
Pass values directly to IdServerClient or use an IdServerOptions dataclass
for more control:
from idserver import IdServerClient, IdServerOptions
client = IdServerClient(options=IdServerOptions(
authority="https://mytenant.signward.com",
client_id="my-app",
client_secret="...",
scopes=["openid", "profile", "email", "roles"],
audience="idserver-api", # expected JWT aud claim
issuer=None, # default: discovery.issuer
timeout=10.0,
verify_ssl=True, # set False for localhost dev
))
Using your own HTTP client
If you want to share an httpx.AsyncClient with the rest of your app:
import httpx
from idserver import IdServerClient
shared_http = httpx.AsyncClient(timeout=20.0)
client = IdServerClient(
authority="...",
client_id="...",
http_client=shared_http,
)
# When constructed this way, IdServerClient will NOT close shared_http.
Error handling
All exceptions derive from idserver.IdServerError:
from idserver import IdServerError, InvalidTokenError, TokenExchangeError
try:
user = await client.validate_token(token)
except InvalidTokenError as e:
...
except TokenExchangeError as e:
print(e.error, e.description, e.status_code)
except IdServerError as e:
...
License
MIT
Project details
Release history Release notifications | RSS feed
Download files
Download the file for your platform. If you're not sure which to choose, learn more about installing packages.
Source Distribution
Built Distribution
Filter files by name, interpreter, ABI, and platform.
If you're not sure about the file name format, learn more about wheel file names.
Copy a direct link to the current filters
File details
Details for the file signward_idserver_client-1.0.1.tar.gz.
File metadata
- Download URL: signward_idserver_client-1.0.1.tar.gz
- Upload date:
- Size: 14.0 kB
- Tags: Source
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
0c73d964b0de2736c2b365f94a2837ce9eee559d91a78ad44e2b86d0e4e18591
|
|
| MD5 |
a22122ff703b885859805e7b4dea621c
|
|
| BLAKE2b-256 |
57788c3fb783b7c1bf0dd5a067a4df3c1f68a542a4c200efd62564efb89a8d1d
|
File details
Details for the file signward_idserver_client-1.0.1-py3-none-any.whl.
File metadata
- Download URL: signward_idserver_client-1.0.1-py3-none-any.whl
- Upload date:
- Size: 15.7 kB
- Tags: Python 3
- Uploaded using Trusted Publishing? No
- Uploaded via: twine/6.2.0 CPython/3.13.12
File hashes
| Algorithm | Hash digest | |
|---|---|---|
| SHA256 |
a182f709b5095f8aced381aa8cd33cff4fe961a5df467d3f9eeb0757edb262ef
|
|
| MD5 |
c8dda50d79c60e31c39767e6a525f28c
|
|
| BLAKE2b-256 |
1e8a1c3e0320d77b1afb0a4bacb72a9bf5104053cef84dd7cae88a3c20fb242c
|