ZanShield SDK for Python
Add ZanShield multi-factor authentication to your system's login. ZanShield is the authentication service run by eGAZ (eGovernment Authority Zanzibar).
Your system checks the user's password as usual, then hands them to ZanShield for the second factor. ZanShield decides everything from there: whether the user sets up their first method or verifies an existing one, and which methods (authenticator app, security key, email, SMS) your system allows. It then sends the user back to you with a signed acknowledgement. This library builds the ticket you send and checks the acknowledgement you get back.
Install
pip install zanshield-sdk
Requires Python 3.9+. Installs cryptography automatically.
Before you start
Your institution admin registers your system in the ZanShield administration console. You get:
- your system code (e.g.
PAYROLL01) and institution code - your system's private key (shown once; keep it on your server only, never in a browser or mobile app)
- ZanShield's engine public key (the admin console's "Download ZanShield public key" button)
- the portal address, e.g.
https://portal.zanshield.devtech.co.tz
The redirect URI you register is where ZanShield sends users back to.
Usage
from zanshield_sdk import AcknowledgementRejected, ZanShieldClient, load_private_key, load_public_key
client = ZanShieldClient(
system_code="PAYROLL01",
institution_code="EGAZ",
my_private_key=load_private_key(open("/secure/zanshield-private.pem").read()),
engine_public_key=load_public_key(open("/secure/zanshield-engine-public.pem").read()),
)
# 1. After the user's password is correct:
ticket = client.create_ticket(
user.id,
user.email,
full_name=user.full_name, # optional: the portal greets the user by name
mobile=user.phone, # optional: without it the user can't choose SMS codes
)
return redirect(ZanShieldClient.portal_url("https://portal.zanshield.devtech.co.tz", ticket))
# 2. At your redirect URI, ZanShield sends the user back with ?ticket=...
try:
ack = client.accept_acknowledgement(request.args["ticket"])
except AcknowledgementRejected as e:
# Genuine, but must not sign in: e.reason is "NOT_AUTHENTICATED", "EXPIRED" or "REPLAYED".
return show_error(e.acknowledgement.message or str(e))
start_session_for(ack.user_id)
accept_acknowledgement does every check your callback needs before it starts a session. If any check fails it raises an exception and returns nothing:
- The ticket decrypts with your key and is signed by ZanShield. If not, it raises
ZanShieldError, and you must never trust such a ticket. response_codeis9000andis_authenticatedis true.- It's fresh: issued less than 3 minutes ago, configurable with
max_acknowledgement_age=. - Its
txn_idhasn't been used before, so a leaked callback URL can't be replayed into a second session.
More than one server
By default, used txn_ids are remembered in memory, which protects a single process. If your application runs on several servers or workers, pass a store backed by something they share. Any object with a mark_used(txn_id, keep_until) -> bool method works:
class RedisTxnIdStore:
def __init__(self, redis):
self.redis = redis
def mark_used(self, txn_id, keep_until):
ttl = max(1, int((keep_until - datetime.now(timezone.utc)).total_seconds()))
return bool(self.redis.set(f"zanshield:txn:{txn_id}", 1, nx=True, ex=ttl))
client = ZanShieldClient(..., txn_id_store=RedisTxnIdStore(redis))
Response codes
ResponseCodes has every code ZanShield uses. On an acknowledgement, only ResponseCodes.SUCCESS (9000) means the user verified.
License
Apache-2.0
Metadata
Release files for zanshield-sdk 1.0.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 | |
|---|---|---|---|
| zanshield_sdk-1.0.0.tar.gz | 15.3 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| zanshield_sdk-1.0.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.2 kB
Release files / zanshield_sdk-1.0.0.tar.gz
| Download URL | zanshield_sdk-1.0.0.tar.gz |
|---|---|
| Size | 15.3 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
a5b7fb22e819d784cc8823f37d55a17be8758685883ecb53969bbb163d8a40b0
|
|
BLAKE2b-256 checksum How to use checksums |
45a5ee9aee57c006c6fdb0c0e55f4ca2d8812e89a4ae9d89bb86b20d319d32de
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency logRelease files / zanshield_sdk-1.0.0-py3-none-any.whl
| Download URL | zanshield_sdk-1.0.0-py3-none-any.whl |
|---|---|
| Size | 12.9 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
09b46df006c6a0ce766ef8f9e053f841017f11b893ce4a8a31952ec24cfa2b84
|
|
BLAKE2b-256 checksum How to use checksums |
84b48bf14f279b1171c3231cdd7672e4603db774f9f0dc11d1eaf10f5a1c1975
|
| Upload date | |
|
Uploaded using Trusted Publishing? What is trusted publishing? |
Yes |
| Uploaded via |
twine/7.0.0 CPython/3.13.14
|
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.
PyPI Publish Attestation
PyPI verified that this artifact, at this checksum, originated from the publisher listed below.
Signed by GitHub Actions, verified by PyPI on Oct 10, 2026.
Transparency log