Auth.xjtu
auth-xjtu automates authentication for services protected by Xi'an
Jiaotong University's unified identity gateway.
The package deliberately separates two features:
- Entry discovery finds the authentication entry for pages that expose a login link without redirecting automatically.
- Login starts from a redirecting business URL or a known authentication entry and completes the OAuth/CAS chain.
This separation keeps the authentication flow predictable while allowing site-specific discovery rules to evolve independently.
Requirements
- Python 3.9+
- pip
Installation
Install the published package:
python -m pip install auth-xjtu
Install a development checkout with test dependencies:
python -m pip install -e ".[dev]"
Login from a business URL
Authenticator.login() follows the application's redirects to the XJTU
gateway, performs the current MFA preflight, submits the password form, and
follows the authenticated redirect back to the application:
from auth_xjtu import Authenticator, LoginStatus
auth = Authenticator(
username="your_netid",
password="your_password",
)
status, message = auth.login("https://gmis.xjtu.edu.cn/pyxx/")
if status is LoginStatus.SUCCESS:
session = auth.get_session()
print("Login successful")
else:
print(f"Login failed: {message}")
The argument may also be a known authentication entry URL. login() handles
HTTP redirects but does not scan arbitrary application HTML for login links.
Discover an entry, then log in
When an application page does not redirect and only exposes a login link, use
an EntryFinder. Sharing the same session preserves cookies collected during
discovery:
from auth_xjtu import Authenticator, EntryFinder, LoginStatus
app_url = "https://portal.example.edu/application"
auth = Authenticator("your_netid", "your_password")
finder = EntryFinder(session=auth.get_session())
entry_url = finder.find(app_url)
status, message = auth.login(entry_url)
if status is LoginStatus.SUCCESS:
response = auth.get_session().get(app_url, timeout=10)
response.raise_for_status()
For discovery without a shared session, the convenience function
find_entry(app_url) is also available.
Entry discovery strategies
EntryFinder resolves an entry in this order:
- a hard-coded URL in a matching
SiteRule; - a redirect to a different host or a login-like URL;
- CSS selectors configured for the matching site;
- generic password-form and scored login-link detection.
Per-site configuration
A site can use a hard-coded path:
from auth_xjtu import EntryFinder, SiteRule
rules = (
SiteRule(
hostname="portal.example.edu",
fixed_entry_url="/sso/start",
),
)
entry_url = EntryFinder(rules=rules).find(
"https://portal.example.edu/application"
)
Or a page-specific CSS selector:
rules = (
SiteRule(
hostname="library.example.edu",
selectors=(
"a.campus-sso",
"form#unified-login",
),
keywords=("campus access",),
),
)
Rules passed to EntryFinder replace the built-in rule set. Built-in XJTU
rules live in auth_xjtu/entry_discovery/rules.py; this is the intended place
for small, reviewed site-specific hard-coded entries.
If no generic strategy or rule succeeds, discovery raises
EntryDiscoveryError instead of silently treating the application URL as a
login entry.
Login status codes
Authenticator.login() returns (LoginStatus, message). LoginStatus is an
IntEnum, so it remains compatible with existing integer comparisons.
| Value | Enum | Meaning |
|---|---|---|
| 0 | SUCCESS |
The session is authenticated. |
| 1 | ENTRY_REDIRECT_FAILED |
The entry URL could not reach the login page. |
| 2 | PAYLOAD_CONSTRUCTION_FAILED |
Tokens, public key, or encrypted payload could not be created. |
| 3 | POST_LOGIN_REDIRECT_FAILED |
The redirect after credential submission failed. |
| 4 | LOGIN_SUBMISSION_FAILED |
Submission or credential preflight was rejected. |
| 5 | MFA_REQUIRED |
The account requires an interactive second factor. |
Project layout
src/auth_xjtu/
├── entry_discovery/
│ ├── finder.py # generic discovery strategies
│ └── rules.py # per-site configuration and hard-coded entries
├── login/
│ ├── authenticator.py
│ ├── payload.py
│ └── redirects.py
├── authenticator.py # backward-compatible import
├── exceptions.py
└── __init__.py
tests/
├── unit/ # isolated tests without network or credentials
└── integration/ # live XJTU tests
examples/
└── course_selection/ # read-only GMIS course-list query
Examples
The GMIS course-list example logs in from the GMIS application URL, queries every result page with a current cache-busting timestamp, and can filter results by course name. It is read-only and does not submit course selections.
Migrating from 0.2.x
Version 0.3.0 contains intentional breaking changes:
Authenticator.login()now takesstart_url; replace keyword calls such aslogin(dest_app_url=...)withlogin(start_url=...).- Imports from the undocumented
auth_xjtu.coremodules are no longer supported. UseAuthenticator,EntryFinder,SiteRule, andfind_entryfrom the top-level package. - The first value returned by
login()is nowLoginStatus. It is anIntEnum, so existing comparisons such asstatus == 0continue to work. - Entry discovery and login are separate operations. Use
EntryFinderonly when a page exposes a login link without redirecting automatically.
Testing
Run the complete local suite:
python -m pytest
Live integration cases skip automatically unless both credentials are available. To run them in PowerShell:
$env:TEST_USERNAME = "your_netid"
$env:TEST_PASSWORD = "your_password"
python -m pytest -m integration
Run only isolated tests:
python -m pytest -m "not integration"
License and disclaimer
This project is licensed under the MIT License.
It is an independent project and is not affiliated with, authorized by, or endorsed by Xi'an Jiaotong University. Use it responsibly and do not commit credentials, cookies, or authentication logs to source control.
Metadata
Release files for auth-xjtu 0.3.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 | |
|---|---|---|---|
| auth_xjtu-0.3.0.tar.gz | 14.1 kB | Details |
Built distribution (wheel)
| File | Interpreter | ABI | Platform | Reset |
|---|---|---|---|---|
| auth_xjtu-0.3.0-py3-none-any.whl | Python 3 | none | any | Details |
Total release size: 28.4 kB
Release files / auth_xjtu-0.3.0.tar.gz
| Download URL | auth_xjtu-0.3.0.tar.gz |
|---|---|
| Size | 14.1 kB |
| Tags | Source |
|
SHA-256 checksum How to use checksums |
7648b531c9b0f9b883f8b12d62097e40a1b4dbd891ce15cd618359c6ff02af60
|
|
BLAKE2b-256 checksum How to use checksums |
7d9dc7cf0a0e13045752108c78c5420efb505648f3a76d6b46398fe1f9cedc53
|
| 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 Jul 30, 2026.
Transparency logRelease files / auth_xjtu-0.3.0-py3-none-any.whl
| Download URL | auth_xjtu-0.3.0-py3-none-any.whl |
|---|---|
| Size | 14.3 kB |
| Tags | Python 3 |
|
SHA-256 checksum How to use checksums |
f62af8d88d9fe76beed4f533314af140abe143388d884642a26173485f89e79b
|
|
BLAKE2b-256 checksum How to use checksums |
abdb1f731d63133da37194beae54f253fe2ed1a58695cad607291845bb8ffb0f
|
| 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 Jul 30, 2026.
Transparency log