Fix web login: migrate to /api/v2/auth/web/login + push approval (closes #250)#355
Fix web login: migrate to /api/v2/auth/web/login + push approval (closes #250)#355twannooitmeer wants to merge 3 commits into
Conversation
Trade Republic deprecated the legacy /api/v1/auth/web/login endpoint
(all requests now return HTTP 426 CLIENT_VERSION_OUTDATED regardless of
User-Agent). The current web app uses /api/v2/auth/web/login with:
* x-tr-platform: web
* x-tr-app-version: 15.7.0 (web track, distinct from the Android version)
* x-tr-device-info: base64(JSON) browser fingerprint
* x-aws-waf-token: same value as the aws-waf-token cookie
The v2 flow also no longer issues a numeric code: the user approves the
login via a push notification in the TR mobile app. The web app polls
GET /api/v2/auth/web/login/processes/{processId} until the push is
acknowledged.
Changes:
* pytr/api.py: add TR_WEB_APP_VERSION / TR_WEB_USER_AGENT / TR_WEB_LOGIN_PATH
constants (overridable via env), _get_device_id() persisting a stable
UUID at ~/.pytr/device_id, _build_device_info_header(), _auth_headers()
helper, and await_web_login_approval() polling loop.
* initiate_weblogin() now POSTs to v2 with the new headers, polls for
push approval, and returns 0 to signal the v2 flow.
* complete_weblogin('') is a no-op for v2 (approval already happened).
* pytr/account.py: when initiate_weblogin() returns 0, skip the
code/SMS prompts and call complete_weblogin('') directly.
Refs: pytr-org#250
|
Worked for me! |
|
Thanks for the contribution. As the current web login method still works, would you mind keeping the coding available as well and add an option for selecting the desired one? |
|
@RealCLanger i can look into this sometime soon. For me the "current" sign in method was not working as my TR account only allows for sign in confirmation through in-app notification. I was not able to get TR to text me a code (hence the reworked method). How do you suggest we implement the choice? |
I would suggest adding an option |
Per maintainer feedback on pytr-org#355: the legacy v1 web-login flow still works for many users, so the v2 path should be opt-in rather than the default. - pytr/main.py: add --v2 flag to the shared login args; plumb args.v2 through every login() call site. - pytr/account.py: accept v2 kwarg, pass through as use_v2_login=. The v1 prompt/SMS path is restored byte-for-byte; the v2 push-approval branch only runs when v2=True. - pytr/api.py: gate the v2 endpoint, headers (Origin/Referer, x-tr-*, x-aws-waf-token, macOS Chrome UA), device-id fingerprint, and await_web_login_approval() poll loop behind use_v2_login. Default TradeRepublicApi() behavior is identical to upstream master: - POST /api/v1/auth/web/login with session default headers - resend_weblogin() POSTs to v1 with _default_headers - complete_weblogin(code) POSTs to v1 with no extra headers - initiate_weblogin() returns countdownInSeconds+1 Constants renamed for clarity: TR_V1_LOGIN_PATH, TR_V2_LOGIN_PATH, TR_WEB_USER_AGENT_V2 (env-overridable as before). Usage: pytr login # v1, unchanged pytr login --v2 # v2 push-approval flow
Per maintainer feedback on pytr-org#355: the legacy v1 web-login flow still works for many users, so the v2 path should be opt-in rather than the default. - pytr/main.py: add --v2 flag to the shared login args; plumb args.v2 through every login() call site. - pytr/account.py: accept v2 kwarg, pass through as use_v2_login=. The v1 prompt/SMS path is restored byte-for-byte; the v2 push-approval branch only runs when v2=True. - pytr/api.py: gate the v2 endpoint, headers (Origin/Referer, x-tr-*, x-aws-waf-token, macOS Chrome UA), device-id fingerprint, and await_web_login_approval() poll loop behind use_v2_login. Default TradeRepublicApi() behavior is identical to upstream master: - POST /api/v1/auth/web/login with session default headers - resend_weblogin() POSTs to v1 with _default_headers - complete_weblogin(code) POSTs to v1 with no extra headers - initiate_weblogin() returns countdownInSeconds+1 Constants renamed for clarity: TR_V1_LOGIN_PATH, TR_V2_LOGIN_PATH, TR_WEB_USER_AGENT_V2 (env-overridable as before). Usage: pytr login # v1, unchanged pytr login --v2 # v2 push-approval flow
afa3d21 to
0c4791b
Compare
|
I made the requested change(s)
As I do not have a TR account that can verify the legacy/v1 flow, I did not do an end to end test for that version. Ruff check and pytest were succesful. I also did a test of the --v2 flow end-to-end. This works for me as well. |
Summary
Trade Republic deprecated the legacy
/api/v1/auth/web/loginendpoint. All requests to it currently returnHTTP 426 CLIENT_VERSION_OUTDATEDregardless ofUser-Agent, which makespytr loginunusable on a fresh install (refs #250). (Verified on 2026-05-29; the cutover itself happened at some earlier point.)This PR migrates the auth flow to
/api/v2/auth/web/login, which is whatapp.traderepublic.comcurrently uses.What changed
/api/v2/auth/web/loginrequires four new headersCaptured from the live web app:
The
x-tr-device-infopayload looks like:{ "stableDeviceId": "<64 hex chars>", "model": "Apple Macintosh", "browser": "Chrome", "browserVersion": "148.0.0.0", "os": "Mac OS", "osVersion": "10.15.7", "timezone": "Europe/Amsterdam", "timezoneOffset": -120, "screen": "1800x1169x30", "preferredLanguages": ["en", "en-US"], "numberOfCores": 12, "deviceMemory": 16 }v2 uses push-approval, not a numeric code
The v2 flow no longer issues an SMS/numeric code. Instead the user approves the login via a push notification in the Trade Republic mobile app, and the web client polls
GET /api/v2/auth/web/login/processes/{processId}until the responsestatusindicates approval (and atr_sessioncookie is set).Implementation
pytr/api.pyTR_WEB_APP_VERSION,TR_WEB_USER_AGENT,TR_WEB_LOGIN_PATH, overridable viaPYTR_TR_APP_VERSION/PYTR_TR_USER_AGENTso future TR bumps don't require a code change:_get_device_id()persists a stable 64-hex device id at~/.pytr/device_id._build_device_info_header()constructs the base64-encoded JSON fingerprint._auth_headers()returns all four required headers (WAF token included when known).await_web_login_approval()polls the newprocesses/{processId}endpoint every 2 s with a 180 s timeout. Detects approval via either an explicitstatusfield or the presence of atr_sessioncookie.initiate_weblogin()now POSTs to v2 with the new headers, kicks off the poll, and returns0to signal the v2 flow.complete_weblogin('')is a no-op (approval already happened during initiation).pytr/account.pyinitiate_weblogin()returns0, skip the code/SMS prompts entirely and callcomplete_weblogin('')to persist cookies.countdown > 0case (dead branch in practice, but safe to keep).Tested
Verified locally end-to-end on macOS, Python 3.12: WAF token fetch →
processId→ push approval → session cookie →Logged in.Notes
trustDeviceRegistrationOptions(returned by the process endpoint); that would let users skip the push step on subsequent logins. Happy to follow up in a separate PR.