transactional-auth-react 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +223 -0
- package/dist/index.d.mts +252 -0
- package/dist/index.d.ts +252 -0
- package/dist/index.js +333 -0
- package/dist/index.mjs +290 -0
- package/package.json +58 -0
package/README.md
ADDED
|
@@ -0,0 +1,223 @@
|
|
|
1
|
+
# transactional-auth-react
|
|
2
|
+
|
|
3
|
+
React SDK for Transactional Auth - OpenID Connect authentication for React applications.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
npm install transactional-auth-react
|
|
9
|
+
# or
|
|
10
|
+
yarn add transactional-auth-react
|
|
11
|
+
# or
|
|
12
|
+
pnpm add transactional-auth-react
|
|
13
|
+
```
|
|
14
|
+
|
|
15
|
+
## Quick Start
|
|
16
|
+
|
|
17
|
+
### 1. Configure the Provider
|
|
18
|
+
|
|
19
|
+
Wrap your application with the `TransactionalAuthProvider`:
|
|
20
|
+
|
|
21
|
+
```tsx
|
|
22
|
+
import { TransactionalAuthProvider } from 'transactional-auth-react';
|
|
23
|
+
|
|
24
|
+
function App() {
|
|
25
|
+
return (
|
|
26
|
+
<TransactionalAuthProvider
|
|
27
|
+
domain="auth.usetransactional.com"
|
|
28
|
+
clientId="your-client-id"
|
|
29
|
+
redirectUri={window.location.origin}
|
|
30
|
+
>
|
|
31
|
+
<YourApp />
|
|
32
|
+
</TransactionalAuthProvider>
|
|
33
|
+
);
|
|
34
|
+
}
|
|
35
|
+
```
|
|
36
|
+
|
|
37
|
+
### 2. Add Login/Logout
|
|
38
|
+
|
|
39
|
+
Use the `useAuth` hook to access authentication state and methods:
|
|
40
|
+
|
|
41
|
+
```tsx
|
|
42
|
+
import { useAuth } from 'transactional-auth-react';
|
|
43
|
+
|
|
44
|
+
function LoginButton() {
|
|
45
|
+
const { isAuthenticated, isLoading, loginWithRedirect, logout, user } = useAuth();
|
|
46
|
+
|
|
47
|
+
if (isLoading) {
|
|
48
|
+
return <div>Loading...</div>;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
if (isAuthenticated) {
|
|
52
|
+
return (
|
|
53
|
+
<div>
|
|
54
|
+
<span>Welcome, {user?.name}</span>
|
|
55
|
+
<button onClick={() => logout()}>Logout</button>
|
|
56
|
+
</div>
|
|
57
|
+
);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
return <button onClick={() => loginWithRedirect()}>Login</button>;
|
|
61
|
+
}
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### 3. Protect Routes
|
|
65
|
+
|
|
66
|
+
Use the `AuthGuard` component or `withAuthenticationRequired` HOC:
|
|
67
|
+
|
|
68
|
+
```tsx
|
|
69
|
+
import { AuthGuard } from 'transactional-auth-react';
|
|
70
|
+
|
|
71
|
+
function ProtectedPage() {
|
|
72
|
+
return (
|
|
73
|
+
<AuthGuard fallback={<div>Loading...</div>}>
|
|
74
|
+
<div>This content is only visible to authenticated users</div>
|
|
75
|
+
</AuthGuard>
|
|
76
|
+
);
|
|
77
|
+
}
|
|
78
|
+
```
|
|
79
|
+
|
|
80
|
+
Or with the HOC:
|
|
81
|
+
|
|
82
|
+
```tsx
|
|
83
|
+
import { withAuthenticationRequired } from 'transactional-auth-react';
|
|
84
|
+
|
|
85
|
+
function Dashboard() {
|
|
86
|
+
return <div>Dashboard content</div>;
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
export default withAuthenticationRequired(Dashboard);
|
|
90
|
+
```
|
|
91
|
+
|
|
92
|
+
## API Reference
|
|
93
|
+
|
|
94
|
+
### TransactionalAuthProvider
|
|
95
|
+
|
|
96
|
+
The main provider component that wraps your application.
|
|
97
|
+
|
|
98
|
+
| Prop | Type | Default | Description |
|
|
99
|
+
|------|------|---------|-------------|
|
|
100
|
+
| `domain` | `string` | Required | Auth domain (e.g., 'auth.usetransactional.com') |
|
|
101
|
+
| `clientId` | `string` | Required | Your application's client ID |
|
|
102
|
+
| `redirectUri` | `string` | `window.location.origin` | Redirect URI after authentication |
|
|
103
|
+
| `scope` | `string` | `'openid profile email'` | Scopes to request |
|
|
104
|
+
| `audience` | `string` | - | API audience for access token |
|
|
105
|
+
| `cacheLocation` | `'memory' \| 'localstorage'` | `'localstorage'` | Where to store tokens |
|
|
106
|
+
| `useRefreshTokens` | `boolean` | `true` | Use refresh tokens for silent renewal |
|
|
107
|
+
| `onRedirectCallback` | `(appState?) => void` | - | Callback after redirect from login |
|
|
108
|
+
|
|
109
|
+
### useAuth Hook
|
|
110
|
+
|
|
111
|
+
Returns the authentication context with state and methods.
|
|
112
|
+
|
|
113
|
+
```tsx
|
|
114
|
+
const {
|
|
115
|
+
// State
|
|
116
|
+
isAuthenticated, // boolean - Whether user is authenticated
|
|
117
|
+
isLoading, // boolean - Whether auth state is loading
|
|
118
|
+
user, // TransactionalAuthUser | null
|
|
119
|
+
error, // Error | null
|
|
120
|
+
|
|
121
|
+
// Methods
|
|
122
|
+
loginWithRedirect, // (options?) => Promise<void>
|
|
123
|
+
loginWithPopup, // (options?) => Promise<void>
|
|
124
|
+
logout, // (options?) => Promise<void>
|
|
125
|
+
getAccessToken, // () => Promise<string | undefined>
|
|
126
|
+
getIdTokenClaims, // () => Promise<object | undefined>
|
|
127
|
+
hasPermission, // (permission: string) => boolean
|
|
128
|
+
hasRole, // (role: string) => boolean
|
|
129
|
+
} = useAuth();
|
|
130
|
+
```
|
|
131
|
+
|
|
132
|
+
### useUser Hook
|
|
133
|
+
|
|
134
|
+
Returns the current authenticated user.
|
|
135
|
+
|
|
136
|
+
```tsx
|
|
137
|
+
const { user, isLoading } = useUser();
|
|
138
|
+
```
|
|
139
|
+
|
|
140
|
+
### useAccessToken Hook
|
|
141
|
+
|
|
142
|
+
Returns the current access token (or null if not authenticated).
|
|
143
|
+
|
|
144
|
+
```tsx
|
|
145
|
+
const accessToken = useAccessToken();
|
|
146
|
+
```
|
|
147
|
+
|
|
148
|
+
### AuthGuard Component
|
|
149
|
+
|
|
150
|
+
Protects content that requires authentication.
|
|
151
|
+
|
|
152
|
+
```tsx
|
|
153
|
+
<AuthGuard
|
|
154
|
+
fallback={<Loading />} // Shown while loading
|
|
155
|
+
onUnauthenticated={() => {}} // Called when not authenticated
|
|
156
|
+
autoRedirect={true} // Auto-redirect to login
|
|
157
|
+
>
|
|
158
|
+
<ProtectedContent />
|
|
159
|
+
</AuthGuard>
|
|
160
|
+
```
|
|
161
|
+
|
|
162
|
+
### withAuthenticationRequired HOC
|
|
163
|
+
|
|
164
|
+
Higher-order component for protecting components.
|
|
165
|
+
|
|
166
|
+
```tsx
|
|
167
|
+
export default withAuthenticationRequired(Component, {
|
|
168
|
+
fallback: <Loading />,
|
|
169
|
+
onUnauthenticated: () => {},
|
|
170
|
+
returnTo: '/dashboard',
|
|
171
|
+
});
|
|
172
|
+
```
|
|
173
|
+
|
|
174
|
+
## Making API Calls
|
|
175
|
+
|
|
176
|
+
Use the access token to authenticate API requests:
|
|
177
|
+
|
|
178
|
+
```tsx
|
|
179
|
+
import { useAuth } from 'transactional-auth-react';
|
|
180
|
+
|
|
181
|
+
function ApiExample() {
|
|
182
|
+
const { getAccessToken } = useAuth();
|
|
183
|
+
|
|
184
|
+
async function fetchData() {
|
|
185
|
+
const token = await getAccessToken();
|
|
186
|
+
|
|
187
|
+
const response = await fetch('https://api.example.com/data', {
|
|
188
|
+
headers: {
|
|
189
|
+
Authorization: `Bearer ${token}`,
|
|
190
|
+
},
|
|
191
|
+
});
|
|
192
|
+
|
|
193
|
+
return response.json();
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ...
|
|
197
|
+
}
|
|
198
|
+
```
|
|
199
|
+
|
|
200
|
+
## Next.js App Router
|
|
201
|
+
|
|
202
|
+
For Next.js 13+ with the App Router, mark components as client components:
|
|
203
|
+
|
|
204
|
+
```tsx
|
|
205
|
+
'use client';
|
|
206
|
+
|
|
207
|
+
import { TransactionalAuthProvider } from 'transactional-auth-react';
|
|
208
|
+
|
|
209
|
+
export function AuthProvider({ children }: { children: React.ReactNode }) {
|
|
210
|
+
return (
|
|
211
|
+
<TransactionalAuthProvider
|
|
212
|
+
domain="auth.usetransactional.com"
|
|
213
|
+
clientId="your-client-id"
|
|
214
|
+
>
|
|
215
|
+
{children}
|
|
216
|
+
</TransactionalAuthProvider>
|
|
217
|
+
);
|
|
218
|
+
}
|
|
219
|
+
```
|
|
220
|
+
|
|
221
|
+
## License
|
|
222
|
+
|
|
223
|
+
MIT
|
package/dist/index.d.mts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Transactional Auth React - Types
|
|
6
|
+
*/
|
|
7
|
+
interface TransactionalAuthUser {
|
|
8
|
+
sub: string;
|
|
9
|
+
email?: string;
|
|
10
|
+
emailVerified?: boolean;
|
|
11
|
+
name?: string;
|
|
12
|
+
givenName?: string;
|
|
13
|
+
familyName?: string;
|
|
14
|
+
picture?: string;
|
|
15
|
+
phoneNumber?: string;
|
|
16
|
+
phoneNumberVerified?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface TransactionalAuthState {
|
|
19
|
+
isAuthenticated: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
user: TransactionalAuthUser | null;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
}
|
|
24
|
+
interface LoginOptions {
|
|
25
|
+
/** Return URL after login */
|
|
26
|
+
returnTo?: string;
|
|
27
|
+
/** State to pass through the authentication flow */
|
|
28
|
+
appState?: Record<string, unknown>;
|
|
29
|
+
/** Force specific connection (e.g., 'google', 'github') */
|
|
30
|
+
connection?: string;
|
|
31
|
+
/** Login hint (pre-fill email) */
|
|
32
|
+
loginHint?: string;
|
|
33
|
+
/** UI locales */
|
|
34
|
+
uiLocales?: string;
|
|
35
|
+
}
|
|
36
|
+
interface LogoutOptions {
|
|
37
|
+
/** Return URL after logout */
|
|
38
|
+
returnTo?: string;
|
|
39
|
+
/** Whether to logout from the identity provider as well */
|
|
40
|
+
federated?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface TransactionalAuthContextValue extends TransactionalAuthState {
|
|
43
|
+
/** Redirect to login page */
|
|
44
|
+
loginWithRedirect: (options?: LoginOptions) => Promise<void>;
|
|
45
|
+
/** Open login popup */
|
|
46
|
+
loginWithPopup: (options?: LoginOptions) => Promise<void>;
|
|
47
|
+
/** Logout the user */
|
|
48
|
+
logout: (options?: LogoutOptions) => Promise<void>;
|
|
49
|
+
/** Get the access token (silently refreshes if needed) */
|
|
50
|
+
getAccessToken: () => Promise<string | undefined>;
|
|
51
|
+
/** Get ID token claims */
|
|
52
|
+
getIdTokenClaims: () => Promise<Record<string, unknown> | undefined>;
|
|
53
|
+
/** Check if user has specific permission */
|
|
54
|
+
hasPermission: (permission: string) => boolean;
|
|
55
|
+
/** Check if user has specific role */
|
|
56
|
+
hasRole: (role: string) => boolean;
|
|
57
|
+
}
|
|
58
|
+
interface TransactionalAuthProviderProps {
|
|
59
|
+
/** Auth domain (e.g., 'auth.usetransactional.com' or 'myapp.auth.usetransactional.com') */
|
|
60
|
+
domain: string;
|
|
61
|
+
/** Client ID of your application */
|
|
62
|
+
clientId: string;
|
|
63
|
+
/** Redirect URI after authentication (defaults to window.location.origin) */
|
|
64
|
+
redirectUri?: string;
|
|
65
|
+
/** Scopes to request (defaults to 'openid profile email') */
|
|
66
|
+
scope?: string;
|
|
67
|
+
/** API audience for access token */
|
|
68
|
+
audience?: string;
|
|
69
|
+
/** Where to store tokens: 'memory' or 'localstorage' */
|
|
70
|
+
cacheLocation?: 'memory' | 'localstorage';
|
|
71
|
+
/** Use refresh tokens for silent renewal */
|
|
72
|
+
useRefreshTokens?: boolean;
|
|
73
|
+
/** Callback after redirect from login */
|
|
74
|
+
onRedirectCallback?: (appState?: Record<string, unknown>) => void;
|
|
75
|
+
/** Children components */
|
|
76
|
+
children: React.ReactNode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* TransactionalAuthProvider Component
|
|
81
|
+
*
|
|
82
|
+
* Provides authentication context to your React application.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* import { TransactionalAuthProvider } from 'transactional-auth-react';
|
|
87
|
+
*
|
|
88
|
+
* function App() {
|
|
89
|
+
* return (
|
|
90
|
+
* <TransactionalAuthProvider
|
|
91
|
+
* domain="auth.usetransactional.com"
|
|
92
|
+
* clientId="your-client-id"
|
|
93
|
+
* redirectUri={window.location.origin}
|
|
94
|
+
* >
|
|
95
|
+
* <YourApp />
|
|
96
|
+
* </TransactionalAuthProvider>
|
|
97
|
+
* );
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare function TransactionalAuthProvider({ domain, clientId, redirectUri, scope, audience, cacheLocation, useRefreshTokens, onRedirectCallback, children, }: TransactionalAuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
102
|
+
|
|
103
|
+
declare const TransactionalAuthContext: React$1.Context<TransactionalAuthContextValue>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Transactional Auth React - useAuth Hook
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook to access authentication state and methods.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* import { useAuth } from 'transactional-auth-react';
|
|
115
|
+
*
|
|
116
|
+
* function LoginButton() {
|
|
117
|
+
* const { isAuthenticated, loginWithRedirect, logout, user } = useAuth();
|
|
118
|
+
*
|
|
119
|
+
* if (isAuthenticated) {
|
|
120
|
+
* return (
|
|
121
|
+
* <div>
|
|
122
|
+
* <span>Welcome, {user?.name}</span>
|
|
123
|
+
* <button onClick={() => logout()}>Logout</button>
|
|
124
|
+
* </div>
|
|
125
|
+
* );
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* return <button onClick={() => loginWithRedirect()}>Login</button>;
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function useAuth(): TransactionalAuthContextValue;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Transactional Auth React - useUser Hook
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
interface UseUserResult {
|
|
139
|
+
user: TransactionalAuthUser | null;
|
|
140
|
+
isLoading: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Hook to access the current authenticated user.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* import { useUser } from 'transactional-auth-react';
|
|
148
|
+
*
|
|
149
|
+
* function Profile() {
|
|
150
|
+
* const { user, isLoading } = useUser();
|
|
151
|
+
*
|
|
152
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
153
|
+
* if (!user) return <div>Not logged in</div>;
|
|
154
|
+
*
|
|
155
|
+
* return (
|
|
156
|
+
* <div>
|
|
157
|
+
* <img src={user.picture} alt={user.name} />
|
|
158
|
+
* <h1>{user.name}</h1>
|
|
159
|
+
* <p>{user.email}</p>
|
|
160
|
+
* </div>
|
|
161
|
+
* );
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
declare function useUser(): UseUserResult;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Transactional Auth React - useAccessToken Hook
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Hook to get the current access token.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* import { useAccessToken } from 'transactional-auth-react';
|
|
176
|
+
*
|
|
177
|
+
* function ApiComponent() {
|
|
178
|
+
* const accessToken = useAccessToken();
|
|
179
|
+
*
|
|
180
|
+
* useEffect(() => {
|
|
181
|
+
* if (accessToken) {
|
|
182
|
+
* fetch('/api/data', {
|
|
183
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
184
|
+
* });
|
|
185
|
+
* }
|
|
186
|
+
* }, [accessToken]);
|
|
187
|
+
*
|
|
188
|
+
* return <div>...</div>;
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function useAccessToken(): string | null;
|
|
193
|
+
|
|
194
|
+
interface AuthGuardProps {
|
|
195
|
+
/** Content to show while authenticated */
|
|
196
|
+
children: React$1.ReactNode;
|
|
197
|
+
/** Content to show while loading */
|
|
198
|
+
fallback?: React$1.ReactNode;
|
|
199
|
+
/** Callback when user is not authenticated */
|
|
200
|
+
onUnauthenticated?: () => void;
|
|
201
|
+
/** Whether to automatically redirect to login */
|
|
202
|
+
autoRedirect?: boolean;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Component that guards content for authenticated users only.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```tsx
|
|
209
|
+
* import { AuthGuard } from 'transactional-auth-react';
|
|
210
|
+
*
|
|
211
|
+
* function ProtectedPage() {
|
|
212
|
+
* return (
|
|
213
|
+
* <AuthGuard fallback={<div>Loading...</div>}>
|
|
214
|
+
* <div>Protected content</div>
|
|
215
|
+
* </AuthGuard>
|
|
216
|
+
* );
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function AuthGuard({ children, fallback, onUnauthenticated, autoRedirect, }: AuthGuardProps): react_jsx_runtime.JSX.Element | null;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Transactional Auth React - withAuthenticationRequired HOC
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
interface WithAuthenticationRequiredOptions {
|
|
227
|
+
/** Content to show while loading */
|
|
228
|
+
fallback?: React$1.ReactNode;
|
|
229
|
+
/** Callback when user is not authenticated */
|
|
230
|
+
onUnauthenticated?: () => void;
|
|
231
|
+
/** Return URL after login */
|
|
232
|
+
returnTo?: string;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Higher-order component that wraps a component to require authentication.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```tsx
|
|
239
|
+
* import { withAuthenticationRequired } from 'transactional-auth-react';
|
|
240
|
+
*
|
|
241
|
+
* function Dashboard() {
|
|
242
|
+
* return <div>Dashboard content</div>;
|
|
243
|
+
* }
|
|
244
|
+
*
|
|
245
|
+
* export default withAuthenticationRequired(Dashboard, {
|
|
246
|
+
* fallback: <div>Loading...</div>,
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
declare function withAuthenticationRequired<P extends object>(Component: React$1.ComponentType<P>, options?: WithAuthenticationRequiredOptions): React$1.ComponentType<P>;
|
|
251
|
+
|
|
252
|
+
export { AuthGuard, type LoginOptions, type LogoutOptions, TransactionalAuthContext, type TransactionalAuthContextValue, TransactionalAuthProvider, type TransactionalAuthProviderProps, type TransactionalAuthState, type TransactionalAuthUser, useAccessToken, useAuth, useUser, withAuthenticationRequired };
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,252 @@
|
|
|
1
|
+
import * as react_jsx_runtime from 'react/jsx-runtime';
|
|
2
|
+
import * as React$1 from 'react';
|
|
3
|
+
|
|
4
|
+
/**
|
|
5
|
+
* Transactional Auth React - Types
|
|
6
|
+
*/
|
|
7
|
+
interface TransactionalAuthUser {
|
|
8
|
+
sub: string;
|
|
9
|
+
email?: string;
|
|
10
|
+
emailVerified?: boolean;
|
|
11
|
+
name?: string;
|
|
12
|
+
givenName?: string;
|
|
13
|
+
familyName?: string;
|
|
14
|
+
picture?: string;
|
|
15
|
+
phoneNumber?: string;
|
|
16
|
+
phoneNumberVerified?: boolean;
|
|
17
|
+
}
|
|
18
|
+
interface TransactionalAuthState {
|
|
19
|
+
isAuthenticated: boolean;
|
|
20
|
+
isLoading: boolean;
|
|
21
|
+
user: TransactionalAuthUser | null;
|
|
22
|
+
error: Error | null;
|
|
23
|
+
}
|
|
24
|
+
interface LoginOptions {
|
|
25
|
+
/** Return URL after login */
|
|
26
|
+
returnTo?: string;
|
|
27
|
+
/** State to pass through the authentication flow */
|
|
28
|
+
appState?: Record<string, unknown>;
|
|
29
|
+
/** Force specific connection (e.g., 'google', 'github') */
|
|
30
|
+
connection?: string;
|
|
31
|
+
/** Login hint (pre-fill email) */
|
|
32
|
+
loginHint?: string;
|
|
33
|
+
/** UI locales */
|
|
34
|
+
uiLocales?: string;
|
|
35
|
+
}
|
|
36
|
+
interface LogoutOptions {
|
|
37
|
+
/** Return URL after logout */
|
|
38
|
+
returnTo?: string;
|
|
39
|
+
/** Whether to logout from the identity provider as well */
|
|
40
|
+
federated?: boolean;
|
|
41
|
+
}
|
|
42
|
+
interface TransactionalAuthContextValue extends TransactionalAuthState {
|
|
43
|
+
/** Redirect to login page */
|
|
44
|
+
loginWithRedirect: (options?: LoginOptions) => Promise<void>;
|
|
45
|
+
/** Open login popup */
|
|
46
|
+
loginWithPopup: (options?: LoginOptions) => Promise<void>;
|
|
47
|
+
/** Logout the user */
|
|
48
|
+
logout: (options?: LogoutOptions) => Promise<void>;
|
|
49
|
+
/** Get the access token (silently refreshes if needed) */
|
|
50
|
+
getAccessToken: () => Promise<string | undefined>;
|
|
51
|
+
/** Get ID token claims */
|
|
52
|
+
getIdTokenClaims: () => Promise<Record<string, unknown> | undefined>;
|
|
53
|
+
/** Check if user has specific permission */
|
|
54
|
+
hasPermission: (permission: string) => boolean;
|
|
55
|
+
/** Check if user has specific role */
|
|
56
|
+
hasRole: (role: string) => boolean;
|
|
57
|
+
}
|
|
58
|
+
interface TransactionalAuthProviderProps {
|
|
59
|
+
/** Auth domain (e.g., 'auth.usetransactional.com' or 'myapp.auth.usetransactional.com') */
|
|
60
|
+
domain: string;
|
|
61
|
+
/** Client ID of your application */
|
|
62
|
+
clientId: string;
|
|
63
|
+
/** Redirect URI after authentication (defaults to window.location.origin) */
|
|
64
|
+
redirectUri?: string;
|
|
65
|
+
/** Scopes to request (defaults to 'openid profile email') */
|
|
66
|
+
scope?: string;
|
|
67
|
+
/** API audience for access token */
|
|
68
|
+
audience?: string;
|
|
69
|
+
/** Where to store tokens: 'memory' or 'localstorage' */
|
|
70
|
+
cacheLocation?: 'memory' | 'localstorage';
|
|
71
|
+
/** Use refresh tokens for silent renewal */
|
|
72
|
+
useRefreshTokens?: boolean;
|
|
73
|
+
/** Callback after redirect from login */
|
|
74
|
+
onRedirectCallback?: (appState?: Record<string, unknown>) => void;
|
|
75
|
+
/** Children components */
|
|
76
|
+
children: React.ReactNode;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* TransactionalAuthProvider Component
|
|
81
|
+
*
|
|
82
|
+
* Provides authentication context to your React application.
|
|
83
|
+
*
|
|
84
|
+
* @example
|
|
85
|
+
* ```tsx
|
|
86
|
+
* import { TransactionalAuthProvider } from 'transactional-auth-react';
|
|
87
|
+
*
|
|
88
|
+
* function App() {
|
|
89
|
+
* return (
|
|
90
|
+
* <TransactionalAuthProvider
|
|
91
|
+
* domain="auth.usetransactional.com"
|
|
92
|
+
* clientId="your-client-id"
|
|
93
|
+
* redirectUri={window.location.origin}
|
|
94
|
+
* >
|
|
95
|
+
* <YourApp />
|
|
96
|
+
* </TransactionalAuthProvider>
|
|
97
|
+
* );
|
|
98
|
+
* }
|
|
99
|
+
* ```
|
|
100
|
+
*/
|
|
101
|
+
declare function TransactionalAuthProvider({ domain, clientId, redirectUri, scope, audience, cacheLocation, useRefreshTokens, onRedirectCallback, children, }: TransactionalAuthProviderProps): react_jsx_runtime.JSX.Element;
|
|
102
|
+
|
|
103
|
+
declare const TransactionalAuthContext: React$1.Context<TransactionalAuthContextValue>;
|
|
104
|
+
|
|
105
|
+
/**
|
|
106
|
+
* Transactional Auth React - useAuth Hook
|
|
107
|
+
*/
|
|
108
|
+
|
|
109
|
+
/**
|
|
110
|
+
* Hook to access authentication state and methods.
|
|
111
|
+
*
|
|
112
|
+
* @example
|
|
113
|
+
* ```tsx
|
|
114
|
+
* import { useAuth } from 'transactional-auth-react';
|
|
115
|
+
*
|
|
116
|
+
* function LoginButton() {
|
|
117
|
+
* const { isAuthenticated, loginWithRedirect, logout, user } = useAuth();
|
|
118
|
+
*
|
|
119
|
+
* if (isAuthenticated) {
|
|
120
|
+
* return (
|
|
121
|
+
* <div>
|
|
122
|
+
* <span>Welcome, {user?.name}</span>
|
|
123
|
+
* <button onClick={() => logout()}>Logout</button>
|
|
124
|
+
* </div>
|
|
125
|
+
* );
|
|
126
|
+
* }
|
|
127
|
+
*
|
|
128
|
+
* return <button onClick={() => loginWithRedirect()}>Login</button>;
|
|
129
|
+
* }
|
|
130
|
+
* ```
|
|
131
|
+
*/
|
|
132
|
+
declare function useAuth(): TransactionalAuthContextValue;
|
|
133
|
+
|
|
134
|
+
/**
|
|
135
|
+
* Transactional Auth React - useUser Hook
|
|
136
|
+
*/
|
|
137
|
+
|
|
138
|
+
interface UseUserResult {
|
|
139
|
+
user: TransactionalAuthUser | null;
|
|
140
|
+
isLoading: boolean;
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Hook to access the current authenticated user.
|
|
144
|
+
*
|
|
145
|
+
* @example
|
|
146
|
+
* ```tsx
|
|
147
|
+
* import { useUser } from 'transactional-auth-react';
|
|
148
|
+
*
|
|
149
|
+
* function Profile() {
|
|
150
|
+
* const { user, isLoading } = useUser();
|
|
151
|
+
*
|
|
152
|
+
* if (isLoading) return <div>Loading...</div>;
|
|
153
|
+
* if (!user) return <div>Not logged in</div>;
|
|
154
|
+
*
|
|
155
|
+
* return (
|
|
156
|
+
* <div>
|
|
157
|
+
* <img src={user.picture} alt={user.name} />
|
|
158
|
+
* <h1>{user.name}</h1>
|
|
159
|
+
* <p>{user.email}</p>
|
|
160
|
+
* </div>
|
|
161
|
+
* );
|
|
162
|
+
* }
|
|
163
|
+
* ```
|
|
164
|
+
*/
|
|
165
|
+
declare function useUser(): UseUserResult;
|
|
166
|
+
|
|
167
|
+
/**
|
|
168
|
+
* Transactional Auth React - useAccessToken Hook
|
|
169
|
+
*/
|
|
170
|
+
/**
|
|
171
|
+
* Hook to get the current access token.
|
|
172
|
+
*
|
|
173
|
+
* @example
|
|
174
|
+
* ```tsx
|
|
175
|
+
* import { useAccessToken } from 'transactional-auth-react';
|
|
176
|
+
*
|
|
177
|
+
* function ApiComponent() {
|
|
178
|
+
* const accessToken = useAccessToken();
|
|
179
|
+
*
|
|
180
|
+
* useEffect(() => {
|
|
181
|
+
* if (accessToken) {
|
|
182
|
+
* fetch('/api/data', {
|
|
183
|
+
* headers: { Authorization: `Bearer ${accessToken}` }
|
|
184
|
+
* });
|
|
185
|
+
* }
|
|
186
|
+
* }, [accessToken]);
|
|
187
|
+
*
|
|
188
|
+
* return <div>...</div>;
|
|
189
|
+
* }
|
|
190
|
+
* ```
|
|
191
|
+
*/
|
|
192
|
+
declare function useAccessToken(): string | null;
|
|
193
|
+
|
|
194
|
+
interface AuthGuardProps {
|
|
195
|
+
/** Content to show while authenticated */
|
|
196
|
+
children: React$1.ReactNode;
|
|
197
|
+
/** Content to show while loading */
|
|
198
|
+
fallback?: React$1.ReactNode;
|
|
199
|
+
/** Callback when user is not authenticated */
|
|
200
|
+
onUnauthenticated?: () => void;
|
|
201
|
+
/** Whether to automatically redirect to login */
|
|
202
|
+
autoRedirect?: boolean;
|
|
203
|
+
}
|
|
204
|
+
/**
|
|
205
|
+
* Component that guards content for authenticated users only.
|
|
206
|
+
*
|
|
207
|
+
* @example
|
|
208
|
+
* ```tsx
|
|
209
|
+
* import { AuthGuard } from 'transactional-auth-react';
|
|
210
|
+
*
|
|
211
|
+
* function ProtectedPage() {
|
|
212
|
+
* return (
|
|
213
|
+
* <AuthGuard fallback={<div>Loading...</div>}>
|
|
214
|
+
* <div>Protected content</div>
|
|
215
|
+
* </AuthGuard>
|
|
216
|
+
* );
|
|
217
|
+
* }
|
|
218
|
+
* ```
|
|
219
|
+
*/
|
|
220
|
+
declare function AuthGuard({ children, fallback, onUnauthenticated, autoRedirect, }: AuthGuardProps): react_jsx_runtime.JSX.Element | null;
|
|
221
|
+
|
|
222
|
+
/**
|
|
223
|
+
* Transactional Auth React - withAuthenticationRequired HOC
|
|
224
|
+
*/
|
|
225
|
+
|
|
226
|
+
interface WithAuthenticationRequiredOptions {
|
|
227
|
+
/** Content to show while loading */
|
|
228
|
+
fallback?: React$1.ReactNode;
|
|
229
|
+
/** Callback when user is not authenticated */
|
|
230
|
+
onUnauthenticated?: () => void;
|
|
231
|
+
/** Return URL after login */
|
|
232
|
+
returnTo?: string;
|
|
233
|
+
}
|
|
234
|
+
/**
|
|
235
|
+
* Higher-order component that wraps a component to require authentication.
|
|
236
|
+
*
|
|
237
|
+
* @example
|
|
238
|
+
* ```tsx
|
|
239
|
+
* import { withAuthenticationRequired } from 'transactional-auth-react';
|
|
240
|
+
*
|
|
241
|
+
* function Dashboard() {
|
|
242
|
+
* return <div>Dashboard content</div>;
|
|
243
|
+
* }
|
|
244
|
+
*
|
|
245
|
+
* export default withAuthenticationRequired(Dashboard, {
|
|
246
|
+
* fallback: <div>Loading...</div>,
|
|
247
|
+
* });
|
|
248
|
+
* ```
|
|
249
|
+
*/
|
|
250
|
+
declare function withAuthenticationRequired<P extends object>(Component: React$1.ComponentType<P>, options?: WithAuthenticationRequiredOptions): React$1.ComponentType<P>;
|
|
251
|
+
|
|
252
|
+
export { AuthGuard, type LoginOptions, type LogoutOptions, TransactionalAuthContext, type TransactionalAuthContextValue, TransactionalAuthProvider, type TransactionalAuthProviderProps, type TransactionalAuthState, type TransactionalAuthUser, useAccessToken, useAuth, useUser, withAuthenticationRequired };
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,333 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __create = Object.create;
|
|
3
|
+
var __defProp = Object.defineProperty;
|
|
4
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
5
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
6
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
7
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
8
|
+
var __export = (target, all) => {
|
|
9
|
+
for (var name in all)
|
|
10
|
+
__defProp(target, name, { get: all[name], enumerable: true });
|
|
11
|
+
};
|
|
12
|
+
var __copyProps = (to, from, except, desc) => {
|
|
13
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
14
|
+
for (let key of __getOwnPropNames(from))
|
|
15
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
16
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
17
|
+
}
|
|
18
|
+
return to;
|
|
19
|
+
};
|
|
20
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
21
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
22
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
23
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
24
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
25
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
26
|
+
mod
|
|
27
|
+
));
|
|
28
|
+
var __toCommonJS = (mod) => __copyProps(__defProp({}, "__esModule", { value: true }), mod);
|
|
29
|
+
|
|
30
|
+
// src/index.ts
|
|
31
|
+
var index_exports = {};
|
|
32
|
+
__export(index_exports, {
|
|
33
|
+
AuthGuard: () => AuthGuard,
|
|
34
|
+
TransactionalAuthContext: () => TransactionalAuthContext,
|
|
35
|
+
TransactionalAuthProvider: () => TransactionalAuthProvider,
|
|
36
|
+
useAccessToken: () => useAccessToken,
|
|
37
|
+
useAuth: () => useAuth,
|
|
38
|
+
useUser: () => useUser,
|
|
39
|
+
withAuthenticationRequired: () => withAuthenticationRequired
|
|
40
|
+
});
|
|
41
|
+
module.exports = __toCommonJS(index_exports);
|
|
42
|
+
|
|
43
|
+
// src/TransactionalAuthProvider.tsx
|
|
44
|
+
var React = __toESM(require("react"));
|
|
45
|
+
var import_oidc_client_ts = require("oidc-client-ts");
|
|
46
|
+
|
|
47
|
+
// src/context.ts
|
|
48
|
+
var import_react = require("react");
|
|
49
|
+
var defaultContext = {
|
|
50
|
+
isAuthenticated: false,
|
|
51
|
+
isLoading: true,
|
|
52
|
+
user: null,
|
|
53
|
+
error: null,
|
|
54
|
+
loginWithRedirect: async () => {
|
|
55
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
56
|
+
},
|
|
57
|
+
loginWithPopup: async () => {
|
|
58
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
59
|
+
},
|
|
60
|
+
logout: async () => {
|
|
61
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
62
|
+
},
|
|
63
|
+
getAccessToken: async () => {
|
|
64
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
65
|
+
},
|
|
66
|
+
getIdTokenClaims: async () => {
|
|
67
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
68
|
+
},
|
|
69
|
+
hasPermission: () => false,
|
|
70
|
+
hasRole: () => false
|
|
71
|
+
};
|
|
72
|
+
var TransactionalAuthContext = (0, import_react.createContext)(defaultContext);
|
|
73
|
+
|
|
74
|
+
// src/TransactionalAuthProvider.tsx
|
|
75
|
+
var import_jsx_runtime = require("react/jsx-runtime");
|
|
76
|
+
function mapUser(user) {
|
|
77
|
+
return {
|
|
78
|
+
sub: user.profile.sub,
|
|
79
|
+
email: user.profile.email,
|
|
80
|
+
emailVerified: user.profile.email_verified,
|
|
81
|
+
name: user.profile.name,
|
|
82
|
+
givenName: user.profile.given_name,
|
|
83
|
+
familyName: user.profile.family_name,
|
|
84
|
+
picture: user.profile.picture,
|
|
85
|
+
phoneNumber: user.profile.phone_number,
|
|
86
|
+
phoneNumberVerified: user.profile.phone_number_verified
|
|
87
|
+
};
|
|
88
|
+
}
|
|
89
|
+
function TransactionalAuthProvider({
|
|
90
|
+
domain,
|
|
91
|
+
clientId,
|
|
92
|
+
redirectUri,
|
|
93
|
+
scope = "openid profile email",
|
|
94
|
+
audience,
|
|
95
|
+
cacheLocation = "localstorage",
|
|
96
|
+
useRefreshTokens = true,
|
|
97
|
+
onRedirectCallback,
|
|
98
|
+
children
|
|
99
|
+
}) {
|
|
100
|
+
const [state, setState] = React.useState({
|
|
101
|
+
isAuthenticated: false,
|
|
102
|
+
isLoading: true,
|
|
103
|
+
user: null,
|
|
104
|
+
error: null
|
|
105
|
+
});
|
|
106
|
+
const userManagerRef = React.useRef(null);
|
|
107
|
+
const [permissions, setPermissions] = React.useState([]);
|
|
108
|
+
const [roles, setRoles] = React.useState([]);
|
|
109
|
+
React.useEffect(() => {
|
|
110
|
+
const effectiveRedirectUri = redirectUri || (typeof window !== "undefined" ? window.location.origin : "");
|
|
111
|
+
const effectiveScope = useRefreshTokens ? `${scope} offline_access` : scope;
|
|
112
|
+
const manager = new import_oidc_client_ts.UserManager({
|
|
113
|
+
authority: `https://${domain}`,
|
|
114
|
+
client_id: clientId,
|
|
115
|
+
redirect_uri: effectiveRedirectUri,
|
|
116
|
+
post_logout_redirect_uri: effectiveRedirectUri,
|
|
117
|
+
scope: effectiveScope,
|
|
118
|
+
response_type: "code",
|
|
119
|
+
automaticSilentRenew: useRefreshTokens,
|
|
120
|
+
userStore: typeof window !== "undefined" ? cacheLocation === "localstorage" ? new import_oidc_client_ts.WebStorageStateStore({ store: window.localStorage }) : new import_oidc_client_ts.WebStorageStateStore({ store: window.sessionStorage }) : void 0,
|
|
121
|
+
extraQueryParams: audience ? { audience } : void 0
|
|
122
|
+
});
|
|
123
|
+
userManagerRef.current = manager;
|
|
124
|
+
manager.getUser().then((user) => {
|
|
125
|
+
if (user && !user.expired) {
|
|
126
|
+
setState({
|
|
127
|
+
isAuthenticated: true,
|
|
128
|
+
isLoading: false,
|
|
129
|
+
user: mapUser(user),
|
|
130
|
+
error: null
|
|
131
|
+
});
|
|
132
|
+
const accessToken = user.access_token;
|
|
133
|
+
if (accessToken) {
|
|
134
|
+
try {
|
|
135
|
+
const payload = JSON.parse(atob(accessToken.split(".")[1]));
|
|
136
|
+
setRoles(payload.roles || []);
|
|
137
|
+
setPermissions(payload.permissions || []);
|
|
138
|
+
} catch {
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
} else {
|
|
142
|
+
setState((s) => ({ ...s, isLoading: false }));
|
|
143
|
+
}
|
|
144
|
+
}).catch((error) => {
|
|
145
|
+
setState((s) => ({ ...s, isLoading: false, error }));
|
|
146
|
+
});
|
|
147
|
+
if (typeof window !== "undefined") {
|
|
148
|
+
const params = new URLSearchParams(window.location.search);
|
|
149
|
+
if (params.has("code") || params.has("error")) {
|
|
150
|
+
manager.signinRedirectCallback().then((user) => {
|
|
151
|
+
setState({
|
|
152
|
+
isAuthenticated: true,
|
|
153
|
+
isLoading: false,
|
|
154
|
+
user: mapUser(user),
|
|
155
|
+
error: null
|
|
156
|
+
});
|
|
157
|
+
onRedirectCallback?.(user.state);
|
|
158
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
159
|
+
}).catch((error) => {
|
|
160
|
+
setState((s) => ({ ...s, isLoading: false, error }));
|
|
161
|
+
});
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
const handleUserLoaded = (user) => {
|
|
165
|
+
setState({
|
|
166
|
+
isAuthenticated: true,
|
|
167
|
+
isLoading: false,
|
|
168
|
+
user: mapUser(user),
|
|
169
|
+
error: null
|
|
170
|
+
});
|
|
171
|
+
};
|
|
172
|
+
const handleUserUnloaded = () => {
|
|
173
|
+
setState({
|
|
174
|
+
isAuthenticated: false,
|
|
175
|
+
isLoading: false,
|
|
176
|
+
user: null,
|
|
177
|
+
error: null
|
|
178
|
+
});
|
|
179
|
+
setRoles([]);
|
|
180
|
+
setPermissions([]);
|
|
181
|
+
};
|
|
182
|
+
const handleSilentRenewError = (error) => {
|
|
183
|
+
console.error("Silent renew error:", error);
|
|
184
|
+
};
|
|
185
|
+
manager.events.addUserLoaded(handleUserLoaded);
|
|
186
|
+
manager.events.addUserUnloaded(handleUserUnloaded);
|
|
187
|
+
manager.events.addSilentRenewError(handleSilentRenewError);
|
|
188
|
+
return () => {
|
|
189
|
+
manager.events.removeUserLoaded(handleUserLoaded);
|
|
190
|
+
manager.events.removeUserUnloaded(handleUserUnloaded);
|
|
191
|
+
manager.events.removeSilentRenewError(handleSilentRenewError);
|
|
192
|
+
};
|
|
193
|
+
}, [domain, clientId, redirectUri, scope, audience, cacheLocation, useRefreshTokens, onRedirectCallback]);
|
|
194
|
+
const contextValue = React.useMemo(
|
|
195
|
+
() => ({
|
|
196
|
+
...state,
|
|
197
|
+
loginWithRedirect: async (options) => {
|
|
198
|
+
await userManagerRef.current?.signinRedirect({
|
|
199
|
+
state: options?.appState,
|
|
200
|
+
extraQueryParams: {
|
|
201
|
+
...options?.connection ? { connection: options.connection } : {},
|
|
202
|
+
...options?.loginHint ? { login_hint: options.loginHint } : {},
|
|
203
|
+
...options?.uiLocales ? { ui_locales: options.uiLocales } : {}
|
|
204
|
+
},
|
|
205
|
+
redirect_uri: options?.returnTo
|
|
206
|
+
});
|
|
207
|
+
},
|
|
208
|
+
loginWithPopup: async (options) => {
|
|
209
|
+
const user = await userManagerRef.current?.signinPopup({
|
|
210
|
+
extraQueryParams: {
|
|
211
|
+
...options?.connection ? { connection: options.connection } : {},
|
|
212
|
+
...options?.loginHint ? { login_hint: options.loginHint } : {}
|
|
213
|
+
}
|
|
214
|
+
});
|
|
215
|
+
if (user) {
|
|
216
|
+
setState({
|
|
217
|
+
isAuthenticated: true,
|
|
218
|
+
isLoading: false,
|
|
219
|
+
user: mapUser(user),
|
|
220
|
+
error: null
|
|
221
|
+
});
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
logout: async (options) => {
|
|
225
|
+
await userManagerRef.current?.signoutRedirect({
|
|
226
|
+
post_logout_redirect_uri: options?.returnTo
|
|
227
|
+
});
|
|
228
|
+
},
|
|
229
|
+
getAccessToken: async () => {
|
|
230
|
+
const user = await userManagerRef.current?.getUser();
|
|
231
|
+
return user?.access_token;
|
|
232
|
+
},
|
|
233
|
+
getIdTokenClaims: async () => {
|
|
234
|
+
const user = await userManagerRef.current?.getUser();
|
|
235
|
+
return user?.profile;
|
|
236
|
+
},
|
|
237
|
+
hasPermission: (permission) => {
|
|
238
|
+
return permissions.includes(permission);
|
|
239
|
+
},
|
|
240
|
+
hasRole: (role) => {
|
|
241
|
+
return roles.includes(role);
|
|
242
|
+
}
|
|
243
|
+
}),
|
|
244
|
+
[state, permissions, roles]
|
|
245
|
+
);
|
|
246
|
+
return /* @__PURE__ */ (0, import_jsx_runtime.jsx)(TransactionalAuthContext.Provider, { value: contextValue, children });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
// src/hooks/useAuth.ts
|
|
250
|
+
var import_react2 = require("react");
|
|
251
|
+
function useAuth() {
|
|
252
|
+
const context = (0, import_react2.useContext)(TransactionalAuthContext);
|
|
253
|
+
if (!context) {
|
|
254
|
+
throw new Error("useAuth must be used within a TransactionalAuthProvider");
|
|
255
|
+
}
|
|
256
|
+
return context;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
// src/hooks/useUser.ts
|
|
260
|
+
function useUser() {
|
|
261
|
+
const { user, isLoading } = useAuth();
|
|
262
|
+
return { user, isLoading };
|
|
263
|
+
}
|
|
264
|
+
|
|
265
|
+
// src/hooks/useAccessToken.ts
|
|
266
|
+
var import_react3 = require("react");
|
|
267
|
+
function useAccessToken() {
|
|
268
|
+
const { getAccessToken, isAuthenticated } = useAuth();
|
|
269
|
+
const [token, setToken] = (0, import_react3.useState)(null);
|
|
270
|
+
(0, import_react3.useEffect)(() => {
|
|
271
|
+
if (isAuthenticated) {
|
|
272
|
+
getAccessToken().then((t) => setToken(t || null));
|
|
273
|
+
} else {
|
|
274
|
+
setToken(null);
|
|
275
|
+
}
|
|
276
|
+
}, [getAccessToken, isAuthenticated]);
|
|
277
|
+
return token;
|
|
278
|
+
}
|
|
279
|
+
|
|
280
|
+
// src/components/AuthGuard.tsx
|
|
281
|
+
var React2 = __toESM(require("react"));
|
|
282
|
+
var import_jsx_runtime2 = require("react/jsx-runtime");
|
|
283
|
+
function AuthGuard({
|
|
284
|
+
children,
|
|
285
|
+
fallback,
|
|
286
|
+
onUnauthenticated,
|
|
287
|
+
autoRedirect = true
|
|
288
|
+
}) {
|
|
289
|
+
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth();
|
|
290
|
+
React2.useEffect(() => {
|
|
291
|
+
if (!isLoading && !isAuthenticated) {
|
|
292
|
+
if (onUnauthenticated) {
|
|
293
|
+
onUnauthenticated();
|
|
294
|
+
} else if (autoRedirect && typeof window !== "undefined") {
|
|
295
|
+
loginWithRedirect({ returnTo: window.location.pathname });
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
}, [isLoading, isAuthenticated, onUnauthenticated, autoRedirect, loginWithRedirect]);
|
|
299
|
+
if (isLoading) {
|
|
300
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children: fallback ?? null });
|
|
301
|
+
}
|
|
302
|
+
if (!isAuthenticated) {
|
|
303
|
+
return null;
|
|
304
|
+
}
|
|
305
|
+
return /* @__PURE__ */ (0, import_jsx_runtime2.jsx)(import_jsx_runtime2.Fragment, { children });
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
// src/components/withAuthenticationRequired.tsx
|
|
309
|
+
var import_jsx_runtime3 = require("react/jsx-runtime");
|
|
310
|
+
function withAuthenticationRequired(Component, options) {
|
|
311
|
+
const WrappedComponent = function WithAuthenticationRequired(props) {
|
|
312
|
+
return /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(
|
|
313
|
+
AuthGuard,
|
|
314
|
+
{
|
|
315
|
+
fallback: options?.fallback,
|
|
316
|
+
onUnauthenticated: options?.onUnauthenticated,
|
|
317
|
+
children: /* @__PURE__ */ (0, import_jsx_runtime3.jsx)(Component, { ...props })
|
|
318
|
+
}
|
|
319
|
+
);
|
|
320
|
+
};
|
|
321
|
+
WrappedComponent.displayName = `withAuthenticationRequired(${Component.displayName || Component.name || "Component"})`;
|
|
322
|
+
return WrappedComponent;
|
|
323
|
+
}
|
|
324
|
+
// Annotate the CommonJS export names for ESM import in node:
|
|
325
|
+
0 && (module.exports = {
|
|
326
|
+
AuthGuard,
|
|
327
|
+
TransactionalAuthContext,
|
|
328
|
+
TransactionalAuthProvider,
|
|
329
|
+
useAccessToken,
|
|
330
|
+
useAuth,
|
|
331
|
+
useUser,
|
|
332
|
+
withAuthenticationRequired
|
|
333
|
+
});
|
package/dist/index.mjs
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
// src/TransactionalAuthProvider.tsx
|
|
2
|
+
import * as React from "react";
|
|
3
|
+
import { UserManager, WebStorageStateStore } from "oidc-client-ts";
|
|
4
|
+
|
|
5
|
+
// src/context.ts
|
|
6
|
+
import { createContext } from "react";
|
|
7
|
+
var defaultContext = {
|
|
8
|
+
isAuthenticated: false,
|
|
9
|
+
isLoading: true,
|
|
10
|
+
user: null,
|
|
11
|
+
error: null,
|
|
12
|
+
loginWithRedirect: async () => {
|
|
13
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
14
|
+
},
|
|
15
|
+
loginWithPopup: async () => {
|
|
16
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
17
|
+
},
|
|
18
|
+
logout: async () => {
|
|
19
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
20
|
+
},
|
|
21
|
+
getAccessToken: async () => {
|
|
22
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
23
|
+
},
|
|
24
|
+
getIdTokenClaims: async () => {
|
|
25
|
+
throw new Error("TransactionalAuthProvider not found");
|
|
26
|
+
},
|
|
27
|
+
hasPermission: () => false,
|
|
28
|
+
hasRole: () => false
|
|
29
|
+
};
|
|
30
|
+
var TransactionalAuthContext = createContext(defaultContext);
|
|
31
|
+
|
|
32
|
+
// src/TransactionalAuthProvider.tsx
|
|
33
|
+
import { jsx } from "react/jsx-runtime";
|
|
34
|
+
function mapUser(user) {
|
|
35
|
+
return {
|
|
36
|
+
sub: user.profile.sub,
|
|
37
|
+
email: user.profile.email,
|
|
38
|
+
emailVerified: user.profile.email_verified,
|
|
39
|
+
name: user.profile.name,
|
|
40
|
+
givenName: user.profile.given_name,
|
|
41
|
+
familyName: user.profile.family_name,
|
|
42
|
+
picture: user.profile.picture,
|
|
43
|
+
phoneNumber: user.profile.phone_number,
|
|
44
|
+
phoneNumberVerified: user.profile.phone_number_verified
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
function TransactionalAuthProvider({
|
|
48
|
+
domain,
|
|
49
|
+
clientId,
|
|
50
|
+
redirectUri,
|
|
51
|
+
scope = "openid profile email",
|
|
52
|
+
audience,
|
|
53
|
+
cacheLocation = "localstorage",
|
|
54
|
+
useRefreshTokens = true,
|
|
55
|
+
onRedirectCallback,
|
|
56
|
+
children
|
|
57
|
+
}) {
|
|
58
|
+
const [state, setState] = React.useState({
|
|
59
|
+
isAuthenticated: false,
|
|
60
|
+
isLoading: true,
|
|
61
|
+
user: null,
|
|
62
|
+
error: null
|
|
63
|
+
});
|
|
64
|
+
const userManagerRef = React.useRef(null);
|
|
65
|
+
const [permissions, setPermissions] = React.useState([]);
|
|
66
|
+
const [roles, setRoles] = React.useState([]);
|
|
67
|
+
React.useEffect(() => {
|
|
68
|
+
const effectiveRedirectUri = redirectUri || (typeof window !== "undefined" ? window.location.origin : "");
|
|
69
|
+
const effectiveScope = useRefreshTokens ? `${scope} offline_access` : scope;
|
|
70
|
+
const manager = new UserManager({
|
|
71
|
+
authority: `https://${domain}`,
|
|
72
|
+
client_id: clientId,
|
|
73
|
+
redirect_uri: effectiveRedirectUri,
|
|
74
|
+
post_logout_redirect_uri: effectiveRedirectUri,
|
|
75
|
+
scope: effectiveScope,
|
|
76
|
+
response_type: "code",
|
|
77
|
+
automaticSilentRenew: useRefreshTokens,
|
|
78
|
+
userStore: typeof window !== "undefined" ? cacheLocation === "localstorage" ? new WebStorageStateStore({ store: window.localStorage }) : new WebStorageStateStore({ store: window.sessionStorage }) : void 0,
|
|
79
|
+
extraQueryParams: audience ? { audience } : void 0
|
|
80
|
+
});
|
|
81
|
+
userManagerRef.current = manager;
|
|
82
|
+
manager.getUser().then((user) => {
|
|
83
|
+
if (user && !user.expired) {
|
|
84
|
+
setState({
|
|
85
|
+
isAuthenticated: true,
|
|
86
|
+
isLoading: false,
|
|
87
|
+
user: mapUser(user),
|
|
88
|
+
error: null
|
|
89
|
+
});
|
|
90
|
+
const accessToken = user.access_token;
|
|
91
|
+
if (accessToken) {
|
|
92
|
+
try {
|
|
93
|
+
const payload = JSON.parse(atob(accessToken.split(".")[1]));
|
|
94
|
+
setRoles(payload.roles || []);
|
|
95
|
+
setPermissions(payload.permissions || []);
|
|
96
|
+
} catch {
|
|
97
|
+
}
|
|
98
|
+
}
|
|
99
|
+
} else {
|
|
100
|
+
setState((s) => ({ ...s, isLoading: false }));
|
|
101
|
+
}
|
|
102
|
+
}).catch((error) => {
|
|
103
|
+
setState((s) => ({ ...s, isLoading: false, error }));
|
|
104
|
+
});
|
|
105
|
+
if (typeof window !== "undefined") {
|
|
106
|
+
const params = new URLSearchParams(window.location.search);
|
|
107
|
+
if (params.has("code") || params.has("error")) {
|
|
108
|
+
manager.signinRedirectCallback().then((user) => {
|
|
109
|
+
setState({
|
|
110
|
+
isAuthenticated: true,
|
|
111
|
+
isLoading: false,
|
|
112
|
+
user: mapUser(user),
|
|
113
|
+
error: null
|
|
114
|
+
});
|
|
115
|
+
onRedirectCallback?.(user.state);
|
|
116
|
+
window.history.replaceState({}, "", window.location.pathname);
|
|
117
|
+
}).catch((error) => {
|
|
118
|
+
setState((s) => ({ ...s, isLoading: false, error }));
|
|
119
|
+
});
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const handleUserLoaded = (user) => {
|
|
123
|
+
setState({
|
|
124
|
+
isAuthenticated: true,
|
|
125
|
+
isLoading: false,
|
|
126
|
+
user: mapUser(user),
|
|
127
|
+
error: null
|
|
128
|
+
});
|
|
129
|
+
};
|
|
130
|
+
const handleUserUnloaded = () => {
|
|
131
|
+
setState({
|
|
132
|
+
isAuthenticated: false,
|
|
133
|
+
isLoading: false,
|
|
134
|
+
user: null,
|
|
135
|
+
error: null
|
|
136
|
+
});
|
|
137
|
+
setRoles([]);
|
|
138
|
+
setPermissions([]);
|
|
139
|
+
};
|
|
140
|
+
const handleSilentRenewError = (error) => {
|
|
141
|
+
console.error("Silent renew error:", error);
|
|
142
|
+
};
|
|
143
|
+
manager.events.addUserLoaded(handleUserLoaded);
|
|
144
|
+
manager.events.addUserUnloaded(handleUserUnloaded);
|
|
145
|
+
manager.events.addSilentRenewError(handleSilentRenewError);
|
|
146
|
+
return () => {
|
|
147
|
+
manager.events.removeUserLoaded(handleUserLoaded);
|
|
148
|
+
manager.events.removeUserUnloaded(handleUserUnloaded);
|
|
149
|
+
manager.events.removeSilentRenewError(handleSilentRenewError);
|
|
150
|
+
};
|
|
151
|
+
}, [domain, clientId, redirectUri, scope, audience, cacheLocation, useRefreshTokens, onRedirectCallback]);
|
|
152
|
+
const contextValue = React.useMemo(
|
|
153
|
+
() => ({
|
|
154
|
+
...state,
|
|
155
|
+
loginWithRedirect: async (options) => {
|
|
156
|
+
await userManagerRef.current?.signinRedirect({
|
|
157
|
+
state: options?.appState,
|
|
158
|
+
extraQueryParams: {
|
|
159
|
+
...options?.connection ? { connection: options.connection } : {},
|
|
160
|
+
...options?.loginHint ? { login_hint: options.loginHint } : {},
|
|
161
|
+
...options?.uiLocales ? { ui_locales: options.uiLocales } : {}
|
|
162
|
+
},
|
|
163
|
+
redirect_uri: options?.returnTo
|
|
164
|
+
});
|
|
165
|
+
},
|
|
166
|
+
loginWithPopup: async (options) => {
|
|
167
|
+
const user = await userManagerRef.current?.signinPopup({
|
|
168
|
+
extraQueryParams: {
|
|
169
|
+
...options?.connection ? { connection: options.connection } : {},
|
|
170
|
+
...options?.loginHint ? { login_hint: options.loginHint } : {}
|
|
171
|
+
}
|
|
172
|
+
});
|
|
173
|
+
if (user) {
|
|
174
|
+
setState({
|
|
175
|
+
isAuthenticated: true,
|
|
176
|
+
isLoading: false,
|
|
177
|
+
user: mapUser(user),
|
|
178
|
+
error: null
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
},
|
|
182
|
+
logout: async (options) => {
|
|
183
|
+
await userManagerRef.current?.signoutRedirect({
|
|
184
|
+
post_logout_redirect_uri: options?.returnTo
|
|
185
|
+
});
|
|
186
|
+
},
|
|
187
|
+
getAccessToken: async () => {
|
|
188
|
+
const user = await userManagerRef.current?.getUser();
|
|
189
|
+
return user?.access_token;
|
|
190
|
+
},
|
|
191
|
+
getIdTokenClaims: async () => {
|
|
192
|
+
const user = await userManagerRef.current?.getUser();
|
|
193
|
+
return user?.profile;
|
|
194
|
+
},
|
|
195
|
+
hasPermission: (permission) => {
|
|
196
|
+
return permissions.includes(permission);
|
|
197
|
+
},
|
|
198
|
+
hasRole: (role) => {
|
|
199
|
+
return roles.includes(role);
|
|
200
|
+
}
|
|
201
|
+
}),
|
|
202
|
+
[state, permissions, roles]
|
|
203
|
+
);
|
|
204
|
+
return /* @__PURE__ */ jsx(TransactionalAuthContext.Provider, { value: contextValue, children });
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
// src/hooks/useAuth.ts
|
|
208
|
+
import { useContext } from "react";
|
|
209
|
+
function useAuth() {
|
|
210
|
+
const context = useContext(TransactionalAuthContext);
|
|
211
|
+
if (!context) {
|
|
212
|
+
throw new Error("useAuth must be used within a TransactionalAuthProvider");
|
|
213
|
+
}
|
|
214
|
+
return context;
|
|
215
|
+
}
|
|
216
|
+
|
|
217
|
+
// src/hooks/useUser.ts
|
|
218
|
+
function useUser() {
|
|
219
|
+
const { user, isLoading } = useAuth();
|
|
220
|
+
return { user, isLoading };
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
// src/hooks/useAccessToken.ts
|
|
224
|
+
import { useState as useState2, useEffect as useEffect2 } from "react";
|
|
225
|
+
function useAccessToken() {
|
|
226
|
+
const { getAccessToken, isAuthenticated } = useAuth();
|
|
227
|
+
const [token, setToken] = useState2(null);
|
|
228
|
+
useEffect2(() => {
|
|
229
|
+
if (isAuthenticated) {
|
|
230
|
+
getAccessToken().then((t) => setToken(t || null));
|
|
231
|
+
} else {
|
|
232
|
+
setToken(null);
|
|
233
|
+
}
|
|
234
|
+
}, [getAccessToken, isAuthenticated]);
|
|
235
|
+
return token;
|
|
236
|
+
}
|
|
237
|
+
|
|
238
|
+
// src/components/AuthGuard.tsx
|
|
239
|
+
import * as React2 from "react";
|
|
240
|
+
import { Fragment, jsx as jsx2 } from "react/jsx-runtime";
|
|
241
|
+
function AuthGuard({
|
|
242
|
+
children,
|
|
243
|
+
fallback,
|
|
244
|
+
onUnauthenticated,
|
|
245
|
+
autoRedirect = true
|
|
246
|
+
}) {
|
|
247
|
+
const { isAuthenticated, isLoading, loginWithRedirect } = useAuth();
|
|
248
|
+
React2.useEffect(() => {
|
|
249
|
+
if (!isLoading && !isAuthenticated) {
|
|
250
|
+
if (onUnauthenticated) {
|
|
251
|
+
onUnauthenticated();
|
|
252
|
+
} else if (autoRedirect && typeof window !== "undefined") {
|
|
253
|
+
loginWithRedirect({ returnTo: window.location.pathname });
|
|
254
|
+
}
|
|
255
|
+
}
|
|
256
|
+
}, [isLoading, isAuthenticated, onUnauthenticated, autoRedirect, loginWithRedirect]);
|
|
257
|
+
if (isLoading) {
|
|
258
|
+
return /* @__PURE__ */ jsx2(Fragment, { children: fallback ?? null });
|
|
259
|
+
}
|
|
260
|
+
if (!isAuthenticated) {
|
|
261
|
+
return null;
|
|
262
|
+
}
|
|
263
|
+
return /* @__PURE__ */ jsx2(Fragment, { children });
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
// src/components/withAuthenticationRequired.tsx
|
|
267
|
+
import { jsx as jsx3 } from "react/jsx-runtime";
|
|
268
|
+
function withAuthenticationRequired(Component, options) {
|
|
269
|
+
const WrappedComponent = function WithAuthenticationRequired(props) {
|
|
270
|
+
return /* @__PURE__ */ jsx3(
|
|
271
|
+
AuthGuard,
|
|
272
|
+
{
|
|
273
|
+
fallback: options?.fallback,
|
|
274
|
+
onUnauthenticated: options?.onUnauthenticated,
|
|
275
|
+
children: /* @__PURE__ */ jsx3(Component, { ...props })
|
|
276
|
+
}
|
|
277
|
+
);
|
|
278
|
+
};
|
|
279
|
+
WrappedComponent.displayName = `withAuthenticationRequired(${Component.displayName || Component.name || "Component"})`;
|
|
280
|
+
return WrappedComponent;
|
|
281
|
+
}
|
|
282
|
+
export {
|
|
283
|
+
AuthGuard,
|
|
284
|
+
TransactionalAuthContext,
|
|
285
|
+
TransactionalAuthProvider,
|
|
286
|
+
useAccessToken,
|
|
287
|
+
useAuth,
|
|
288
|
+
useUser,
|
|
289
|
+
withAuthenticationRequired
|
|
290
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "transactional-auth-react",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "React SDK for Transactional Auth - OpenID Connect authentication for React applications",
|
|
5
|
+
"main": "dist/index.js",
|
|
6
|
+
"module": "dist/index.mjs",
|
|
7
|
+
"types": "dist/index.d.ts",
|
|
8
|
+
"exports": {
|
|
9
|
+
".": {
|
|
10
|
+
"import": "./dist/index.mjs",
|
|
11
|
+
"require": "./dist/index.js",
|
|
12
|
+
"types": "./dist/index.d.ts"
|
|
13
|
+
}
|
|
14
|
+
},
|
|
15
|
+
"sideEffects": false,
|
|
16
|
+
"files": [
|
|
17
|
+
"dist",
|
|
18
|
+
"README.md"
|
|
19
|
+
],
|
|
20
|
+
"scripts": {
|
|
21
|
+
"build": "tsup src/index.ts --format cjs,esm --dts --clean",
|
|
22
|
+
"dev": "tsup src/index.ts --format cjs,esm --dts --watch",
|
|
23
|
+
"typecheck": "tsc --noEmit",
|
|
24
|
+
"lint": "eslint src --ext .ts,.tsx",
|
|
25
|
+
"prepublishOnly": "npm run build"
|
|
26
|
+
},
|
|
27
|
+
"keywords": [
|
|
28
|
+
"react",
|
|
29
|
+
"auth",
|
|
30
|
+
"authentication",
|
|
31
|
+
"oidc",
|
|
32
|
+
"openid-connect",
|
|
33
|
+
"oauth2",
|
|
34
|
+
"transactional"
|
|
35
|
+
],
|
|
36
|
+
"author": "Transactional",
|
|
37
|
+
"license": "MIT",
|
|
38
|
+
"repository": {
|
|
39
|
+
"type": "git",
|
|
40
|
+
"url": "https://github.com/TransactionalHQ/transactional-auth-react"
|
|
41
|
+
},
|
|
42
|
+
"homepage": "https://github.com/TransactionalHQ/transactional-auth-react#readme",
|
|
43
|
+
"bugs": {
|
|
44
|
+
"url": "https://github.com/TransactionalHQ/transactional-auth-react/issues"
|
|
45
|
+
},
|
|
46
|
+
"peerDependencies": {
|
|
47
|
+
"react": "^18.0.0 || ^19.0.0"
|
|
48
|
+
},
|
|
49
|
+
"dependencies": {
|
|
50
|
+
"oidc-client-ts": "^3.0.1"
|
|
51
|
+
},
|
|
52
|
+
"devDependencies": {
|
|
53
|
+
"@types/react": "^18.2.0",
|
|
54
|
+
"react": "^18.2.0",
|
|
55
|
+
"tsup": "^8.0.0",
|
|
56
|
+
"typescript": "^5.5.0"
|
|
57
|
+
}
|
|
58
|
+
}
|