pymaskinporten
Python client for Maskinporten, Digdir's machine-to-machine OAuth2 token service for Norwegian businesses that exchange data.
graph LR
Consumer["Application"] -- request token ---> Maskinporten
Maskinporten -- issue new token ---> Consumer
Consumer -- use token ---> Service["External Service"]
Installation
pip install pymaskinporten
Optional extras add the secret-store SDKs and the demo app:
| Extra | Adds |
|---|---|
azure |
Azure Key Vault loader |
databricks |
Databricks secrets loader |
aws |
AWS Secrets Manager loader |
gcp |
Google Secret Manager loader |
web |
The pymaskinporten-web demo app |
For example: pip install "pymaskinporten[azure,web]".
Example usage
This assumes you have created the integration at Digdir and have its private key (PEM), key ID, client ID and scope.
By default the credentials are read from the environment variables PRIVATE_KEY,
MASKINPORTEN_CLIENT_ID, KID and SCOPE:
from pymaskinporten import request_maskinporten_token
access_token, expires_in = request_maskinporten_token("test") # or "prod"
api_env must be "test" or "prod"; anything else raises ValueError. A failed token request
raises MaskinportenTokenError, with Maskinporten's HTTP status, OAuth error code (e.g.
invalid_scope) and description as attributes when there was a response:
from pymaskinporten import MaskinportenTokenError
try:
access_token, expires_in = request_maskinporten_token("test")
except MaskinportenTokenError as e:
print(e.status, e.code, e.description)
Secret stores
load_config() can also read the credentials from a secret store. Pass the result with config=:
from pymaskinporten import load_config, request_maskinporten_token
config = load_config("azure", key_vault_uri="https://my-vault.vault.azure.net/")
access_token, expires_in = request_maskinporten_token("prod", config=config)
| Store | Call | Install |
|---|---|---|
| Azure Key Vault | load_config("azure", key_vault_uri="https://...") |
pip install "pymaskinporten[azure]" |
| Databricks secrets | load_config("databricks", secret_scope="maskinporten") |
pip install "pymaskinporten[databricks]" |
| AWS Secrets Manager | load_config("aws", region="eu-north-1") |
pip install "pymaskinporten[aws]" |
| Google Secret Manager | load_config("gcp", project_id="my-project") |
pip install "pymaskinporten[gcp]" |
Each store signs in with its SDK's default credentials: DefaultAzureCredential for Azure,
the Databricks runtime for Databricks, boto3's credential chain for AWS (region falls back to
boto3's configured region), and Application Default Credentials for Google (the latest secret
version is read).
Secret stores use dashes in the names, because Key Vault doesn't allow underscores:
| Credential | Environment variable | Secret name |
|---|---|---|
| Private key (PEM) | PRIVATE_KEY |
PRIVATE-KEY |
| Client ID | MASKINPORTEN_CLIENT_ID |
MASKINPORTEN-CLIENT-ID |
| Key ID | KID |
KID |
| Scope | SCOPE |
SCOPE |
Name prefixes
prefix is put in front of every environment variable or secret name, so test and prod
credentials can live side by side:
# Reads PROD_PRIVATE_KEY, PROD_KID, ...
load_config(prefix="PROD_")
# Reads maskinporten/prod/PRIVATE-KEY, ...
load_config("aws", prefix="maskinporten/prod/")
The prefix is used as-is, so use only characters the source allows (besides letters and digits):
- Environment variables:
_ - Azure Key Vault:
- - Google Secret Manager:
-_ - Databricks:
-_. - AWS Secrets Manager:
-_/+=.@
Passing credentials directly
To use credentials from anywhere else, build them yourself and pass them with config=:
from pymaskinporten import MaskinportenSecrets, request_maskinporten_token
secrets = MaskinportenSecrets(
PRIVATE_KEY=open("private_key.pem").read(),
MASKINPORTEN_CLIENT_ID="your-client-id",
KID="your-kid",
SCOPE="your-scope",
)
access_token, expires_in = request_maskinporten_token("prod", config=secrets)
Using a .env file
By default, load_config() reads environment variables only. To keep the credentials in a .env
file, install python-dotenv (pip install python-dotenv) and load it before requesting a
token:
from dotenv import load_dotenv
load_dotenv()
The demo app below does this for you.
Reusing tokens
request_maskinporten_token fetches a new token on every call. To reuse a token until shortly
before it expires, keep a MaskinportenClient around and call get_token() whenever you need one:
from pymaskinporten import MaskinportenClient
client = MaskinportenClient(api_env="prod") # reads the config once with load_config()
headers = {"Authorization": f"Bearer {client.get_token()}"}
It fetches a new token refresh_margin seconds (default 10) before the old one expires. It is
thread-safe: concurrent callers share one request. In async code, call it with
await asyncio.to_thread(client.get_token). It takes the same token_url and audience
overrides as request_maskinporten_token.
Other endpoints
api_env picks the token endpoint and the JWT audience. To call another server, such as a local
mock, override them:
access_token, expires_in = request_maskinporten_token(
token_url="http://localhost:6969/token",
audience="http://localhost:6969/",
)
Always override audience together with token_url. With token_url alone, the JWT is still
made out to the real Maskinporten, and the other server could use it there for up to a minute.
Checking a setup
pymaskinporten doctor requests a token with your credentials and, if Maskinporten refuses,
explains the likely cause (wrong scope, unregistered key, rejected JWT, network) with a checklist.
It never prints the private key or the token.
pymaskinporten doctor # test environment, credentials from environment variables
pymaskinporten doctor --env prod --deployment azure --key-vault-uri https://my-vault.vault.azure.net/
pymaskinporten doctor --help # all options, the same as load_config()
Testing against Maskinporten
To check a change against the real test environment, create a Maskinporten integration for a synthetic organisation (syntetisk organisasjon) in Digdir's test self-service, then from the repo root:
uv run --extra web python scripts/make_test_key.py # writes a new .env with a key pair
# paste PUBLIC_KEY from .env into the integration, fill in KID, MASKINPORTEN_CLIENT_ID, SCOPE
uv run --extra web python scripts/real_token_test.py
Docker container
To test the library in a web browser, use the pymaskinporten-web demo app (a flask app).
Run it locally with pip install "pymaskinporten[web]" and pymaskinporten-web, or use the
Docker image:
cat > .env <<EOF
MASKINPORTEN_CLIENT_ID=your-client-id
KID=your-kid
SCOPE=your-scope
EOF
docker run -p 127.0.0.1:5000:5000 --env-file .env \
-e PRIVATE_KEY="$(cat private_key.pem)" \
ghcr.io/norwegianveterinaryinstitute/pymaskinporten:main
--env-file keeps quotes as part of the value and doesn't support multi-line values, so leave
the values unquoted and pass the PEM key with -e. In PowerShell, use
-e PRIVATE_KEY="$(Get-Content -Raw private_key.pem)".
Then open http://127.0.0.1:5000.
Metadata
Release files for pymaskinporten 2.1.0
For a detailed explanation of source distributions (sdists) and built distributions (wheels), please see the package formats documentation.
Source distribution (sdist)
| File | Size | Uploaded | Provenance | |
|---|---|---|---|---|
| pymaskinporten-2.1.0.tar.gz | 15.1 kB | Without provenance | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| pymaskinporten-2.1.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 32.7 kB
Release files / pymaskinporten-2.1.0.tar.gz
| Download URL | pymaskinporten-2.1.0.tar.gz |
|---|---|
| Size | 15.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
047e5b0a10a0af997c24337386b859d5c7cb948bcef5ef1f35cc54eaf6ae7ba8
|
|
BLAKE2b-256 checksum How to use checksums |
bd3611a4b07f4eb2a20ec83040632595bbb7bc3a1508d055813b940b903fc86f
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Release files / pymaskinporten-2.1.0-py3-none-any.whl
| Download URL | pymaskinporten-2.1.0-py3-none-any.whl |
|---|---|
| Size | 17.6 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
5ff239e720bec49307a260512241c055e7eff985eff1692353df527d61e0e8c6
|
|
BLAKE2b-256 checksum How to use checksums |
02a58b0e5d577faa41461b59c9eed22d6c428c492deaa2ac8dcc8234b125e4d2
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
uv/0.13.3 {"installer":{"name":"uv","version":"0.13.3","subcommand":["publish"]},"python":null,"implementation":{"name":null,"version":null},"distro":{"name":"Ubuntu","version":"24.04","id":"noble","libc":null},"system":{"name":null,"release":null},"cpu":null,"openssl_version":null,"setuptools_version":null,"rustc_version":null,"ci":true}
|
Security for pymaskinporten 2.1.0
Provenance
Provenance describes where a file came from. On PyPI, provenance is shared via attestations, which provide a verifiable record of the build or publishing details. View details, limitations and caveats.
No provenance found
None of the files in this release were uploaded with provenance metadata.
A note on package security
PyPI is a repository for distributing packages, not a service for auditing them. While third-party security researchers and our staff do their best to detect and remove malicious packages, PyPI cannot guarantee the quality, security or suitability of any given package.