diff --git a/README.md b/README.md index ad96d62b..5e3634d0 100644 --- a/README.md +++ b/README.md @@ -215,7 +215,9 @@ To use the SDK with Expo, configure the app at build time by providing the `doma | API | Description | | ------------ | ---------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------------- | | domain | Mandatory: Provide the Auth0 domain that can be found at the [Application Settings](https://manage.auth0.com/#/applications) | -| customScheme | Optional: Custom scheme to build the callback URL with. The value provided here should be passed to the `customScheme` option parameter of the `authorize` and `clearSession` methods. The custom scheme should be a unique, all lowercase value with no special characters. | +| customScheme | Optional: Custom scheme to build the callback URL with. The value provided here should be passed to the `customScheme` option parameter of the `authorize` and `clearSession` methods. The custom scheme should be a unique, all lowercase value with no special characters. To use Android App Links, set this value to `"https"`. | + +**Note:** When using `customScheme: "https"` for Android App Links, the plugin will automatically add `android:autoVerify="true"` to the intent-filter in your Android manifest to enable automatic verification of App Links. Now you can run the application using `expo run:android` or `expo run:ios`. diff --git a/src/plugin/__tests__/withAuth0-test.ts b/src/plugin/__tests__/withAuth0-test.ts index 9863f7df..88da9a78 100644 --- a/src/plugin/__tests__/withAuth0-test.ts +++ b/src/plugin/__tests__/withAuth0-test.ts @@ -212,6 +212,101 @@ describe(addAndroidAuth0Manifest, () => { expect(dataElement?.$['android:scheme']).toBe('com.custom.scheme'); expect(dataElement?.$['android:host']).toBe('sample.auth0.com'); }); + + it(`should add android:autoVerify="true" when customScheme is https`, () => { + const config = getConfig(); + const result = addAndroidAuth0Manifest( + [ + { + domain: 'sample.auth0.com', + customScheme: 'https', + }, + ], + config, + 'com.auth0.testapp' + ); + + // Access the RedirectActivity to check if autoVerify is correctly added + const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( + result.modResults + ); + const redirectActivity = mainApplication.activity?.find( + (activity) => + activity.$['android:name'] === + 'com.auth0.android.provider.RedirectActivity' + ); + + const intentFilter = redirectActivity?.['intent-filter']?.[0]; + + expect(intentFilter?.$).toBeDefined(); + expect(intentFilter?.$?.['android:autoVerify']).toBe('true'); + + const dataElement = intentFilter?.data?.[0]; + expect(dataElement?.$['android:scheme']).toBe('https'); + expect(dataElement?.$['android:host']).toBe('sample.auth0.com'); + }); + + it(`should add android:autoVerify="true" when customScheme is http`, () => { + const config = getConfig(); + const result = addAndroidAuth0Manifest( + [ + { + domain: 'sample.auth0.com', + customScheme: 'http', + }, + ], + config, + 'com.auth0.testapp' + ); + + // Access the RedirectActivity to check if autoVerify is correctly added + const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( + result.modResults + ); + const redirectActivity = mainApplication.activity?.find( + (activity) => + activity.$['android:name'] === + 'com.auth0.android.provider.RedirectActivity' + ); + + const intentFilter = redirectActivity?.['intent-filter']?.[0]; + + expect(intentFilter?.$).toBeDefined(); + expect(intentFilter?.$?.['android:autoVerify']).toBe('true'); + + const dataElement = intentFilter?.data?.[0]; + expect(dataElement?.$['android:scheme']).toBe('http'); + expect(dataElement?.$['android:host']).toBe('sample.auth0.com'); + }); + + it(`should not add android:autoVerify when customScheme is not http/https`, () => { + const config = getConfig(); + const result = addAndroidAuth0Manifest( + [ + { + domain: 'sample.auth0.com', + customScheme: 'com.custom.scheme', + }, + ], + config, + 'com.auth0.testapp' + ); + + // Access the RedirectActivity + const mainApplication = AndroidConfig.Manifest.getMainApplicationOrThrow( + result.modResults + ); + const redirectActivity = mainApplication.activity?.find( + (activity) => + activity.$['android:name'] === + 'com.auth0.android.provider.RedirectActivity' + ); + + const intentFilter = redirectActivity?.['intent-filter']?.[0]; + + // autoVerify should not be present for non-http(s) schemes + expect(intentFilter?.$?.['android:autoVerify']).toBeUndefined(); + }); }); describe(addIOSAuth0ConfigInInfoPList, () => { diff --git a/src/plugin/withAuth0.ts b/src/plugin/withAuth0.ts index 67ce5c5d..08c06b38 100644 --- a/src/plugin/withAuth0.ts +++ b/src/plugin/withAuth0.ts @@ -30,9 +30,15 @@ export const addAndroidAuth0Manifest = ( if (auth0Configs.length === 0) { throw new Error(`No auth0 domain specified in expo config`); } + // Check if any config uses https/http scheme for App Links + const hasAppLinks = auth0Configs.some( + (config) => + config.customScheme === 'https' || config.customScheme === 'http' + ); const intentFilterContent = [ { + ...(hasAppLinks && { $: { 'android:autoVerify': 'true' as AndroidConfig.Manifest.StringBoolean } }), action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }], category: [ { $: { 'android:name': 'android.intent.category.DEFAULT' } }, @@ -61,17 +67,26 @@ export const addAndroidAuth0Manifest = ( 'tools:node': 'replace', 'android:exported': 'true', }, - 'intent-filter': intentFilterContent, + 'intent-filter': intentFilterContent as AndroidConfig.Manifest.ManifestIntentFilter[], }; mainApplication.activity = mainApplication.activity || []; mainApplication.activity.push(redirectActivity); } - + redirectActivity['intent-filter'] = - redirectActivity['intent-filter'] || intentFilterContent; + redirectActivity['intent-filter'] || intentFilterContent as AndroidConfig.Manifest.ManifestIntentFilter[]; const intentFilter = redirectActivity['intent-filter'][0] || {}; + if (!intentFilter) { + throw new Error('Failed to create intent filter'); + } intentFilter.data = intentFilter.data || []; + // Add android:autoVerify="true" for App Links + if (hasAppLinks) { + intentFilter.$ = intentFilter.$ || {}; + intentFilter.$['android:autoVerify'] = 'true' as AndroidConfig.Manifest.StringBoolean; + } + // Add data elements for each auth0Config auth0Configs.forEach((config) => { if (config.domain == null) {