OpenID Connect Provider (OP) library for Python
pyop is an OpenID Connect Provider (OP) library in Python, enabling applications to act as identity providers. It is actively maintained with a regular release cadence, adding new features, improving compatibility, and addressing bug fixes. The current version is 3.4.2.
Common errors
-
pymongo.errors.CollectionInvalid: collection names must not start or end with '.' or contain '$'
cause This error typically indicates an incompatibility between an older version of pyop's MongoStorage and PyMongo 4, which has stricter naming conventions.fixUpgrade pyop to version 3.4.1 or higher to ensure compatibility with PyMongo 4. Alternatively, if you cannot upgrade pyop, downgrade your PyMongo version to 3.x. -
AttributeError: 'Server' object has no attribute 'get_additional_scopes'
cause This error or similar issues related to custom scope handling often arise after upgrading to pyop v3.0.0 due to a breaking change in how additional scopes are managed.fixThe 'additional scopes' feature was re-implemented in a non-backwards compatible way in v3.0.0. Consult the release notes for v3.0.0 and update your custom scope definition and retrieval logic accordingly. -
TypeError: argument of type 'NoneType' is not iterable in userinfo handling
cause A bug existed in pyop versions prior to 3.4.1 where userinfo handling in stateless mode could lead to `TypeError` if a user ID was `None`.fixUpgrade pyop to version 3.4.1 or higher to resolve the bug related to `None` user IDs in stateless userinfo handling.
Warnings
- breaking Version 3.0.0 reverted support for 'additional scopes' introduced in v2.1.0 and reimplemented it in a non-backwards compatible way.
- gotcha When using PKCE support (introduced in v3.3.0), the library explicitly states that 'plaintext support is missing'.
- gotcha Prior to v3.4.1, pyop had compatibility issues with PyMongo 4, potentially leading to errors when using MongoStorage.
- gotcha Version 3.4.0 introduced support for a stateless code flow. If your application relies on stateful operations, upgrading might require attention to ensure existing state management patterns are compatible or if explicit stateless handling is desired.
Install
-
pip install pyop -
pip install pyop[mongo] -
pip install pyop[redis]
Imports
- Server
from pyop import Server
from pyop.server import Server
- DictStorage
from pyop.server.storage import DictStorage
from pyop.storage import DictStorage
Quickstart
import os
from pyop.server import Server
from pyop.storage import DictStorage
def create_op_server():
# In a real application, configuration would be loaded from a file or environment
OP_BASE_URL = os.environ.get('OP_BASE_URL', 'http://localhost:8090')
JWKS_URI = f'{OP_BASE_URL}/jwks.json'
# Client information (for registered clients)
# In a real scenario, this would come from a client registration process/database
CLIENTS = {
'test_client': {
'client_id': 'test_client',
'client_secret': 'test_secret',
'redirect_uris': ['http://localhost:8000/cb'],
'response_types': ['code', 'id_token', 'code id_token'],
'scope': ['openid', 'profile', 'email'],
'subject_type': 'pairwise'
}
}
# In-memory storage for demonstration purposes
# For production, use MongoStorage, RedisStorage, or a custom persistent storage
storage = DictStorage()
storage.store_clients(CLIENTS)
# Minimal server configuration
server_config = {
'issuer': OP_BASE_URL,
'jwks_uri': JWKS_URI,
'authentication_methods': ['client_secret_basic'],
'response_types_supported': ['code', 'id_token', 'code id_token'],
'subject_types_supported': ['pairwise'],
'scopes_supported': ['openid', 'profile', 'email'],
'claims_supported': ['sub', 'name', 'email', 'given_name', 'family_name']
}
op_server = Server(server_config, storage)
print(f"OpenID Connect Provider Server initialized with issuer: {op_server.configuration.issuer}")
return op_server
if __name__ == '__main__':
# Example usage: this only initializes the server, does not run a web server.
# A production app would integrate this into Flask, Django, FastAPI, etc.
# Example: op_server.handle_authentication_request(request_params, session_id)
op = create_op_server()
# You would then integrate 'op' with your web framework to handle OIDC endpoints.