diff --git a/MIGRATION_GUIDE.md b/MIGRATION_GUIDE.md index 79a56121..55d8d129 100644 --- a/MIGRATION_GUIDE.md +++ b/MIGRATION_GUIDE.md @@ -113,17 +113,7 @@ On React Native Web, the `authorize()` method now triggers a **full-page redirec **✅ Action Required:** Review the new **[FAQ entry](#faq-authorize-web)** for guidance on how to correctly handle the post-login flow on the web. The `Auth0Provider` and `useAuth0` hook are designed to manage this flow automatically. -### Change #5: New Peer Dependency for Web Support - -To support the web platform, the library now has an **optional peer dependency** on `@auth0/auth0-spa-js`. - -**✅ Action Required:** If you are using `react-native-auth0` in a React Native Web project, you **must** install this package. Native-only projects can ignore this. - -```bash -npm install @auth0/auth0-spa-js -``` - -### Change #6: Hook Methods Now Throw Error +### Change #5: Hook Methods Now Throw Error Previously, all hook-related methods such as `getCredentials()`, `saveCredentials()`, etc., did not throw error directly. Instead, any issues were silently handled and surfaced via the error property in `useAuth0()`: diff --git a/REACT_NATIVE_WEB_SETUP.md b/REACT_NATIVE_WEB_SETUP.md index 99686275..c4ffd740 100644 --- a/REACT_NATIVE_WEB_SETUP.md +++ b/REACT_NATIVE_WEB_SETUP.md @@ -17,19 +17,7 @@ If you want to use React Native Web with React Native Auth0, follow these steps: Follow the official React Native Web installation guide: **https://necolas.github.io/react-native-web/docs/setup/** -### 2. Install Auth0 SPA JS (Required for Web) - -React Native Auth0 requires `@auth0/auth0-spa-js` for web platform support: - -```bash -# Using npm -npm install @auth0/auth0-spa-js - -# Using yarn -yarn add @auth0/auth0-spa-js -``` - -### 3. Use React Native Auth0 +### 2. Use React Native Auth0 Once React Native Web and Auth0 SPA JS are installed, you can use React Native Auth0 exactly as you would in a native React Native app. The library will automatically detect the web platform and use the appropriate implementation. diff --git a/example/package.json b/example/package.json index c58536e1..1410d964 100644 --- a/example/package.json +++ b/example/package.json @@ -12,7 +12,6 @@ "build:ios": "react-native build-ios --mode Debug" }, "dependencies": { - "@auth0/auth0-spa-js": "^2.2.0", "@react-navigation/bottom-tabs": "^7.4.2", "@react-navigation/native": "^7.1.13", "@react-navigation/stack": "^7.3.6", diff --git a/package.json b/package.json index 91271b1a..388a99a8 100644 --- a/package.json +++ b/package.json @@ -73,20 +73,15 @@ "registry": "https://registry.npmjs.org/" }, "peerDependencies": { - "@auth0/auth0-spa-js": ">=2.2.0", "react": ">=19.0.0", "react-native": ">=0.78.0" }, "peerDependenciesMeta": { - "@auth0/auth0-spa-js": { - "optional": true - }, "expo": { "optional": true } }, "devDependencies": { - "@auth0/auth0-spa-js": "^2.2.0", "@commitlint/config-conventional": "^17.0.2", "@eslint/compat": "^1.2.7", "@eslint/eslintrc": "^3.3.0", @@ -140,6 +135,7 @@ "typescript": "5.2.2" }, "dependencies": { + "@auth0/auth0-spa-js": "2.3.0", "base-64": "^1.0.0", "jwt-decode": "^4.0.0", "url": "^0.11.4" diff --git a/src/core/interfaces/IWebAuthProvider.ts b/src/core/interfaces/IWebAuthProvider.ts index 8c8bad7f..e2d58696 100644 --- a/src/core/interfaces/IWebAuthProvider.ts +++ b/src/core/interfaces/IWebAuthProvider.ts @@ -2,6 +2,7 @@ import type { Credentials, WebAuthorizeParameters, ClearSessionParameters, + User, } from '../../types'; import type { @@ -53,6 +54,11 @@ export interface IWebAuthProvider { options?: NativeClearSessionOptions | WebClearSessionOptions ): Promise; + /** + * Checks the user's session and updates the local state if the session is still valid. + */ + checkWebSession(): Promise; + /** * Cancels an ongoing web authentication transaction. * diff --git a/src/hooks/Auth0Provider.tsx b/src/hooks/Auth0Provider.tsx index 5ae27063..72108a29 100644 --- a/src/hooks/Auth0Provider.tsx +++ b/src/hooks/Auth0Provider.tsx @@ -64,6 +64,9 @@ export const Auth0Provider = ({ // If the redirect fails, dispatch an error. dispatch({ type: 'ERROR', error: e as AuthError }); } + } else if (typeof window !== 'undefined') { + const user = await client.webAuth.checkWebSession(); + dispatch({ type: 'INITIALIZED', user }); } try { const credentials = await client.credentialsManager.getCredentials(); diff --git a/src/hooks/__tests__/Auth0Provider.spec.tsx b/src/hooks/__tests__/Auth0Provider.spec.tsx index fc2e9ad9..2f4defd9 100644 --- a/src/hooks/__tests__/Auth0Provider.spec.tsx +++ b/src/hooks/__tests__/Auth0Provider.spec.tsx @@ -88,6 +88,7 @@ const createMockClient = () => { clearSession: jest.fn().mockResolvedValue(undefined), cancelWebAuth: jest.fn().mockResolvedValue(undefined), handleRedirectCallback: jest.fn().mockResolvedValue(undefined), + checkWebSession: jest.fn().mockResolvedValue(null), }, credentialsManager: { hasValidCredentials: jest.fn().mockResolvedValue(false), @@ -194,11 +195,20 @@ describe('Auth0Provider', () => { }); it('should render a loading state initially', async () => { - // Make getCredentials return a promise that we can control + // Make both checkWebSession and getCredentials return promises that we can control + let resolveCheckSession: (value: any) => void; let resolveCredentials: (value: any) => void; + + const checkSessionPromise = new Promise((resolve) => { + resolveCheckSession = resolve; + }); const credentialsPromise = new Promise((resolve) => { resolveCredentials = resolve; }); + + mockClientInstance.webAuth.checkWebSession.mockReturnValue( + checkSessionPromise + ); mockClientInstance.credentialsManager.getCredentials.mockReturnValue( credentialsPromise ); @@ -214,8 +224,9 @@ describe('Auth0Provider', () => { // Should show loading state initially expect(screen.getByTestId('loading')).toBeDefined(); - // Resolve the credentials promise + // Resolve the promises await act(async () => { + resolveCheckSession!(null); resolveCredentials!(null); }); diff --git a/src/platforms/native/adapters/NativeWebAuthProvider.ts b/src/platforms/native/adapters/NativeWebAuthProvider.ts index f323b58f..f26054bb 100644 --- a/src/platforms/native/adapters/NativeWebAuthProvider.ts +++ b/src/platforms/native/adapters/NativeWebAuthProvider.ts @@ -4,6 +4,7 @@ import type { Credentials, WebAuthorizeParameters, ClearSessionParameters, + User, } from '../../../types'; import type { NativeAuthorizeOptions, @@ -28,6 +29,10 @@ export class NativeWebAuthProvider implements IWebAuthProvider { throw new AuthError('NotImplemented', webAuthNotSupported); } + async checkWebSession(): Promise { + throw new AuthError('NotImplemented', webAuthNotSupported); + } + async authorize( parameters: WebAuthorizeParameters = {}, options: NativeAuthorizeOptions = {} diff --git a/src/platforms/web/adapters/WebAuth0Client.ts b/src/platforms/web/adapters/WebAuth0Client.ts index dbd7d12d..1da04a1a 100644 --- a/src/platforms/web/adapters/WebAuth0Client.ts +++ b/src/platforms/web/adapters/WebAuth0Client.ts @@ -66,7 +66,7 @@ export class WebAuth0Client implements IAuth0Client { domain: options.domain, clientId: options.clientId, cacheLocation: options.cacheLocation ?? 'memory', - useRefreshTokens: options.useRefreshTokens ?? true, + useRefreshTokens: options.useRefreshTokens ?? false, authorizationParams: { redirect_uri: typeof window !== 'undefined' ? window.location.origin : '', diff --git a/src/platforms/web/adapters/WebWebAuthProvider.ts b/src/platforms/web/adapters/WebWebAuthProvider.ts index 0be8bc40..02d4bf85 100644 --- a/src/platforms/web/adapters/WebWebAuthProvider.ts +++ b/src/platforms/web/adapters/WebWebAuthProvider.ts @@ -3,14 +3,46 @@ import type { Credentials, WebAuthorizeParameters, ClearSessionParameters, + User, } from '../../../types'; import { AuthError, WebAuthError } from '../../../core/models'; import { finalizeScope } from '../../../core/utils'; -import type { Auth0Client, PopupCancelledError } from '@auth0/auth0-spa-js'; +import type { + Auth0Client, + PopupCancelledError, + User as SpaJSUser, +} from '@auth0/auth0-spa-js'; export class WebWebAuthProvider implements IWebAuthProvider { constructor(private client: Auth0Client) {} + // private method to convert a SpaJSUser to a User + private convertUser(user: SpaJSUser | undefined): User | null { + if (!user || !user.sub) return null; + return { + sub: user.sub, + name: user.name, + givenName: user.given_name, + familyName: user.family_name, + middleName: user.middle_name, + nickname: user.nickname, + preferredUsername: user.preferred_username, + profile: user.profile, + picture: user.picture, + website: user.website, + email: user.email, + emailVerified: user.email_verified, + gender: user.gender, + birthdate: user.birthdate, + zoneinfo: user.zoneinfo, + locale: user.locale, + phoneNumber: user.phone_number, + phoneNumberVerified: user.phone_number_verified, + address: user.address, + updatedAt: user.updated_at, + }; + } + async authorize( parameters: WebAuthorizeParameters = {} ): Promise { @@ -79,6 +111,14 @@ export class WebWebAuthProvider implements IWebAuthProvider { } } + async checkWebSession(): Promise { + await this.client.checkSession(); + const spaUser: SpaJSUser | undefined = await this.client.getUser(); + // convert this to a User + const user = this.convertUser(spaUser); + return user; + } + async cancelWebAuth(): Promise { // Web-based flows cannot be programmatically cancelled. This is a no-op. return Promise.resolve(); diff --git a/yarn.lock b/yarn.lock index 8ce171e2..5bd86061 100644 --- a/yarn.lock +++ b/yarn.lock @@ -50,7 +50,7 @@ __metadata: languageName: node linkType: hard -"@auth0/auth0-spa-js@npm:^2.2.0": +"@auth0/auth0-spa-js@npm:2.3.0": version: 2.3.0 resolution: "@auth0/auth0-spa-js@npm:2.3.0" checksum: 6f9c84cd300ba7215a7b894690418010cd743ada5970083f0753f189a39208b4262a9828f28c8f932ca33c1dfe6e587b057a6c956e40632aea9dbed5099220fd @@ -5184,7 +5184,6 @@ __metadata: version: 0.0.0-use.local resolution: "Auth0Example@workspace:example" dependencies: - "@auth0/auth0-spa-js": ^2.2.0 "@babel/core": ^7.25.2 "@babel/preset-env": ^7.25.3 "@babel/runtime": ^7.25.0 @@ -14604,7 +14603,7 @@ __metadata: version: 0.0.0-use.local resolution: "react-native-auth0@workspace:." dependencies: - "@auth0/auth0-spa-js": ^2.2.0 + "@auth0/auth0-spa-js": 2.3.0 "@commitlint/config-conventional": ^17.0.2 "@eslint/compat": ^1.2.7 "@eslint/eslintrc": ^3.3.0 @@ -14660,12 +14659,9 @@ __metadata: typescript: 5.2.2 url: ^0.11.4 peerDependencies: - "@auth0/auth0-spa-js": ">=2.2.0" react: ">=19.0.0" react-native: ">=0.78.0" peerDependenciesMeta: - "@auth0/auth0-spa-js": - optional: true expo: optional: true languageName: unknown