Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
21 commits
Select commit Hold shift + click to select a range
04d21ba
feat(expo): add hosted auth hook
mikepitre Jun 22, 2026
b857636
chore(expo): add hosted auth changeset
mikepitre Jun 22, 2026
9341d6b
fix(expo): activate hosted auth session
mikepitre Jun 22, 2026
1f63334
fix(expo): redeem hosted auth with code verifier
mikepitre Jun 23, 2026
674196f
fix(expo): rename hosted auth option to mode
mikepitre Jun 24, 2026
1c9afea
fix(expo): satisfy hosted auth lint rules
mikepitre Jun 24, 2026
390aa68
fix(expo): publish hosted auth verifier dependencies
mikepitre Jun 24, 2026
b1b6e2d
fix(expo): address hosted auth review feedback
mikepitre Jun 25, 2026
8ccb80a
fix(expo): harden hosted auth session handoff
mikepitre Jun 25, 2026
23e998a
fix(clerk-js): type hosted auth verifier body
mikepitre Jun 25, 2026
1c1a4e0
test(js): harden hosted auth verifier handling
mikepitre Jun 25, 2026
8870565
fix(clerk-js): trim hosted auth reload path
mikepitre Jun 25, 2026
0794a8b
fix(expo): stabilize hosted auth CI
mikepitre Jun 25, 2026
bb8bbc6
fix(expo): scope hosted auth completion to expo
mikepitre Jun 25, 2026
84bde48
fix(expo): move hosted auth to subpath export
mikepitre Jun 25, 2026
e64412d
fix(expo): align hosted auth callbacks with native apps
mikepitre Jul 9, 2026
8dfae53
fix(expo): harden hosted auth completion
mikepitre Jul 10, 2026
ed2f301
fix(expo): tighten hosted auth flow
mikepitre Jul 10, 2026
c83f85f
fix(expo): complete hosted auth platform wiring
mikepitre Jul 10, 2026
322ca1a
fix(expo): harden hosted auth flow
mikepitre Jul 13, 2026
6040c7b
chore(expo): release hosted auth as a minor version
mikepitre Jul 13, 2026
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions .changeset/hosted-auth-expo.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
'@clerk/expo': minor
---

Add `@clerk/expo/hosted-auth` for signing in or signing up through Account Portal from native Expo apps.
36 changes: 36 additions & 0 deletions packages/expo/app.plugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -9,10 +9,12 @@
* Native modules and views are registered via Expo Modules autolinking.
*/
const {
AndroidConfig,
withXcodeProject,
withDangerousMod,
withInfoPlist,
withAppBuildGradle,
withAndroidManifest,
withEntitlementsPlist,
} = require('@expo/config-plugins');
const path = require('path');
Expand All @@ -21,6 +23,30 @@ const packageJson = require('./package.json');

const CLERK_MIN_IOS_VERSION = '17.0';

const addHostedAuthIntentFilter = (mainActivity, packageName) => {
const callbackHost = `${packageName}.callback`;
const intentFilters = mainActivity['intent-filter'] || [];
const callbackIsRegistered = intentFilters.some(intentFilter =>
intentFilter.data?.some(
data => data.$?.['android:scheme'] === 'clerk' && data.$?.['android:host'] === callbackHost,
),
);

if (callbackIsRegistered) {
return;
}

intentFilters.push({
action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }],
category: [
{ $: { 'android:name': 'android.intent.category.DEFAULT' } },
{ $: { 'android:name': 'android.intent.category.BROWSABLE' } },
],
data: [{ $: { 'android:scheme': 'clerk', 'android:host': callbackHost } }],
});
mainActivity['intent-filter'] = intentFilters;
};

const withClerkIOS = config => {
console.log('✅ Clerk iOS plugin loaded');

Expand Down Expand Up @@ -94,6 +120,15 @@ const withClerkIOS = config => {
const withClerkAndroid = config => {
console.log('✅ Clerk Android plugin loaded');

config = withAndroidManifest(config, modConfig => {
const packageName = config.android?.package;
if (packageName) {
const mainActivity = AndroidConfig.Manifest.getMainActivityOrThrow(modConfig.modResults);
addHostedAuthIntentFilter(mainActivity, packageName);
}
return modConfig;
});

return withAppBuildGradle(config, modConfig => {
let buildGradle = modConfig.modResults.contents;

Expand Down Expand Up @@ -354,6 +389,7 @@ const withClerkExpo = (config, props = {}) => {

module.exports = withClerkExpo;
module.exports._testing = {
addHostedAuthIntentFilter,
validateThemeJson,
isPlainObject,
VALID_COLOR_KEYS,
Expand Down
4 changes: 4 additions & 0 deletions packages/expo/hosted-auth/package.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
{
"main": "../dist/hosted-auth/index.js",
"types": "../dist/hosted-auth/index.d.ts"
}
5 changes: 5 additions & 0 deletions packages/expo/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -61,6 +61,10 @@
"types": "./dist/apple/index.d.ts",
"default": "./dist/apple/index.js"
},
"./hosted-auth": {
"types": "./dist/hosted-auth/index.d.ts",
"default": "./dist/hosted-auth/index.js"
},
"./resource-cache": {
"types": "./dist/resource-cache/index.d.ts",
"default": "./dist/resource-cache/index.js"
Expand Down Expand Up @@ -92,6 +96,7 @@
"token-cache",
"google",
"apple",
"hosted-auth",
"experimental",
"legacy",
"src/specs",
Expand Down
30 changes: 30 additions & 0 deletions packages/expo/src/__tests__/appPlugin.hostedAuth.test.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { describe, expect, test } from 'vitest';

// eslint-disable-next-line @typescript-eslint/no-require-imports -- CJS plugin, no ESM export
const { addHostedAuthIntentFilter } = require('../../app.plugin.js')._testing;

describe('addHostedAuthIntentFilter', () => {
test('registers the canonical Android hosted auth callback', () => {
const mainActivity = {};

addHostedAuthIntentFilter(mainActivity, 'com.example.app');

expect(mainActivity['intent-filter']).toContainEqual({
action: [{ $: { 'android:name': 'android.intent.action.VIEW' } }],
category: [
{ $: { 'android:name': 'android.intent.category.DEFAULT' } },
{ $: { 'android:name': 'android.intent.category.BROWSABLE' } },
],
data: [{ $: { 'android:scheme': 'clerk', 'android:host': 'com.example.app.callback' } }],
});
});

test('does not duplicate an existing hosted auth callback', () => {
const mainActivity = {};

addHostedAuthIntentFilter(mainActivity, 'com.example.app');
addHostedAuthIntentFilter(mainActivity, 'com.example.app');

expect(mainActivity['intent-filter']).toHaveLength(1);
});
});
Loading
Loading