Python client library for the RCD Identity OIDC provider.
Handle the full OAuth 2.0 / OpenID Connect lifecycle programmatically -authorization URL generation, PKCE, code exchange, RS256 JWT verification, refresh token rotation, and automatic token management.
pip install git+https://github.com/RedCokeDevelopment/rcdid.py.git
You can directly send LLM.txt to your LLM for a quick overview of the library and build your application fast.
Alternatively, you can follow the examples below:
from rcd_id import RCDIdentityClient client = RCDIdentityClient( issuer="https://auth.rcd.network", client_id="rcd_your_client_id", client_secret="your-client-secret", ) # 1. Build authorization URL (user visits in browser) req = client.authorization_url( redirect_uri="http://localhost:8080/callback", scope="openid email profile", ) # 2. After callback with ?code=..., exchange for tokens tokens = client.exchange_code( code="authorization-code-from-callback", redirect_uri="http://localhost:8080/callback", ) # 3. Use the access token user = client.userinfo(access_token=tokens.access_token) print(user.email) # [email protected] # 4. Refresh when needed new_tokens = client.refresh_token() print(new_tokens.access_token) # new access token print(new_tokens.refresh_token) # rotated refresh token
from rcd_id import RCDIdentityClient client = RCDIdentityClient( issuer="https://auth.rcd.network", client_id="rcd_your_public_client", client_auth_method="none", use_pkce=True, redirect_uri="http://localhost:8080/callback", ) req = client.authorization_url() print(f"Visit: {req.url}") print(f"Save code_verifier: {req.code_verifier}") # In your callback handler: tokens = client.exchange_code( code="authorization-code", code_verifier=req.code_verifier, )
client.set_tokens(tokens) # This auto-refreshes if the access token is expired: user = client.userinfo() # no token arg needed # Or get a valid token explicitly: valid_token = client.get_valid_token()
RCD Identity rotates refresh tokens. Each refresh_token() call invalidates the old token. Use the on_token_refresh callback to persist new tokens:
def save_tokens(tokens): db.execute( "UPDATE clients SET refresh_token = ? WHERE client_id = ?", [tokens.refresh_token, "my-client"], ) client = RCDIdentityClient( issuer="https://auth.rcd.network", client_id="rcd_abc", client_secret="secret", on_token_refresh=save_tokens, )
from rcd_id import AsyncRCDIdentityClient async with AsyncRCDIdentityClient( issuer="https://auth.rcd.network", client_id="rcd_abc", client_secret="secret", redirect_uri="http://localhost:8080/callback", ) as client: tokens = await client.exchange_code(code="...") user = await client.userinfo()
| Method | Description |
|---|---|
discover() |
Fetch and cache OIDC discovery document |
authorization_url(...) |
Build the authorization URL with PKCE support |
exchange_code(code, ...) |
Exchange authorization code for tokens |
refresh_token(...) |
Refresh access token (handles rotation) |
verify_access_token(token) |
Verify RS256 JWT against JWKS |
verify_id_token(token, nonce) |
Verify ID token and optionally check nonce |
userinfo(access_token) |
Fetch the UserInfo endpoint |
set_tokens(tokens) |
Store tokens for managed mode |
get_valid_token() |
Return valid token, auto-refreshing if expired |
clear_tokens() |
Clear stored tokens |
| Field | Type | Description |
|---|---|---|
access_token |
str |
The access token |
token_type |
str |
Always "Bearer" |
expires_in |
int |
Access token TTL (seconds) |
refresh_token |
str|None |
Refresh token (rotated) |
refresh_token_expires_in |
int|None |
Refresh token TTL |
scope |
str|None |
Granted scopes |
id_token |
str|None |
ID token (JWT) |
is_expired(leeway=30) |
bool |
Check if token is expired |
| Exception | Description |
|---|---|
ConfigurationError |
Invalid client configuration |
DiscoveryError |
Failed to fetch/parse OIDC discovery |
TokenError |
Token endpoint error (carries .error, .error_description) |
TokenVerificationError |
JWT verification failed |
InvalidTokenError |
Token expired or unavailable |
UserInfoError |
Userinfo endpoint error |
SessionError |
State/nonce mismatch |
Configure via client_auth_method:
| Method | Description |
|---|---|
client_secret_basic (default) |
Authorization: Basic <base64(client_id:secret)> |
client_secret_post |
Secret in POST body |
none |
Public client (requires use_pkce=True) |
# Install dev dependencies pip install -e ".[dev]" # Run tests pytest tests/ # Type check mypy rcd_id/ # Lint ruff check rcd_id/ tests/