Canvas LMS — IDP Auto Token Refresh
Automate Canvas API token refresh by replaying institutional IDP login (CAS/SAML) with RSA-encrypted credentials.
How It Works
Entry URL → CAS/IDP (lck + authChainCode) → RSA encrypt password → authExecute
→ loginToken (JWT) → authnEngine → SSO ticket → Canvas session → create API token
→ (optional) delete old tokens by purpose → output NEW_TOKEN=xxx
The script handles the full IDP chain: cookie juggling, JavaScript-redirect handoff, CSRF extraction, and Canvas API token CRUD.
Quick Start
1. Install Dependencies
cd scripts/
python3 -m venv .venv
.venv/bin/pip install -r requirements.txt
Dependencies: requests, beautifulsoup4, pycryptodome, python-dotenv.
2. Configure Credentials
Copy .env.example to .env and fill in:
cp scripts/.env.example scripts/.env
Required fields:
ELEARNING_USERNAME — student/staff ID
ELEARNING_PASSWORD — password
Optional (defaults to Fudan eLearning):
ELEARNING_ENTRY_URL — CAS entry point
ELEARNING_IDP_BASE_URL — IDP base URL
ELEARNING_ENTITY_ID — SP entity ID
ELEARNING_TOKEN_PURPOSE — label for created tokens (default: "OpenClaw Auto Refresh Token")
ELEARNING_CLEANUP_OLD_TOKENS — auto-delete old tokens with same purpose (default: false)
3. Run
# Full flow: login → create token → cleanup old tokens
cd scripts && .venv/bin/python elearning_login.py --cleanup-old-tokens
# Debug mode (verbose HTTP logs + save debug artifacts)
cd scripts && .venv/bin/python elearning_login.py --debug --cleanup-old-tokens
# Dry-run: only test up to public key fetch (no login)
cd scripts && .venv/bin/python elearning_login.py --dry-run --debug
On success, the script prints NEW_TOKEN=<token_value> to stdout.
4. Integrate with Token Lazy-Loading
For agents that call Canvas API, implement a lazy-refresh pattern:
- Read saved token from file
- Validate with
GET /api/v1/users/self — check HTTP status only (don't read body)
- If 200 → use token
- If 401 → run refresh script, capture
NEW_TOKEN=, save to file, retry
- If refresh also fails → alert user (password changed, CAPTCHA triggered, etc.)
Key rules:
- Validate once per session, not on every API call
- Only re-validate on explicit 401 responses
- Don't log or expose raw token values
- The refresh takes ~2-3 seconds, run silently
Security
.env contains credentials — never commit to git (.env is gitignored by default)
debug_output/ may contain session cookies and encrypted payloads — sanitize before sharing
- The script uses PKCS1v1_5 RSA encryption for password transport (matching the IDP's JS frontend)
- Tokens are created with a purpose label for lifecycle management
- Old tokens with matching purpose can be auto-deleted to avoid accumulation
Known Limitations
| Limitation | Impact | Mitigation |
|---|
| CAPTCHA/rate-limiting | Script cannot solve human verification | Alert user, manual intervention needed |
| MFA/2FA | Requires interactive flow not supported by requests | Not supported; alert user |
| IDP interface changes | JSON field names or HTML structure may change | Debug output (--debug) captures raw responses for diagnosis |
| Public key format changes | Script supports PEM, Base64-DER, modulus+exponent | If new format appears, extend parse_public_key_payload() |
Troubleshooting
Run with --debug to save artifacts to debug_output/:
| Symptom | Check |
|---|
未能从入口响应中解析到 lck | debug_output/entry_response.html — look for context_CAS_... |
queryAuthMethods 未找到 userAndPwd | debug_output/query_auth_methods.json — check moduleCode field |
未从 authExecute 提取到 loginToken | debug_output/auth_execute.json — check code/message |
未拿到关键会话 Cookie | Check auth_execute.json for errors, authn_engine_response.html for CAPTCHA |
| Token API returns 401/422 | debug_output/cookies.txt for _csrf_token / _normandy_session |
| Cleanup fails | debug_output/cleanup_summary.json for failed entries |
Adapting to Other Institutions
The IDP flow is based on a common CAS/SAML pattern used by many Chinese universities. To adapt:
- Change URLs in
.env: ELEARNING_ENTRY_URL, ELEARNING_IDP_BASE_URL, ELEARNING_ENTITY_ID
- Test with
--dry-run first to verify lck/authChainCode extraction
- If IDP uses different auth methods: modify
pick_auth_chain_code() in auth_session.py
- If password encryption differs: modify
encrypt_password_rsa() and parse_public_key_payload()
Tested with: Fudan University (id.fudan.edu.cn → elearning.fudan.edu.cn).