Describe the bug
Logging in via a Custom OAuth provider (self-hosted Keycloak, standard OIDC) on the mobile app succeeds completely on the server side every single time, but the app never leaves the login screen — it silently re-initiates the entire OAuth handshake every ~15-20 seconds in an infinite loop. Web login against the exact same Custom OAuth provider/server works correctly on the first try.
Environment
- App version: 4.75.0
- Platform: iOS (native app, "RC Mobile")
- Server version: Rocket.Chat 8.6.1
- Auth method: Custom OAuth pointed at a self-hosted Keycloak instance (standard OIDC —
/auth, /token, /userinfo endpoints)
- Login Style: Tried both
Popup and Redirect in Admin → OAuth → (custom provider) — no change in mobile behavior either way
- 2FA: Disabled account-wide for testing — no change in behavior
Steps to reproduce
- Configure a Custom OAuth provider against a standard OIDC/Keycloak server.
- Open the RC Mobile app (iOS), tap the custom OAuth login button.
- Authenticate against the identity provider (Keycloak login page).
- Observe: the auth popup/browser session closes with no visible error, but the app stays on the login screen.
- Wait ~15-20 seconds without doing anything — a brand new OAuth handshake (fresh authorization code) starts automatically, completes silently (no re-prompt, since the IdP still has an active session), and the app still doesn't log in. This repeats indefinitely.
What I verified server-side (ruling out server/IdP/proxy causes)
Traced the full request chain end-to-end across three independent login attempts, all with identical results:
GET https://sso.example.com/realms/<realm>/protocol/openid-connect/auth?client_id=<id>&redirect_uri=https%3A%2F%2Fchat.example.com%2F_oauth%2F<provider>&response_type=code&state=<state>&scope=openid
→ Keycloak returns 302 immediately (existing IdP session, no interactive prompt needed after the first attempt).
- RocketChat's server exchanges the code:
POST https://sso.example.com/realms/<realm>/protocol/openid-connect/token → succeeds.
GET https://sso.example.com/realms/<realm>/protocol/openid-connect/userinfo → succeeds, returns the full identity (email, name, sub, etc.).
- RocketChat's
CustomOAuth logs "Identity response" with the correct user identity.
- RocketChat's login-validation callbacks fire cleanly, in order, with no exception:
afterValidateNewOAuthUser → onValidateLogin → afterValidateLogin.
- The app's own REST call —
POST /api/v1/login with userAgent: RC Mobile; ios ... and the OAuth credential in the body — returns HTTP 200.
This full sequence (steps 1-6) completed successfully three times in a row across separate login attempts, each with its own fresh authorization code, each getting a clean 200 on /api/v1/login. There is no error, rejection, or exception anywhere in the server-side chain on any attempt. Despite that, the app immediately restarts the same sequence again ~15-20 seconds later, and never transitions out of the login screen.
Decoded state parameter from the request (values redacted, structure intact):
{
"loginStyle": "redirect",
"credentialToken": "<redacted>",
"isCordova": true,
"redirectUrl": "rocketchat://auth"
}
Note loginStyle is redirect regardless of what the server's Admin → OAuth → Login Style setting is configured to (tried both Popup and Redirect server-side with no change in this value or in mobile behavior) — the mobile app appears to always request redirect style for Custom OAuth, independent of that server setting.
Possibly related
This looks like it could be the same underlying class of bug as #7333 (fixed by #7340: "don't cancel login saga on 2s timeout") — a race/cancellation between successful OAuth completion and the UI state advancing past the login screen — but resurfacing in the newer native-browser-session Custom OAuth flow introduced by #6987 / #7419 (which moved Custom OAuth to ASWebAuthenticationSession/Chrome Custom Tabs and explicitly forced redirect-style login, "activating existing (previously dormant) saga logic"). #7340's fix predates that rework, so it may not cover this newer code path.
Expected behavior
After a successful OAuth completion (confirmed server-side), the app should recognize the returned session and transition to the logged-in state, rather than silently discarding it and re-initiating the OAuth flow.
Additional notes
- Uninstalling and reinstalling the app does not change the behavior.
- Force-quitting and reopening the app does not change the behavior.
- Happy to provide more detail (redacted server logs, HAR-equivalent request traces) on request — omitted here since they contain internal hostnames/identifiers.
Describe the bug
Logging in via a Custom OAuth provider (self-hosted Keycloak, standard OIDC) on the mobile app succeeds completely on the server side every single time, but the app never leaves the login screen — it silently re-initiates the entire OAuth handshake every ~15-20 seconds in an infinite loop. Web login against the exact same Custom OAuth provider/server works correctly on the first try.
Environment
/auth,/token,/userinfoendpoints)PopupandRedirectin Admin → OAuth → (custom provider) — no change in mobile behavior either waySteps to reproduce
What I verified server-side (ruling out server/IdP/proxy causes)
Traced the full request chain end-to-end across three independent login attempts, all with identical results:
GET https://sso.example.com/realms/<realm>/protocol/openid-connect/auth?client_id=<id>&redirect_uri=https%3A%2F%2Fchat.example.com%2F_oauth%2F<provider>&response_type=code&state=<state>&scope=openid→ Keycloak returns
302immediately (existing IdP session, no interactive prompt needed after the first attempt).POST https://sso.example.com/realms/<realm>/protocol/openid-connect/token→ succeeds.GET https://sso.example.com/realms/<realm>/protocol/openid-connect/userinfo→ succeeds, returns the full identity (email, name, sub, etc.).CustomOAuthlogs"Identity response"with the correct user identity.afterValidateNewOAuthUser→onValidateLogin→afterValidateLogin.POST /api/v1/loginwithuserAgent: RC Mobile; ios ...and the OAuth credential in the body — returns HTTP 200.This full sequence (steps 1-6) completed successfully three times in a row across separate login attempts, each with its own fresh authorization code, each getting a clean
200on/api/v1/login. There is no error, rejection, or exception anywhere in the server-side chain on any attempt. Despite that, the app immediately restarts the same sequence again ~15-20 seconds later, and never transitions out of the login screen.Decoded
stateparameter from the request (values redacted, structure intact):{ "loginStyle": "redirect", "credentialToken": "<redacted>", "isCordova": true, "redirectUrl": "rocketchat://auth" }Note
loginStyleisredirectregardless of what the server's Admin → OAuth → Login Style setting is configured to (tried bothPopupandRedirectserver-side with no change in this value or in mobile behavior) — the mobile app appears to always requestredirectstyle for Custom OAuth, independent of that server setting.Possibly related
This looks like it could be the same underlying class of bug as #7333 (fixed by #7340: "don't cancel login saga on 2s timeout") — a race/cancellation between successful OAuth completion and the UI state advancing past the login screen — but resurfacing in the newer native-browser-session Custom OAuth flow introduced by #6987 / #7419 (which moved Custom OAuth to
ASWebAuthenticationSession/Chrome Custom Tabs and explicitly forced redirect-style login, "activating existing (previously dormant) saga logic"). #7340's fix predates that rework, so it may not cover this newer code path.Expected behavior
After a successful OAuth completion (confirmed server-side), the app should recognize the returned session and transition to the logged-in state, rather than silently discarding it and re-initiating the OAuth flow.
Additional notes