Migrate WeConnect auth from deprecated BFF endpoints to direct OIDC#237
Migrate WeConnect auth from deprecated BFF endpoints to direct OIDC#237oldgitdaddy wants to merge 4 commits into
Conversation
VW shut down the legacy BFF endpoints (emea.bff.cariad.digital/user-login/*) at the Azure WAF layer, returning 403 for all clients. This broke WeConnect authentication. Replace the deprecated BFF-based auth flow with direct OIDC endpoints, following the same pattern already used by MyCupraSession: - Authorization: direct OIDC identity.vwgroup.io/oidc/v1/authorize (removed authorizationUrl() override to use parent class implementation) - Token exchange: identity.vwgroup.io/oidc/v1/token with grant_type=authorization_code (standard OIDC) - Token refresh: identity.vwgroup.io/oidc/v1/token (standard OIDC) - Updated User-Agent to Volkswagen/3.61.0-android/14 to match latest APK Fixes tillsteinbach#155 in CarConnectivity
|
Is it working for you? I don't get past the token fetching from /oidc/v1/token. The post request gives me a 401 |
The direct POST to identity.vwgroup.io/oidc/v1/token with grant_type=authorization_code returns 401 access_denied because Auth0 binds the authorization code to the CARIAD BFF as the authorized exchanger. Switch to the OIDC hybrid flow (response_type=code id_token token) where access_token and id_token are delivered directly in the callback URL — no server-side token exchange is needed. The parent class OpenIDSession.authorizationUrl() already uses this response type so the authorization URL was already correct. Key changes: - fetchTokens(): extract tokens from callback directly, skip the broken token exchange POST to /oidc/v1/token - refresh(): trigger full re-login since hybrid flow issues no refresh_token (~2h access token lifetime) - refreshTokens(): simplified to delegate to login() for graceful fallback - _handle_new_auth_flow(): add action=default to login form POST (required by Auth0 Universal Login, per PR #333 finding) - Scope: add offline_access - Remove unused imports (requests, InsecureTransportError, etc.) Aligned with robinostlund/volkswagencarnet#333. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
At least for my small test program it works now. With the additional commit I can connect and query vehicles. token refresh not tried.... |
|
ok refresh also works. I am very curious how VW is thinking about how to handle this on the long run. The refresh now is much more expensive. Let´s see if it pays off to cut off frontend APIs and to force everyone to do a full new login. VW good luck! 🥇 |
The OIDC hybrid flow has no refresh_token, but the websession preserves Auth0 SSO cookies from the initial login. refresh() now: 1. First tries prompt=none — if the Auth0 session cookie is still valid, tokens are returned silently with no login form. 2. Falls back to full credential-based login if the session expired. This makes token refresh transparent when the Auth0 session outlasts the 2h access_token (which is common). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The --refresh test now: 1. Captures WeConnectSession log messages during refresh() 2. Simulates token expiry (expires_at=0) to force the refresh path 3. Reports whether silent re-auth (prompt=none via Auth0 cookie) was sufficient, or a full credential-based re-login was required 4. Shows the failure reason if silent auth fell back Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
|
added test script, which we of course can remove again. With this we can trigger refresh and force token expiry. |
|
Installed branch fix/vw-oidc-migration Region: Italy Previous error: Current error: No OTP prompt is shown. |
Problem
VW shut down the legacy BFF (Backend-for-Frontend) auth endpoints at the Azure WAF layer. The endpoints
emea.bff.cariad.digital/user-login/v1/authorizeandemea.bff.cariad.digital/user-login/login/v1now return 403 Forbidden for all clients. This broke WeConnect authentication for all Volkswagen accounts.Fixes: tillsteinbach/CarConnectivity#155
Solution — Updated: OIDC Hybrid Flow
Update (2026-05-31): The original approach (direct OIDC authorization code flow) didn't work — POSTing to
identity.vwgroup.io/oidc/v1/tokenwithgrant_type=authorization_codereturned 401 access_denied. Per feedback from @tillsteinbach, the fix wasn't working because Auth0 binds the authorization code to the CARIAD BFF as the authorized exchanger — only CARIAD BFF can exchange codes for tokens.This PR has been updated to align with the working solution in robinostlund/volkswagencarnet#333 by @s1gmund80, which uses the OIDC hybrid flow (
response_type=code id_token token).Why Hybrid Flow Works
With the hybrid flow, Auth0 delivers
access_tokenandid_tokendirectly in the callback URL (URL fragment). No separate server-side token exchange is needed — we skip thePOST /oidc/v1/tokenentirely, avoiding the 401 that the CARIAD BFF token exchange would return.The parent class
OpenIDSession.authorizationUrl()already hardcodesresponse_type='code id_token token', so the authorization URL was already correct. The issue was thatfetchTokens()was still attempting the broken token exchange.Trade-off: No Refresh Token
The OIDC hybrid flow does not return a
refresh_tokenfor security reasons. When theaccess_tokenexpires (~2 hours), a full re-login is required. This matches the behavior in robinostlund/volkswagencarnet#333. All alternative paths were tested and failed:POST /auth/v1/idk/oidc/token(inner opaque code, Bearer JWT)400 Bad RequestPOST /auth/v1/idk/oidc/token(inner code, no Bearer)400 Bad RequestPOST /auth/v1/idk/oidc/token(JWT ascode)400 invalid assertion headersPOST identity.vwgroup.io/oidc/v1/token(direct Auth0, no PKCE)401 access_deniedPOST identity.vwgroup.io/oidc/v1/token(direct Auth0, with PKCE)401 access_deniedPOST /user-login/login/v1(VW-specific endpoint)403 ForbiddenChanges
weconnect/auth/vw_web_session.py3.61.0(old UA caused 401 errors)action=defaultto login form POST body in_handle_new_auth_flow(). This is required by Auth0 Universal Login to distinguish credential submission from other form actions — without it the login POST can be silently ignored (per fix(auth): switch to OIDC hybrid flow after CARIAD BFF token exchange brake robinostlund/volkswagencarnet#333 finding).weconnect/auth/we_connect_session.pyoffline_access— required for refresh token issuance (per PR #333)fetchTokens()parseFromFragment(). No longer POSTs to/oidc/v1/token— skips the broken token exchange entirely.refresh()refresh_tokenrefreshTokens()login()— graceful fallback for the no-refresh-token caseVolkswagen/3.61.0-android/14Key Design Decisions
client_secret: WeConnect is a public OIDC client (unlike MyCupra)access_tokenandid_tokenfrom the Auth0 callback URL are the session tokens — no transformation neededrefreshTokensis preserved in case VW restores a usable code exchange path on the BFF — it will work automatically if refresh tokens become available againauthorizationUrl()override: Parent class already constructs the correct OIDC URL withresponse_type=code id_token tokenReferences
Testing
response_type=code id_token token🤖 Generated with Claude Code