RocketChat / RocketChat/Rocket.Chat.ReactNative
bug: Custom OAuth login succeeds server-side every time but app loops back to login screen (iOS, 4.75.0)
Nobody has claimed this yet.
- Dominant language
- TypeScript
- Stars
- 2.4k
- Forks
- 1.5k
- Avg merge
- 1d 18h
- Merged PRs (30d)
- 90
Description
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,/userinfoendpoints) - Login Style: Tried both
PopupandRedirectin 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 returns302immediately (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
CustomOAuthlogs"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/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 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.
Contributor guide
First steps
- Read the whole issue, then the project's contributing guide.
- Comment on the issue to say you are picking it up — it saves two people doing the same work.
- Fork the repository and make your change on a branch.
- Open a pull request that references the issue number.
Research direction
Start by tracing the Custom OAuth mobile flow through ASWebAuthenticationSession/Chrome Custom Tabs and the login saga, comparing it with the cancellation fix in #7340 and the rework in #6987/#7419. Use the reported successful POST /api/v1/login as the boundary to inspect; done means a successful OAuth response advances the app past the login screen without restarting the handshake.
Written by the indexing model from the issue text.
Assessment
- Tech stack
- ios, react-native, typescript
- Domain
- authentication, mobile
- Issue type
- Bug
- Difficulty
- 4/5
- Estimated time
- 3-5 days
- Activity status
- Quiet
- Clarity
- Mostly clear
- Newbie friendliness
- 52/100