Skip to content

Login fails with "Invalid callback URL" when app uses universal links and customScheme: 'https' #1508

@helmoski

Description

@helmoski

Checklist

Description

Description

When using customScheme: 'https' on iOS, the Linking event listener registered in NativeWebAuthProvider.authorize() forwards all incoming URLs to resumeWebAuth(), including universal links that are not Auth0 callbacks. This causes login to fail with "Invalid callback URL".

Environment

  • react-native-auth0: 5.4.0
  • iOS 15.8.7 and 26.4 (reproduced on both)
  • Expo (React Native)

Setup

  • Auth0 domain: auth.example.com
  • App has universal link support for app.example.com (applinks:app.example.com in associated domains)
  • Auth configured with customScheme: 'https'

Steps to reproduce

  1. Open the app via a universal link (e.g. https://app.example.com/some-path)
  2. Tap login, which calls authorize() with customScheme: 'https'
  3. ASWebAuthenticationSession opens

Expected: Auth flow completes normally
Actual: Login fails with Auth0.WebAuthError: Invalid callback URL: https://app.example.com/some-path

This also reproduces without a universal link open. If Auth0's Universal Login navigates to the app's initiate_login_uri (e.g. https://app.example.com/main?iss=https://auth.example.com/), iOS delivers it as a universal link, the Linking listener catches it, and resumeWebAuth() rejects it.

Root cause

In [NativeWebAuthProvider.js](https://github.com/auth0/react-native-auth0/blob/master/src/platforms/native/adapters/NativeWebAuthProvider.ts#L34-L38), the Linking listener forwards every URL to resumeWebAuth() without checking whether the URL is actually on the Auth0 domain:

linkSubscription = Linking.addEventListener('url', async event => {
    linkSubscription?.remove();
    await this.bridge.resumeWebAuth(event.url);
});

When the app has universal links on a domain other than the Auth0 domain, iOS can deliver those URLs to the app during the auth flow. The listener treats them as auth callbacks, resumeWebAuth() fails validation, and the error propagates to the authorize() promise.

Workaround

We're patching the listener to filter by domain:

linkSubscription = Linking.addEventListener('url', async event => {
    try {
        const url = new URL(event.url);
        if (url.hostname !== this.domain) return;
    } catch {
        return;
    }
    linkSubscription?.remove();
    await this.bridge.resumeWebAuth(event.url);
});

Suggested fix

The SDK should check that the incoming URL's hostname matches this.domain before forwarding to resumeWebAuth(). This would prevent universal links on other domains from being incorrectly processed as auth callbacks.

Reproduction

  1. Open the app via a universal link (e.g. https://app.example.com/some-path)
  2. Tap login, which calls authorize() with customScheme: 'https'
  3. ASWebAuthenticationSession opens

Expected: Auth flow completes normally
Actual: Login fails with Auth0.WebAuthError: Invalid callback URL: https://app.example.com/some-path

This also reproduces without a universal link open. If Auth0's Universal Login navigates to the app's initiate_login_uri (e.g. https://app.example.com/main?iss=https://auth.example.com/), iOS delivers it as a universal link, the Linking listener catches it, and resumeWebAuth() rejects it.

Additional context

No response

react-native-auth0 version

5.4.0

React Native version

0.81.4

Expo version

54.0.10

Platform

iOS

Platform version(s)

15.8.7 and 26.4 (reproduced on both)

Metadata

Metadata

Assignees

No one assigned

    Labels

    bugThis points to a verified bug in the code

    Type

    No type

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions