Skip to content

Commit 6b3e2cf

Browse files
feat(web): Simplify Setup, Add Session Check & Fix Missing Refresh Token (#1303)
1 parent 1cedc06 commit 6b3e2cf

File tree

11 files changed

+74
-40
lines changed

11 files changed

+74
-40
lines changed

MIGRATION_GUIDE.md

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -113,17 +113,7 @@ On React Native Web, the `authorize()` method now triggers a **full-page redirec
113113
114114
**✅ 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.
115115
116-
### Change #5: New Peer Dependency for Web Support
117-
118-
To support the web platform, the library now has an **optional peer dependency** on `@auth0/auth0-spa-js`.
119-
120-
**✅ 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.
121-
122-
```bash
123-
npm install @auth0/auth0-spa-js
124-
```
125-
126-
### Change #6: Hook Methods Now Throw Error
116+
### Change #5: Hook Methods Now Throw Error
127117
128118
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()`:
129119

REACT_NATIVE_WEB_SETUP.md

Lines changed: 1 addition & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,19 +17,7 @@ If you want to use React Native Web with React Native Auth0, follow these steps:
1717
Follow the official React Native Web installation guide:
1818
**https://necolas.github.io/react-native-web/docs/setup/**
1919

20-
### 2. Install Auth0 SPA JS (Required for Web)
21-
22-
React Native Auth0 requires `@auth0/auth0-spa-js` for web platform support:
23-
24-
```bash
25-
# Using npm
26-
npm install @auth0/auth0-spa-js
27-
28-
# Using yarn
29-
yarn add @auth0/auth0-spa-js
30-
```
31-
32-
### 3. Use React Native Auth0
20+
### 2. Use React Native Auth0
3321

3422
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.
3523

example/package.json

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,7 +12,6 @@
1212
"build:ios": "react-native build-ios --mode Debug"
1313
},
1414
"dependencies": {
15-
"@auth0/auth0-spa-js": "^2.2.0",
1615
"@react-navigation/bottom-tabs": "^7.4.2",
1716
"@react-navigation/native": "^7.1.13",
1817
"@react-navigation/stack": "^7.3.6",

package.json

Lines changed: 1 addition & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -73,20 +73,15 @@
7373
"registry": "https://registry.npmjs.org/"
7474
},
7575
"peerDependencies": {
76-
"@auth0/auth0-spa-js": ">=2.2.0",
7776
"react": ">=19.0.0",
7877
"react-native": ">=0.78.0"
7978
},
8079
"peerDependenciesMeta": {
81-
"@auth0/auth0-spa-js": {
82-
"optional": true
83-
},
8480
"expo": {
8581
"optional": true
8682
}
8783
},
8884
"devDependencies": {
89-
"@auth0/auth0-spa-js": "^2.2.0",
9085
"@commitlint/config-conventional": "^17.0.2",
9186
"@eslint/compat": "^1.2.7",
9287
"@eslint/eslintrc": "^3.3.0",
@@ -140,6 +135,7 @@
140135
"typescript": "5.2.2"
141136
},
142137
"dependencies": {
138+
"@auth0/auth0-spa-js": "2.3.0",
143139
"base-64": "^1.0.0",
144140
"jwt-decode": "^4.0.0",
145141
"url": "^0.11.4"

src/core/interfaces/IWebAuthProvider.ts

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@ import type {
22
Credentials,
33
WebAuthorizeParameters,
44
ClearSessionParameters,
5+
User,
56
} from '../../types';
67

78
import type {
@@ -53,6 +54,11 @@ export interface IWebAuthProvider {
5354
options?: NativeClearSessionOptions | WebClearSessionOptions
5455
): Promise<void>;
5556

57+
/**
58+
* Checks the user's session and updates the local state if the session is still valid.
59+
*/
60+
checkWebSession(): Promise<User | null>;
61+
5662
/**
5763
* Cancels an ongoing web authentication transaction.
5864
*

src/hooks/Auth0Provider.tsx

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -64,6 +64,9 @@ export const Auth0Provider = ({
6464
// If the redirect fails, dispatch an error.
6565
dispatch({ type: 'ERROR', error: e as AuthError });
6666
}
67+
} else if (typeof window !== 'undefined') {
68+
const user = await client.webAuth.checkWebSession();
69+
dispatch({ type: 'INITIALIZED', user });
6770
}
6871
try {
6972
const credentials = await client.credentialsManager.getCredentials();

src/hooks/__tests__/Auth0Provider.spec.tsx

Lines changed: 13 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -88,6 +88,7 @@ const createMockClient = () => {
8888
clearSession: jest.fn().mockResolvedValue(undefined),
8989
cancelWebAuth: jest.fn().mockResolvedValue(undefined),
9090
handleRedirectCallback: jest.fn().mockResolvedValue(undefined),
91+
checkWebSession: jest.fn().mockResolvedValue(null),
9192
},
9293
credentialsManager: {
9394
hasValidCredentials: jest.fn().mockResolvedValue(false),
@@ -194,11 +195,20 @@ describe('Auth0Provider', () => {
194195
});
195196

196197
it('should render a loading state initially', async () => {
197-
// Make getCredentials return a promise that we can control
198+
// Make both checkWebSession and getCredentials return promises that we can control
199+
let resolveCheckSession: (value: any) => void;
198200
let resolveCredentials: (value: any) => void;
201+
202+
const checkSessionPromise = new Promise((resolve) => {
203+
resolveCheckSession = resolve;
204+
});
199205
const credentialsPromise = new Promise((resolve) => {
200206
resolveCredentials = resolve;
201207
});
208+
209+
mockClientInstance.webAuth.checkWebSession.mockReturnValue(
210+
checkSessionPromise
211+
);
202212
mockClientInstance.credentialsManager.getCredentials.mockReturnValue(
203213
credentialsPromise
204214
);
@@ -214,8 +224,9 @@ describe('Auth0Provider', () => {
214224
// Should show loading state initially
215225
expect(screen.getByTestId('loading')).toBeDefined();
216226

217-
// Resolve the credentials promise
227+
// Resolve the promises
218228
await act(async () => {
229+
resolveCheckSession!(null);
219230
resolveCredentials!(null);
220231
});
221232

src/platforms/native/adapters/NativeWebAuthProvider.ts

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@ import type {
44
Credentials,
55
WebAuthorizeParameters,
66
ClearSessionParameters,
7+
User,
78
} from '../../../types';
89
import type {
910
NativeAuthorizeOptions,
@@ -28,6 +29,10 @@ export class NativeWebAuthProvider implements IWebAuthProvider {
2829
throw new AuthError('NotImplemented', webAuthNotSupported);
2930
}
3031

32+
async checkWebSession(): Promise<User | null> {
33+
throw new AuthError('NotImplemented', webAuthNotSupported);
34+
}
35+
3136
async authorize(
3237
parameters: WebAuthorizeParameters = {},
3338
options: NativeAuthorizeOptions = {}

src/platforms/web/adapters/WebAuth0Client.ts

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -66,7 +66,7 @@ export class WebAuth0Client implements IAuth0Client {
6666
domain: options.domain,
6767
clientId: options.clientId,
6868
cacheLocation: options.cacheLocation ?? 'memory',
69-
useRefreshTokens: options.useRefreshTokens ?? true,
69+
useRefreshTokens: options.useRefreshTokens ?? false,
7070
authorizationParams: {
7171
redirect_uri:
7272
typeof window !== 'undefined' ? window.location.origin : '',

src/platforms/web/adapters/WebWebAuthProvider.ts

Lines changed: 41 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -3,14 +3,46 @@ import type {
33
Credentials,
44
WebAuthorizeParameters,
55
ClearSessionParameters,
6+
User,
67
} from '../../../types';
78
import { AuthError, WebAuthError } from '../../../core/models';
89
import { finalizeScope } from '../../../core/utils';
9-
import type { Auth0Client, PopupCancelledError } from '@auth0/auth0-spa-js';
10+
import type {
11+
Auth0Client,
12+
PopupCancelledError,
13+
User as SpaJSUser,
14+
} from '@auth0/auth0-spa-js';
1015

1116
export class WebWebAuthProvider implements IWebAuthProvider {
1217
constructor(private client: Auth0Client) {}
1318

19+
// private method to convert a SpaJSUser to a User
20+
private convertUser(user: SpaJSUser | undefined): User | null {
21+
if (!user || !user.sub) return null;
22+
return {
23+
sub: user.sub,
24+
name: user.name,
25+
givenName: user.given_name,
26+
familyName: user.family_name,
27+
middleName: user.middle_name,
28+
nickname: user.nickname,
29+
preferredUsername: user.preferred_username,
30+
profile: user.profile,
31+
picture: user.picture,
32+
website: user.website,
33+
email: user.email,
34+
emailVerified: user.email_verified,
35+
gender: user.gender,
36+
birthdate: user.birthdate,
37+
zoneinfo: user.zoneinfo,
38+
locale: user.locale,
39+
phoneNumber: user.phone_number,
40+
phoneNumberVerified: user.phone_number_verified,
41+
address: user.address,
42+
updatedAt: user.updated_at,
43+
};
44+
}
45+
1446
async authorize(
1547
parameters: WebAuthorizeParameters = {}
1648
): Promise<Credentials> {
@@ -79,6 +111,14 @@ export class WebWebAuthProvider implements IWebAuthProvider {
79111
}
80112
}
81113

114+
async checkWebSession(): Promise<User | null> {
115+
await this.client.checkSession();
116+
const spaUser: SpaJSUser | undefined = await this.client.getUser();
117+
// convert this to a User
118+
const user = this.convertUser(spaUser);
119+
return user;
120+
}
121+
82122
async cancelWebAuth(): Promise<void> {
83123
// Web-based flows cannot be programmatically cancelled. This is a no-op.
84124
return Promise.resolve();

0 commit comments

Comments
 (0)