@mspkapps/auth-client 0.1.21 → 0.1.23

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.
Files changed (3) hide show
  1. package/README.md +242 -1972
  2. package/package.json +1 -1
  3. package/src/AuthClient.js +81 -1
package/README.md CHANGED
@@ -1,2106 +1,376 @@
1
- # Auth Server Demo - Complete Documentation
2
-
3
- A beginner-friendly guide to implementing authentication using the `@mspkapps/auth-client` package with **React Web** and **React Native**.
4
-
5
- ## Table of Contents
6
- - [Overview](#overview)
7
- - [Installation](#installation)
8
- - [React Web](#react-web-setup)
9
- - [React Native](#react-native-setup)
10
- - [Setup](#setup)
11
- - [React Web](#react-web-1)
12
- - [React Native](#react-native-1)
13
- - [Available Functions](#available-functions)
14
- - [Implementation Examples](#implementation-examples)
15
- - [React Web Examples](#react-web-examples)
16
- - [React Native Examples](#react-native-examples)
17
- - [Common Patterns](#common-patterns)
18
- - [Troubleshooting](#troubleshooting)
1
+ # MSPK™ Auth Client (`@mspkapps/auth-client`)
19
2
 
20
- ---
21
-
22
- ## Overview
23
-
24
- This demo project showcases how to integrate the `@mspkapps/auth-client` authentication package in a React application. It includes:
3
+ Simple backend SDK for the MSPK™ Auth Platform.
4
+ Use it from your server code to handle login, register, Google auth, password flows, and profile operations with a few lines.
25
5
 
26
- - ✅ Email/Password Authentication
27
- - ✅ Google Sign-In (OAuth)
28
- - ✅ User Registration
29
- - ✅ Password Reset Flow
30
- - ✅ Email Verification
31
- - ✅ Account Management (Delete Account)
32
- - ✅ Protected User Profile
6
+ - ✅ Email/password login & register
7
+ - ✅ Google OAuth login
8
+ - ✅ Password reset & change flows
9
+ - ✅ Email verification / resend flows
10
+ - ✅ Account delete
11
+ - ✅ Profile read & update
12
+ - ✅ Simple singleton API: `authclient.init(...)` then `authclient.login(...)`
33
13
 
34
14
  ---
35
15
 
36
16
  ## Installation
37
17
 
38
- ### React Web Setup
39
-
40
- #### 1. Create a New React Project (with Vite)
41
-
42
- ```bash
43
- npm create vite@latest my-auth-app -- --template react
44
- cd my-auth-app
45
- ```
46
-
47
- #### 2. Install Required Dependencies
48
-
49
18
  ```bash
50
- npm install @mspkapps/auth-client @react-oauth/google
51
- ```
52
-
53
- ### React Native Setup
54
-
55
- #### 1. Create a New React Native Project
56
-
57
- ```bash
58
- # Using Expo (recommended for beginners)
59
- npx create-expo-app my-auth-app
60
- cd my-auth-app
61
-
62
- # Or using React Native CLI
63
- npx react-native init my-auth-app
64
- cd my-auth-app
65
- ```
66
-
67
- #### 2. Install Required Dependencies
68
-
69
- ```bash
70
- # For Expo
71
- npx expo install @mspkapps/auth-client @react-native-google-signin/google-signin react-native-async-storage
72
-
73
- # For React Native CLI
74
- npm install @mspkapps/auth-client @react-native-google-signin/google-signin react-native-async-storage
75
- ```
76
-
77
- #### 3. Configure Google Sign-In (Expo)
78
-
79
- Add to your `app.json`:
80
-
81
- ```json
82
- {
83
- "expo": {
84
- "plugins": [
85
- [
86
- "@react-native-google-signin/google-signin",
87
- {
88
- "androidClientId": "YOUR_ANDROID_CLIENT_ID.apps.googleusercontent.com",
89
- "iosClientId": "YOUR_IOS_CLIENT_ID.apps.googleusercontent.com"
90
- }
91
- ]
92
- ]
93
- }
94
- }
95
- ```
96
-
97
- Then run:
98
- ```bash
99
- npx expo prebuild
19
+ npm install @mspkapps/auth-client
20
+ # or
21
+ yarn add @mspkapps/auth-client
100
22
  ```
101
23
 
102
24
  ---
103
25
 
104
- ## Setup
105
-
106
- ### React Web
107
-
108
- #### Step 1: Create Authentication Context
109
-
110
- Create `src/AuthContext.jsx` to manage authentication state across your app:
111
-
112
- ```jsx
113
- import React, { createContext, useContext, useCallback, useState, useMemo } from 'react';
114
- import { AuthClient } from '@mspkapps/auth-client';
115
-
116
- // Replace these with your actual API keys
117
- const PUBLIC_KEY = 'your_public_api_key_here';
118
- const API_SECRET = 'your_api_secret_here';
119
-
120
- // Initialize the auth client
121
- // Note: baseUrl is already configured in the package, no need to override it
122
- const authClient = AuthClient.create(PUBLIC_KEY, API_SECRET, {
123
- keyInPath: true, // Use API key in URL path
124
- storage: null, // Keep token in memory only (more secure)
125
- fetch: (input, init = {}) => {
126
- // Add required headers to all requests
127
- const headers = new Headers(init.headers || {});
128
- if (!headers.has('X-API-Key')) headers.set('X-API-Key', PUBLIC_KEY);
129
- if (!headers.has('X-API-Secret')) headers.set('X-API-Secret', API_SECRET);
130
-
131
- // Convert token format from "UserToken" to "Bearer" (if needed by your server)
132
- if (headers.has('Authorization')) {
133
- const authHeader = headers.get('Authorization');
134
- if (authHeader.startsWith('UserToken ')) {
135
- headers.set('Authorization', 'Bearer ' + authHeader.substring(10));
136
- }
137
- }
138
-
139
- return window.fetch(input, { ...init, headers });
140
- },
141
- });
26
+ ## Backend Usage (Recommended)
142
27
 
143
- const AuthContext = createContext(null);
144
-
145
- export function AuthProvider({ children }) {
146
- const [user, setUser] = useState(null);
147
- const [token, setToken] = useState(null);
148
- const [loading, setLoading] = useState(false);
149
- const [error, setError] = useState(null);
150
-
151
- // Email/Password Login
152
- const login = useCallback(async ({ email, password }) => {
153
- setError(null);
154
- setLoading(true);
155
- try {
156
- const res = await authClient.login({ email, password });
157
- const token = res?.data?.access_token;
158
- if (token) {
159
- authClient.setToken(token);
160
- }
161
- setUser(res?.data?.user || null);
162
- setToken(authClient.token);
163
- return res?.data?.user || null;
164
- } catch (err) {
165
- setError(err);
166
- throw err;
167
- } finally {
168
- setLoading(false);
169
- }
170
- }, []);
171
-
172
- // Google Sign-In
173
- const googleLogin = useCallback(async (idToken) => {
174
- setError(null);
175
- setLoading(true);
176
- try {
177
- const res = await authClient.googleAuth({ id_token: idToken });
178
- const token = res?.data?.user_token || res?.data?.access_token;
179
- if (token) {
180
- authClient.setToken(token);
181
- }
182
- setUser(res?.data?.user || null);
183
- setToken(authClient.token || token);
184
- return { user: res?.data?.user, isNewUser: res?.data?.is_new_user };
185
- } catch (err) {
186
- setError(err);
187
- throw err;
188
- } finally {
189
- setLoading(false);
190
- }
191
- }, []);
192
-
193
- // Logout
194
- const logout = useCallback(() => {
195
- setUser(null);
196
- setToken(null);
197
- authClient.token = null;
198
- }, []);
199
-
200
- // Refresh User Profile
201
- const refreshProfile = useCallback(async () => {
202
- if (!token) return;
203
- setLoading(true);
204
- setError(null);
205
- try {
206
- const res = await authClient.getProfile();
207
- setUser(res.data.user);
208
- return res.data.user;
209
- } catch (err) {
210
- setError(err);
211
- throw err;
212
- } finally {
213
- setLoading(false);
214
- }
215
- }, [token]);
216
-
217
- const value = useMemo(
218
- () => ({ authClient, user, token, login, googleLogin, logout, refreshProfile, loading, error }),
219
- [user, token, login, googleLogin, logout, refreshProfile, loading, error]
220
- );
221
-
222
- return <AuthContext.Provider value={value}>{children}</AuthContext.Provider>;
223
- }
28
+ Most apps should only use this package on the **backend** (Node/Express, NestJS, etc.).
224
29
 
225
- // Custom hook to use auth context
226
- export function useAuth() {
227
- const ctx = useContext(AuthContext);
228
- if (!ctx) throw new Error('useAuth must be used within AuthProvider');
229
- return ctx;
230
- }
231
- ```
30
+ ### 1. Initialize once at startup
232
31
 
233
- ### React Native
234
-
235
- #### Step 1: Create Authentication Context
236
-
237
- Create `src/AuthContext.js` with AsyncStorage support for React Native:
238
-
239
- ```jsx
240
- import React, { createContext, useContext, useCallback, useState, useMemo } from 'react';
241
- import { AuthClient } from '@mspkapps/auth-client';
242
- import AsyncStorage from '@react-native-async-storage/async-storage';
243
-
244
- // Replace these with your actual API keys
245
- const PUBLIC_KEY = 'your_public_api_key_here';
246
- const API_SECRET = 'your_api_secret_here';
247
-
248
- // Initialize the auth client with AsyncStorage for React Native
249
- const authClient = AuthClient.create(PUBLIC_KEY, API_SECRET, {
250
- keyInPath: true,
251
- storage: AsyncStorage, // Use AsyncStorage instead of localStorage
252
- fetch: (input, init = {}) => {
253
- const headers = new Headers(init.headers || {});
254
- if (!headers.has('X-API-Key')) headers.set('X-API-Key', PUBLIC_KEY);
255
- if (!headers.has('X-API-Secret')) headers.set('X-API-Secret', API_SECRET);
256
-
257
- if (headers.has('Authorization')) {
258
- const authHeader = headers.get('Authorization');
259
- if (authHeader.startsWith('UserToken ')) {
260
- headers.set('Authorization', 'Bearer ' + authHeader.substring(10));
261
- }
262
- }
263
-
264
- // Use global fetch available in React Native
265
- return fetch(input, { ...init, headers });
266
- },
267
- });
32
+ ```js
33
+ // authClient.js
34
+ import authclient from '@mspkapps/auth-client';
268
35
 
269
- const AuthContext = createContext(null);
270
-
271
- export function AuthProvider({ children }) {
272
- const [user, setUser] = useState(null);
273
- const [token, setToken] = useState(null);
274
- const [loading, setLoading] = useState(false);
275
- const [error, setError] = useState(null);
276
- const [isReady, setIsReady] = useState(false);
277
-
278
- // Load token from storage on mount
279
- React.useEffect(() => {
280
- const bootstrapAsync = async () => {
281
- try {
282
- const savedToken = await AsyncStorage.getItem('auth_user_token');
283
- if (savedToken) {
284
- authClient.setToken(savedToken);
285
- setToken(savedToken);
286
-
287
- // Fetch user profile
288
- try {
289
- const res = await authClient.getProfile();
290
- setUser(res.data.user);
291
- } catch (err) {
292
- // Token might be invalid
293
- await AsyncStorage.removeItem('auth_user_token');
294
- authClient.setToken(null);
295
- setToken(null);
296
- }
297
- }
298
- } catch (err) {
299
- console.error('Failed to restore token', err);
300
- } finally {
301
- setIsReady(true);
302
- }
303
- };
304
-
305
- bootstrapAsync();
306
- }, []);
307
-
308
- const login = useCallback(async ({ email, password }) => {
309
- setError(null);
310
- setLoading(true);
311
- try {
312
- const res = await authClient.login({ email, password });
313
- const token = res?.data?.access_token;
314
- if (token) {
315
- authClient.setToken(token);
316
- }
317
- setUser(res?.data?.user || null);
318
- setToken(authClient.token);
319
- return res?.data?.user || null;
320
- } catch (err) {
321
- setError(err);
322
- throw err;
323
- } finally {
324
- setLoading(false);
325
- }
326
- }, []);
327
-
328
- const googleLogin = useCallback(async (idToken) => {
329
- setError(null);
330
- setLoading(true);
331
- try {
332
- const res = await authClient.googleAuth({ id_token: idToken });
333
- const token = res?.data?.user_token || res?.data?.access_token;
334
- if (token) {
335
- authClient.setToken(token);
336
- }
337
- setUser(res?.data?.user || null);
338
- setToken(authClient.token || token);
339
- return { user: res?.data?.user, isNewUser: res?.data?.is_new_user };
340
- } catch (err) {
341
- setError(err);
342
- throw err;
343
- } finally {
344
- setLoading(false);
345
- }
346
- }, []);
347
-
348
- const logout = useCallback(async () => {
349
- setUser(null);
350
- setToken(null);
351
- authClient.token = null;
352
- await AsyncStorage.removeItem('auth_user_token');
353
- }, []);
354
-
355
- const refreshProfile = useCallback(async () => {
356
- if (!token) return;
357
- setLoading(true);
358
- setError(null);
359
- try {
360
- const res = await authClient.getProfile();
361
- setUser(res.data.user);
362
- return res.data.user;
363
- } catch (err) {
364
- setError(err);
365
- throw err;
366
- } finally {
367
- setLoading(false);
368
- }
369
- }, [token]);
370
-
371
- const value = useMemo(
372
- () => ({ authClient, user, token, login, googleLogin, logout, refreshProfile, loading, error, isReady }),
373
- [user, token, login, googleLogin, logout, refreshProfile, loading, error, isReady]
374
- );
375
-
376
- return (
377
- <AuthContext.Provider value={value}>
378
- {isReady ? children : null}
379
- </AuthContext.Provider>
380
- );
381
- }
36
+ authclient.init({
37
+ apiKey: process.env.MSPK_AUTH_API_KEY,
38
+ apiSecret: process.env.MSPK_AUTH_API_SECRET,
39
+ googleClientId: process.env.GOOGLE_CLIENT_ID, // optional, for Google OAuth
40
+ // baseUrl: 'https://cpanel.backend.mspkapps.in/api/v1', // optional override
41
+ });
382
42
 
383
- export function useAuth() {
384
- const ctx = useContext(AuthContext);
385
- if (!ctx) throw new Error('useAuth must be used within AuthProvider');
386
- return ctx;
387
- }
43
+ export default authclient;
388
44
  ```
389
45
 
390
- #### Step 2: Setup Navigation
391
-
392
- Create `src/Navigation.js` for handling navigation between Login and Home:
46
+ ### 2. Use in your routes/handlers
393
47
 
394
- ```jsx
395
- import React from 'react';
396
- import { NavigationContainer } from '@react-navigation/native';
397
- import { createNativeStackNavigator } from '@react-navigation/native-stack';
398
- import { useAuth } from './AuthContext';
399
- import LoginScreen from './screens/LoginScreen';
400
- import RegisterScreen from './screens/RegisterScreen';
401
- import HomeScreen from './screens/HomeScreen';
402
- import SplashScreen from './screens/SplashScreen';
48
+ ```js
49
+ // exampleRoute.js
50
+ import authclient from './authClient.js';
403
51
 
404
- const Stack = createNativeStackNavigator();
52
+ // Login (email + password)
53
+ export async function loginHandler(req, res) {
54
+ const { email, password } = req.body;
405
55
 
406
- export function RootNavigator() {
407
- const { user, isReady } = useAuth();
408
-
409
- if (!isReady) {
410
- return <SplashScreen />;
56
+ try {
57
+ const result = await authclient.login({ email, password });
58
+ // result.data.user, result.data.user_token, etc.
59
+ res.json(result);
60
+ } catch (err) {
61
+ res.status(err.status || 400).json({
62
+ success: false,
63
+ message: err.message || 'Login failed',
64
+ code: err.code || 'LOGIN_FAILED',
65
+ });
411
66
  }
412
-
413
- return (
414
- <NavigationContainer>
415
- <Stack.Navigator
416
- screenOptions={{ headerShown: false }}
417
- >
418
- {user ? (
419
- <Stack.Screen name="Home" component={HomeScreen} />
420
- ) : (
421
- <Stack.Group screenOptions={{ animationEnabled: false }}>
422
- <Stack.Screen name="Auth" component={AuthNavigator} />
423
- </Stack.Group>
424
- )}
425
- </Stack.Navigator>
426
- </NavigationContainer>
427
- );
428
- }
429
-
430
- function AuthNavigator() {
431
- const [showRegister, setShowRegister] = React.useState(false);
432
-
433
- return (
434
- <Stack.Navigator
435
- screenOptions={{
436
- headerShown: true,
437
- animationEnabled: false,
438
- }}
439
- >
440
- {showRegister ? (
441
- <Stack.Screen
442
- name="Register"
443
- component={RegisterScreen}
444
- options={{
445
- title: 'Sign Up',
446
- headerLeft: () => (
447
- <TouchableOpacity onPress={() => setShowRegister(false)}>
448
- <Text>Back</Text>
449
- </TouchableOpacity>
450
- ),
451
- }}
452
- />
453
- ) : (
454
- <Stack.Screen
455
- name="Login"
456
- component={LoginScreen}
457
- options={{
458
- title: 'Login',
459
- headerRight: () => (
460
- <TouchableOpacity onPress={() => setShowRegister(true)}>
461
- <Text>Sign Up</Text>
462
- </TouchableOpacity>
463
- ),
464
- }}
465
- />
466
- )}
467
- </Stack.Navigator>
468
- );
469
67
  }
470
68
  ```
471
69
 
472
- #### Step 3: Setup Root App Component
473
-
474
- Update `App.js` (or `App.jsx`):
475
-
476
- ```jsx
477
- import React from 'react';
478
- import { AuthProvider } from './src/AuthContext';
479
- import { RootNavigator } from './src/Navigation';
70
+ ```js
71
+ // Register
72
+ export async function registerHandler(req, res) {
73
+ const { email, username, password, name, ...extra } = req.body;
480
74
 
481
- export default function App() {
482
- return (
483
- <AuthProvider>
484
- <RootNavigator />
485
- </AuthProvider>
486
- );
487
- }
488
- ```
489
-
490
- Update `src/main.jsx` to wrap your app with both AuthProvider and GoogleOAuthProvider:
491
-
492
- ```jsx
493
- import React from 'react';
494
- import ReactDOM from 'react-dom/client';
495
- import App from './App.jsx';
496
- import { AuthProvider } from './AuthContext.jsx';
497
- import { GoogleOAuthProvider } from '@react-oauth/google';
498
- import './index.css';
499
-
500
- // Replace with your Google Client ID from Google Cloud Console
501
- const GOOGLE_CLIENT_ID = 'your_google_client_id_here';
502
-
503
- ReactDOM.createRoot(document.getElementById('root')).render(
504
- <React.StrictMode>
505
- <GoogleOAuthProvider clientId={GOOGLE_CLIENT_ID}>
506
- <AuthProvider>
507
- <App />
508
- </AuthProvider>
509
- </GoogleOAuthProvider>
510
- </React.StrictMode>
511
- );
512
- ```
513
-
514
- ### Step 3: Create Main App Component
515
-
516
- Create `src/App.jsx` to handle routing between Login and Home:
517
-
518
- ```jsx
519
- import React, { useState } from 'react';
520
- import { useAuth } from './AuthContext.jsx';
521
- import Login from './components/Login.jsx';
522
- import Register from './components/Register.jsx';
523
- import Home from './components/Home.jsx';
524
- import './App.css';
525
-
526
- export default function App() {
527
- const { user } = useAuth();
528
- const [showRegister, setShowRegister] = useState(false);
529
-
530
- // If user is logged in, show Home page
531
- if (user) {
532
- return <Home />;
75
+ try {
76
+ const result = await authclient.register({
77
+ email,
78
+ username,
79
+ password,
80
+ name,
81
+ ...extra, // custom fields, if configured in MSPK
82
+ });
83
+ res.json(result);
84
+ } catch (err) {
85
+ res.status(err.status || 400).json({
86
+ success: false,
87
+ message: err.message || 'Registration failed',
88
+ code: err.code || 'REGISTER_FAILED',
89
+ });
533
90
  }
534
-
535
- // Otherwise, show Login or Register page
536
- return (
537
- <div className="app-container">
538
- <div className="auth-toggle">
539
- <button
540
- onClick={() => setShowRegister(false)}
541
- className={!showRegister ? 'active' : ''}
542
- >
543
- Login
544
- </button>
545
- <button
546
- onClick={() => setShowRegister(true)}
547
- className={showRegister ? 'active' : ''}
548
- >
549
- Sign Up
550
- </button>
551
- </div>
552
- {showRegister ? <Register /> : <Login />}
553
- </div>
554
- );
555
91
  }
556
92
  ```
557
93
 
558
94
  ---
559
95
 
560
- ## Available Functions
561
-
562
- ### Core Authentication Methods
563
-
564
- All functions are accessed through the `authClient` object or the `useAuth()` hook.
565
-
566
- #### 1. **register** - Create a new user account
567
-
568
- ```javascript
569
- await authClient.register({
570
- email: 'user@example.com',
571
- username: 'johndoe', // Optional
572
- password: 'securePass123',
573
- name: 'John Doe' // Optional
574
- });
575
- ```
576
-
577
- **Parameters:**
578
- - `email` (required): User's email address
579
- - `username` (optional): Unique username
580
- - `password` (required): User's password
581
- - `name` (optional): User's display name
582
-
583
- **Response:** Returns user data and automatically sets authentication token.
96
+ ## API Reference – What Data Each Call Needs
584
97
 
585
- ---
586
-
587
- #### 2. **login** - Login with email/username and password
98
+ All methods below assume you already called:
588
99
 
589
- ```javascript
590
- await authClient.login({
591
- email: 'user@example.com',
592
- password: 'securePass123'
593
- });
100
+ ```js
101
+ import authclient from '@mspkapps/auth-client';
594
102
 
595
- // Or login with username
596
- await authClient.login({
597
- username: 'johndoe',
598
- password: 'securePass123'
103
+ authclient.init({
104
+ apiKey: 'YOUR_API_KEY',
105
+ apiSecret: 'YOUR_API_SECRET',
106
+ googleClientId: 'YOUR_GOOGLE_CLIENT_ID', // optional
599
107
  });
600
108
  ```
601
109
 
602
- **Parameters:**
603
- - `email` OR `username` (required): User identifier
604
- - `password` (required): User's password
110
+ ### Auth & Users
605
111
 
606
- **Response:** Returns user data and authentication token.
607
-
608
- ---
112
+ #### `authclient.login({ email, password })` or `authclient.login({ username, password })`
609
113
 
610
- #### 3. **googleAuth** - Login/Register with Google Sign-In
114
+ - Required:
115
+ - `email` **or** `username` (string)
116
+ - `password` (string)
117
+ - Returns:
118
+ - User data and user token; token is stored internally via `setToken`.
611
119
 
612
- ```javascript
613
- await authClient.googleAuth({
614
- id_token: 'google_id_token_here'
615
- });
616
-
617
- // Alternative: use access_token
618
- await authClient.googleAuth({
619
- access_token: 'google_access_token_here'
620
- });
621
- ```
120
+ #### `authclient.register({ email, username?, password, name?, ...extraFields })`
622
121
 
623
- **Parameters:**
624
- - `id_token` (required if no access_token): Google ID token from credential response
625
- - `access_token` (alternative): Google access token
122
+ - Required:
123
+ - `email` (string)
124
+ - `password` (string)
125
+ - Optional:
126
+ - `username` (string)
127
+ - `name` (string)
128
+ - Any extra profile fields you enabled in MSPK (e.g. `company`, `country`).
129
+ - Returns:
130
+ - User data and user token; token is stored internally.
626
131
 
627
- **Response:** Returns user data, token, and `is_new_user` flag (true if this is first login).
132
+ #### `authclient.googleAuth({ id_token })`
133
+ #### `authclient.googleAuth({ access_token })`
628
134
 
629
- **Note:** Use with `@react-oauth/google` package for the Google Sign-In button.
135
+ - Required:
136
+ - Either:
137
+ - `id_token` (string), or
138
+ - `access_token` (string)
139
+ - The `googleClientId` is taken from `authclient.init(...)` and sent automatically.
140
+ - Returns:
141
+ - User data, token, and possibly a flag like `is_new_user`.
630
142
 
631
143
  ---
632
144
 
633
- #### 4. **logout** - Clear authentication token
145
+ ### Password Flows
634
146
 
635
- ```javascript
636
- authClient.logout();
637
- ```
147
+ #### `authclient.client.requestPasswordReset({ email })`
638
148
 
639
- **Parameters:** None
149
+ - Required:
150
+ - `email` (string)
151
+ - Use when: user clicks “Forgot password?”
640
152
 
641
- **Response:** Clears the stored token.
153
+ #### `authclient.client.requestChangePasswordLink({ email })`
642
154
 
643
- ---
155
+ - Required:
156
+ - `email` (string)
157
+ - Use when: logged-in user requests a “change password” email from settings/profile.
644
158
 
645
- #### 5. **getProfile** - Get current user's profile
159
+ #### `authclient.client.resendVerificationEmail({ email, purpose? })`
646
160
 
647
- ```javascript
648
- const response = await authClient.getProfile();
649
- console.log(response.data.user);
650
- ```
161
+ - Required:
162
+ - `email` (string)
163
+ - Optional:
164
+ - `purpose` (string). Valid values:
165
+ - `New Account`
166
+ - `Password change`
167
+ - `Profile Edit`
168
+ - `Forget Password`
169
+ - `Delete Account`
170
+ - `Set Password - Google User`
171
+ - If `purpose` is missing/invalid, the backend will treat it as `New Account`.
651
172
 
652
- **Parameters:** None (requires authentication token)
173
+ #### `authclient.client.sendGoogleUserSetPasswordEmail({ email })`
653
174
 
654
- **Response:** Returns current user's profile data.
175
+ - Required:
176
+ - `email` (string) – user who signed up via Google and wants a password.
177
+ - Use when: a Google-only user wants to set a traditional password.
655
178
 
656
179
  ---
657
180
 
658
- ### Password Management
659
-
660
- #### 6. **requestPasswordReset** - Send password reset email
661
-
662
- ```javascript
663
- await authClient.requestPasswordReset({
664
- email: 'user@example.com'
665
- });
666
- ```
667
-
668
- **Parameters:**
669
- - `email` (required): Email address to send reset link
181
+ ### Account Management
670
182
 
671
- **Response:** Success message confirming email was sent.
183
+ #### `authclient.client.deleteAccount({ email, password })`
672
184
 
673
- **Use Case:** When user forgot their password on login page.
185
+ - Required:
186
+ - `email` (string)
187
+ - `password` (string) – current password (depending on your server rules).
188
+ - Use when: user confirms account deletion.
674
189
 
675
190
  ---
676
191
 
677
- #### 7. **requestChangePasswordLink** - Send password change email for logged-in users
678
-
679
- ```javascript
680
- await authClient.requestChangePasswordLink({
681
- email: 'user@example.com'
682
- });
683
- ```
192
+ ### Profile
684
193
 
685
- **Parameters:**
686
- - `email` (required): User's email address
194
+ #### `authclient.getProfile()`
687
195
 
688
- **Response:** Success message confirming email was sent.
196
+ - No parameters.
197
+ - Uses the internally stored user token.
198
+ - Returns current user profile.
689
199
 
690
- **Use Case:** When authenticated user wants to change their password from settings/profile page.
200
+ #### `authclient.client.getEditableProfileFields()`
691
201
 
692
- ---
202
+ - No parameters.
203
+ - Returns metadata about which fields this user is allowed to edit based on your MSPK configuration.
693
204
 
694
- #### 8. **sendGoogleUserSetPasswordEmail** - Send password setup email for Google users
205
+ #### `authclient.updateProfile(updates)`
695
206
 
696
- ```javascript
697
- await authClient.sendGoogleUserSetPasswordEmail({
698
- email: 'user@example.com'
207
+ ```js
208
+ const res = await authclient.updateProfile({
209
+ name: 'New Name', // optional
210
+ username: 'new_username', // optional
211
+ email: 'new@example.com', // optional; may trigger verification
212
+ extra: { // optional; your custom fields
213
+ company: 'New Co',
214
+ country: 'US',
215
+ },
699
216
  });
700
217
  ```
701
218
 
702
- **Parameters:**
703
- - `email` (required): Google user's email address
704
-
705
- **Response:** Success message confirming email was sent.
706
-
707
- **Use Case:** When a user who registered via Google Sign-In wants to set a password for traditional login.
219
+ - All keys are **optional**; send only what you want to change.
220
+ - Allowed fields depend on:
221
+ - Core field permissions (name/username/email).
222
+ - Extra fields you defined for the app.
708
223
 
709
224
  ---
710
225
 
711
- ### Email Verification
226
+ ### Generic Authenticated Calls
712
227
 
713
- #### 9. **resendVerificationEmail** - Resend email verification
228
+ #### `authclient.authed(path, { method = 'GET', body, headers } = {})`
714
229
 
715
- ```javascript
716
- await authClient.resendVerificationEmail({
717
- email: 'user@example.com',
718
- purpose: 'New Account' // or 'Account Recovery'
230
+ ```js
231
+ const res = await authclient.authed('user/profile', {
232
+ method: 'PATCH',
233
+ body: { name: 'Another Name' },
234
+ headers: {
235
+ 'X-Custom-Header': '123',
236
+ },
719
237
  });
720
238
  ```
721
239
 
722
- **Parameters:**
723
- - `email` (required): Email address to send verification
724
- - `purpose` (optional): Purpose of verification (e.g., 'New Account')
725
-
726
- **Response:** Success message confirming email was sent.
727
-
728
- **Use Case:** When user's account is not verified and they need a new verification email.
240
+ - Required:
241
+ - `path` (string) relative path (e.g. `'user/profile'`), not full URL.
242
+ - Optional:
243
+ - `method` (string, default `'GET'`)
244
+ - `body` (object) will be `JSON.stringify`’d
245
+ - `headers` (object) – extra headers merged into auth headers.
246
+ - Uses the same API key/secret/Google client and user token as other methods.
729
247
 
730
248
  ---
731
249
 
732
- ### Account Management
733
-
734
- #### 10. **deleteAccount** - Permanently delete user account
250
+ ### Token Helpers
735
251
 
736
- ```javascript
737
- await authClient.deleteAccount({
738
- email: 'user@example.com',
739
- password: 'userPassword123' // Optional, depending on server requirements
740
- });
741
- ```
252
+ #### `authclient.setToken(token)`
742
253
 
743
- **Parameters:**
744
- - `email` (required): Email of account to delete
745
- - `password` (optional): User's password for confirmation
254
+ - Required:
255
+ - `token` (string or `null`).
256
+ - Usually not needed, because:
257
+ - `login`, `register`, and `googleAuth` will set the token automatically.
746
258
 
747
- **Response:** Success message. Token should be cleared after deletion.
259
+ #### `authclient.logout()`
748
260
 
749
- **Warning:** This action is permanent and cannot be undone!
261
+ - No parameters.
262
+ - Clears the stored token in memory (and storage, if configured).
750
263
 
751
264
  ---
752
265
 
753
- ### Advanced Usage
266
+ ## Example Express Setup
754
267
 
755
- #### 11. **authed** - Make custom authenticated API calls
268
+ ```js
269
+ // authClient.js
270
+ import authclient from '@mspkapps/auth-client';
756
271
 
757
- ```javascript
758
- const response = await authClient.authed('custom/endpoint', {
759
- method: 'POST',
760
- body: { key: 'value' },
761
- headers: { 'Custom-Header': 'value' }
272
+ authclient.init({
273
+ apiKey: process.env.MSPK_AUTH_API_KEY,
274
+ apiSecret: process.env.MSPK_AUTH_API_SECRET,
275
+ googleClientId: process.env.GOOGLE_CLIENT_ID,
762
276
  });
763
- ```
764
277
 
765
- **Parameters:**
766
- - `path` (required): API endpoint path (without base URL)
767
- - `options` (optional): Request options
768
- - `method`: HTTP method (GET, POST, PUT, DELETE, etc.)
769
- - `body`: Request body object
770
- - `headers`: Additional headers
771
-
772
- **Response:** Response data from custom endpoint.
773
-
774
- **Use Case:** For custom endpoints not covered by the built-in methods.
775
-
776
- ---
777
-
778
- ## Implementation Examples
779
-
780
- ### React Web Examples
781
-
782
- ### Complete Login Component
783
-
784
- Create `src/components/Login.jsx`:
785
-
786
- ```jsx
787
- import React, { useState } from 'react';
788
- import { useAuth } from '../AuthContext.jsx';
789
- import { GoogleLogin } from '@react-oauth/google';
790
-
791
- export default function Login() {
792
- const { login, googleLogin, authClient } = useAuth();
793
-
794
- // Form state
795
- const [email, setEmail] = useState('');
796
- const [password, setPassword] = useState('');
797
- const [busy, setBusy] = useState(false);
798
- const [error, setError] = useState(null);
799
-
800
- // Password reset state
801
- const [showReset, setShowReset] = useState(false);
802
- const [resetEmail, setResetEmail] = useState('');
803
- const [resetFeedback, setResetFeedback] = useState(null);
804
-
805
- // Google user set password state
806
- const [showGoogleSetPassword, setShowGoogleSetPassword] = useState(false);
807
- const [googleSetPasswordEmail, setGoogleSetPasswordEmail] = useState('');
808
- const [googleSetPasswordFeedback, setGoogleSetPasswordFeedback] = useState(null);
809
-
810
- // Email verification state
811
- const [resendFeedback, setResendFeedback] = useState(null);
812
-
813
- // Handle standard email/password login
814
- async function handleSubmit(e) {
815
- e.preventDefault();
816
- setError(null);
817
- setResendFeedback(null);
818
- setBusy(true);
819
-
820
- try {
821
- await login({ email, password });
822
- // Success - user will be redirected by App.jsx
823
- } catch (err) {
824
- setError(err.message || 'Login failed');
825
-
826
- // Check if account needs verification
827
- if (
828
- err.code === 'EMAIL_NOT_VERIFIED' ||
829
- err.message?.toLowerCase().includes('not been verified')
830
- ) {
831
- setResendFeedback('Your account has not been verified. Click below to resend verification.');
832
- }
833
- } finally {
834
- setBusy(false);
835
- }
836
- }
837
-
838
- // Handle Google Sign-In
839
- async function handleGoogleSuccess(credentialResponse) {
840
- setBusy(true);
841
- try {
842
- const result = await googleLogin(credentialResponse.credential);
843
- if (result.isNewUser) {
844
- console.log('Welcome, new user!');
845
- }
846
- } catch (err) {
847
- setError(err.message || 'Google login failed');
848
- } finally {
849
- setBusy(false);
850
- }
851
- }
852
-
853
- // Handle password reset request
854
- async function handlePasswordReset(e) {
855
- e.preventDefault();
856
- setBusy(true);
857
- setResetFeedback(null);
858
-
859
- try {
860
- await authClient.requestPasswordReset({ email: resetEmail });
861
- setResetFeedback('Password reset email sent. Check your inbox.');
862
- setResetEmail('');
863
- } catch (err) {
864
- setResetFeedback(err.message || 'Failed to send reset email');
865
- } finally {
866
- setBusy(false);
867
- }
868
- }
869
-
870
- // Handle resend verification email
871
- async function handleResendVerification() {
872
- setBusy(true);
873
- setResendFeedback(null);
874
-
875
- try {
876
- await authClient.resendVerificationEmail({
877
- email,
878
- purpose: 'New Account'
879
- });
880
- setResendFeedback('Verification email sent. Please check your inbox.');
881
- } catch (err) {
882
- setResendFeedback(err.message || 'Failed to resend verification');
883
- } finally {
884
- setBusy(false);
885
- }
886
- }
887
-
888
- // Handle Google user set password email
889
- async function handleSendGoogleSetPasswordEmail(e) {
890
- e.preventDefault();
891
- setBusy(true);
892
- setGoogleSetPasswordFeedback(null);
893
-
894
- try {
895
- await authClient.sendGoogleUserSetPasswordEmail({
896
- email: googleSetPasswordEmail
897
- });
898
- setGoogleSetPasswordFeedback('Email sent. Check your inbox to set a password.');
899
- setGoogleSetPasswordEmail('');
900
- } catch (err) {
901
- setGoogleSetPasswordFeedback(err.message || 'Failed to send email');
902
- } finally {
903
- setBusy(false);
904
- }
905
- }
906
-
907
- return (
908
- <div className="login-container">
909
- <h2>Login</h2>
910
-
911
- {/* Email/Password Login Form */}
912
- <form onSubmit={handleSubmit}>
913
- <label>
914
- Email
915
- <input
916
- type="email"
917
- value={email}
918
- onChange={(e) => setEmail(e.target.value)}
919
- required
920
- />
921
- </label>
922
-
923
- <label>
924
- Password
925
- <input
926
- type="password"
927
- value={password}
928
- onChange={(e) => setPassword(e.target.value)}
929
- required
930
- />
931
- </label>
932
-
933
- <button type="submit" disabled={busy}>
934
- {busy ? 'Please wait…' : 'Login'}
935
- </button>
936
- </form>
937
-
938
- {error && <p style={{ color: 'red' }}>{error}</p>}
939
-
940
- {/* Google Sign-In Button */}
941
- <div style={{ marginTop: 16 }}>
942
- <span>or</span>
943
- <GoogleLogin
944
- onSuccess={handleGoogleSuccess}
945
- onError={() => setError('Google login failed')}
946
- />
947
- </div>
948
-
949
- {/* Resend Verification Email */}
950
- {resendFeedback && (
951
- <div>
952
- <p>{resendFeedback}</p>
953
- {resendFeedback.includes('has not been verified') && (
954
- <button onClick={handleResendVerification} disabled={busy}>
955
- Resend Verification Email
956
- </button>
957
- )}
958
- </div>
959
- )}
960
-
961
- {/* Forgot Password */}
962
- <div style={{ marginTop: 16 }}>
963
- <button onClick={() => setShowReset(!showReset)}>
964
- {showReset ? 'Hide' : 'Forgot password?'}
965
- </button>
966
-
967
- {showReset && (
968
- <form onSubmit={handlePasswordReset}>
969
- <input
970
- type="email"
971
- placeholder="Enter your email"
972
- value={resetEmail}
973
- onChange={(e) => setResetEmail(e.target.value)}
974
- required
975
- />
976
- <button type="submit" disabled={busy}>
977
- Send Reset Link
978
- </button>
979
- {resetFeedback && <p>{resetFeedback}</p>}
980
- </form>
981
- )}
982
- </div>
983
-
984
- {/* Google User Set Password */}
985
- <div style={{ marginTop: 12 }}>
986
- <button onClick={() => setShowGoogleSetPassword(!showGoogleSetPassword)}>
987
- {showGoogleSetPassword ? 'Hide' : 'No password? Are you a Google user?'}
988
- </button>
989
-
990
- {showGoogleSetPassword && (
991
- <form onSubmit={handleSendGoogleSetPasswordEmail}>
992
- <input
993
- type="email"
994
- placeholder="Enter your Google account email"
995
- value={googleSetPasswordEmail}
996
- onChange={(e) => setGoogleSetPasswordEmail(e.target.value)}
997
- required
998
- />
999
- <button type="submit" disabled={busy}>
1000
- Send Set-Password Email
1001
- </button>
1002
- {googleSetPasswordFeedback && <p>{googleSetPasswordFeedback}</p>}
1003
- </form>
1004
- )}
1005
- </div>
1006
- </div>
1007
- );
1008
- }
278
+ export default authclient;
1009
279
  ```
1010
280
 
1011
- ---
281
+ ```js
282
+ // routes/auth.js
283
+ import express from 'express';
284
+ import authclient from '../authClient.js';
1012
285
 
1013
- ### Complete Register Component
1014
-
1015
- Create `src/components/Register.jsx`:
1016
-
1017
- ```jsx
1018
- import React, { useState } from 'react';
1019
- import { useAuth } from '../AuthContext.jsx';
1020
-
1021
- export default function Register() {
1022
- const { authClient } = useAuth();
1023
-
1024
- const [email, setEmail] = useState('');
1025
- const [password, setPassword] = useState('');
1026
- const [name, setName] = useState('');
1027
- const [username, setUsername] = useState('');
1028
- const [error, setError] = useState(null);
1029
- const [success, setSuccess] = useState(null);
1030
- const [busy, setBusy] = useState(false);
1031
-
1032
- async function handleSubmit(e) {
1033
- e.preventDefault();
1034
- setError(null);
1035
- setSuccess(null);
1036
- setBusy(true);
1037
-
1038
- try {
1039
- await authClient.register({
1040
- email,
1041
- username,
1042
- password,
1043
- name
1044
- });
1045
-
1046
- setSuccess('Registered! Please verify your email, then login.');
1047
-
1048
- // Clear form
1049
- setEmail('');
1050
- setPassword('');
1051
- setName('');
1052
- setUsername('');
1053
- } catch (err) {
1054
- setError(err.message || 'Registration failed');
1055
- } finally {
1056
- setBusy(false);
1057
- }
1058
- }
1059
-
1060
- return (
1061
- <div className="register-container">
1062
- <h2>Sign Up</h2>
1063
-
1064
- <form onSubmit={handleSubmit}>
1065
- <label>
1066
- Name
1067
- <input
1068
- type="text"
1069
- value={name}
1070
- onChange={(e) => setName(e.target.value)}
1071
- />
1072
- </label>
1073
-
1074
- <label>
1075
- Username
1076
- <input
1077
- type="text"
1078
- value={username}
1079
- onChange={(e) => setUsername(e.target.value)}
1080
- />
1081
- </label>
1082
-
1083
- <label>
1084
- Email
1085
- <input
1086
- type="email"
1087
- value={email}
1088
- onChange={(e) => setEmail(e.target.value)}
1089
- required
1090
- />
1091
- </label>
1092
-
1093
- <label>
1094
- Password
1095
- <input
1096
- type="password"
1097
- value={password}
1098
- onChange={(e) => setPassword(e.target.value)}
1099
- required
1100
- />
1101
- </label>
1102
-
1103
- <button type="submit" disabled={busy}>
1104
- {busy ? 'Please wait…' : 'Create Account'}
1105
- </button>
1106
- </form>
1107
-
1108
- {error && <p style={{ color: 'red' }}>{error}</p>}
1109
- {success && <p style={{ color: 'green' }}>{success}</p>}
1110
- </div>
1111
- );
1112
- }
1113
- ```
286
+ const router = express.Router();
1114
287
 
1115
- ---
1116
-
1117
- ### Complete Home/Dashboard Component
1118
-
1119
- Create `src/components/Home.jsx`:
1120
-
1121
- ```jsx
1122
- import React, { useState } from 'react';
1123
- import { useAuth } from '../AuthContext.jsx';
1124
-
1125
- export default function Home() {
1126
- const { user, token, logout, refreshProfile, loading, authClient } = useAuth();
1127
-
1128
- const [feedback, setFeedback] = useState(null);
1129
- const [busy, setBusy] = useState(false);
1130
-
1131
- // Delete account state
1132
- const [showDeleteForm, setShowDeleteForm] = useState(false);
1133
- const [deleteFeedback, setDeleteFeedback] = useState(null);
1134
-
1135
- // Handle password change request (sends email)
1136
- async function doChangePassword(e) {
1137
- e.preventDefault();
1138
- setBusy(true);
1139
- setFeedback(null);
1140
-
1141
- try {
1142
- await authClient.requestChangePasswordLink({ email: user?.email });
1143
- setFeedback('Password change email sent. Check your inbox.');
1144
- } catch (err) {
1145
- setFeedback(err.message || 'Failed to send password change email');
1146
- } finally {
1147
- setBusy(false);
1148
- }
1149
- }
288
+ router.post('/login', async (req, res) => {
289
+ const { email, password, username } = req.body;
1150
290
 
1151
- // Handle account deletion
1152
- async function doDeleteAccount(e) {
1153
- e.preventDefault();
1154
-
1155
- if (!window.confirm('Are you sure you want to delete your account? This action cannot be undone.')) {
1156
- return;
1157
- }
1158
-
1159
- setBusy(true);
1160
- setDeleteFeedback(null);
1161
-
1162
- try {
1163
- await authClient.deleteAccount({ email: user?.email });
1164
- setDeleteFeedback('Account deleted successfully. Logging out...');
1165
-
1166
- // Auto logout after 1.5 seconds
1167
- setTimeout(() => {
1168
- logout();
1169
- }, 1500);
1170
- } catch (err) {
1171
- setDeleteFeedback(err.message || 'Delete account failed');
1172
- } finally {
1173
- setBusy(false);
1174
- }
291
+ try {
292
+ const result = await authclient.login(
293
+ email ? { email, password } : { username, password }
294
+ );
295
+ res.json(result);
296
+ } catch (err) {
297
+ res.status(err.status || 400).json({
298
+ success: false,
299
+ message: err.message || 'Login failed',
300
+ code: err.code || 'LOGIN_FAILED',
301
+ });
1175
302
  }
1176
-
1177
- return (
1178
- <div className="home-container">
1179
- <h2>Welcome</h2>
1180
-
1181
- {/* User Profile Display */}
1182
- <div className="user-box">
1183
- <p><strong>User ID:</strong> {user?.id || 'n/a'}</p>
1184
- <p><strong>Email:</strong> {user?.email}</p>
1185
- <p><strong>Name:</strong> {user?.name || '—'}</p>
1186
- <p><strong>Login Method:</strong> {user?.login_method || '—'}</p>
1187
- <p><strong>Google Linked:</strong> {user?.google_linked ? 'Yes' : '—'}</p>
1188
- <p><strong>Token (truncated):</strong> {token ? token.slice(0, 18) + '…' : 'none'}</p>
1189
- </div>
1190
-
1191
- {/* Action Buttons */}
1192
- <div className="actions">
1193
- <button onClick={refreshProfile} disabled={loading}>
1194
- Refresh Profile
1195
- </button>
1196
- <button onClick={logout}>Logout</button>
1197
- </div>
1198
-
1199
- {/* Change Password (Email Link) */}
1200
- <form onSubmit={doChangePassword} style={{ marginTop: 24 }}>
1201
- <h3>Change Password</h3>
1202
- <p>We will email a secure link to {user?.email} to change your password.</p>
1203
- <button type="submit" disabled={busy}>
1204
- {busy ? 'Sending…' : 'Send Change Password Email'}
1205
- </button>
1206
- </form>
1207
- {feedback && <p>{feedback}</p>}
1208
-
1209
- {/* Delete Account */}
1210
- <div style={{ marginTop: 24 }}>
1211
- <button
1212
- onClick={() => setShowDeleteForm(!showDeleteForm)}
1213
- style={{ background: 'red', color: 'white' }}
1214
- >
1215
- {showDeleteForm ? 'Cancel' : 'Delete Account'}
1216
- </button>
1217
-
1218
- {showDeleteForm && (
1219
- <form onSubmit={doDeleteAccount}>
1220
- <p style={{ color: 'red' }}>
1221
- Are you sure? This cannot be undone.
1222
- </p>
1223
- <button type="submit" disabled={busy}>
1224
- {busy ? 'Deleting…' : 'Confirm Delete Account'}
1225
- </button>
1226
- {deleteFeedback && <p>{deleteFeedback}</p>}
1227
- </form>
1228
- )}
1229
- </div>
1230
- </div>
1231
- );
1232
- }
1233
- ```
1234
-
1235
- ---
1236
-
1237
- ### React Native Examples
1238
-
1239
- #### Complete React Native Login Screen
1240
-
1241
- Create `src/screens/LoginScreen.js`:
1242
-
1243
- ```jsx
1244
- import React, { useState } from 'react';
1245
- import {
1246
- View,
1247
- Text,
1248
- TextInput,
1249
- TouchableOpacity,
1250
- ScrollView,
1251
- ActivityIndicator,
1252
- Alert,
1253
- } from 'react-native';
1254
- import { GoogleSignin, GoogleSigninButton } from '@react-native-google-signin/google-signin';
1255
- import { useAuth } from '../AuthContext';
1256
-
1257
- GoogleSignin.configure({
1258
- webClientId: 'YOUR_WEB_CLIENT_ID.apps.googleusercontent.com',
1259
303
  });
1260
304
 
1261
- export default function LoginScreen() {
1262
- const { login, googleLogin, authClient, loading } = useAuth();
1263
-
1264
- const [email, setEmail] = useState('');
1265
- const [password, setPassword] = useState('');
1266
- const [busy, setBusy] = useState(false);
1267
- const [error, setError] = useState(null);
1268
- const [showReset, setShowReset] = useState(false);
1269
- const [resetEmail, setResetEmail] = useState('');
1270
-
1271
- async function handleSubmit() {
1272
- setError(null);
1273
- setBusy(true);
1274
-
1275
- try {
1276
- await login({ email, password });
1277
- } catch (err) {
1278
- setError(err.message || 'Login failed');
1279
- } finally {
1280
- setBusy(false);
1281
- }
1282
- }
1283
-
1284
- async function handleGoogleSignIn() {
1285
- try {
1286
- await GoogleSignin.hasPlayServices();
1287
- const userInfo = await GoogleSignin.signIn();
1288
-
1289
- const result = await googleLogin(userInfo.idToken);
1290
- if (result.isNewUser) {
1291
- Alert.alert('Welcome!', 'New account created');
1292
- }
1293
- } catch (err) {
1294
- setError(err.message || 'Google sign in failed');
1295
- }
1296
- }
1297
-
1298
- async function handlePasswordReset() {
1299
- setBusy(true);
1300
- try {
1301
- await authClient.requestPasswordReset({ email: resetEmail });
1302
- Alert.alert('Success', 'Password reset email sent');
1303
- setResetEmail('');
1304
- setShowReset(false);
1305
- } catch (err) {
1306
- setError(err.message || 'Failed to send reset email');
1307
- } finally {
1308
- setBusy(false);
1309
- }
1310
- }
1311
-
1312
- return (
1313
- <ScrollView style={{ flex: 1, padding: 20, backgroundColor: '#fff' }}>
1314
- <Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 24 }}>Login</Text>
1315
-
1316
- <TextInput
1317
- placeholder="Email"
1318
- value={email}
1319
- onChangeText={setEmail}
1320
- keyboardType="email-address"
1321
- editable={!loading}
1322
- style={{
1323
- borderWidth: 1,
1324
- borderColor: '#ddd',
1325
- padding: 12,
1326
- marginBottom: 12,
1327
- borderRadius: 8,
1328
- }}
1329
- />
1330
-
1331
- <TextInput
1332
- placeholder="Password"
1333
- value={password}
1334
- onChangeText={setPassword}
1335
- secureTextEntry
1336
- editable={!loading}
1337
- style={{
1338
- borderWidth: 1,
1339
- borderColor: '#ddd',
1340
- padding: 12,
1341
- marginBottom: 20,
1342
- borderRadius: 8,
1343
- }}
1344
- />
1345
-
1346
- <TouchableOpacity
1347
- onPress={handleSubmit}
1348
- disabled={loading || busy}
1349
- style={{
1350
- backgroundColor: '#0066cc',
1351
- padding: 12,
1352
- borderRadius: 8,
1353
- alignItems: 'center',
1354
- marginBottom: 20,
1355
- }}
1356
- >
1357
- {loading || busy ? (
1358
- <ActivityIndicator color="#fff" />
1359
- ) : (
1360
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Login</Text>
1361
- )}
1362
- </TouchableOpacity>
1363
-
1364
- {error && <Text style={{ color: 'red', marginBottom: 12 }}>{error}</Text>}
1365
-
1366
- <View style={{ alignItems: 'center', marginVertical: 16 }}>
1367
- <Text>or</Text>
1368
- </View>
1369
-
1370
- <GoogleSigninButton
1371
- size={GoogleSigninButton.Size.Wide}
1372
- color={GoogleSigninButton.Color.Dark}
1373
- onPress={handleGoogleSignIn}
1374
- disabled={loading}
1375
- />
1376
-
1377
- <TouchableOpacity
1378
- onPress={() => setShowReset(!showReset)}
1379
- style={{ marginTop: 20 }}
1380
- >
1381
- <Text style={{ color: '#0066cc' }}>
1382
- {showReset ? 'Hide' : 'Forgot password?'}
1383
- </Text>
1384
- </TouchableOpacity>
1385
-
1386
- {showReset && (
1387
- <View style={{ marginTop: 12 }}>
1388
- <TextInput
1389
- placeholder="Enter your email"
1390
- value={resetEmail}
1391
- onChangeText={setResetEmail}
1392
- keyboardType="email-address"
1393
- style={{
1394
- borderWidth: 1,
1395
- borderColor: '#ddd',
1396
- padding: 12,
1397
- marginBottom: 12,
1398
- borderRadius: 8,
1399
- }}
1400
- />
1401
- <TouchableOpacity
1402
- onPress={handlePasswordReset}
1403
- disabled={busy}
1404
- style={{
1405
- backgroundColor: '#0066cc',
1406
- padding: 12,
1407
- borderRadius: 8,
1408
- alignItems: 'center',
1409
- }}
1410
- >
1411
- <Text style={{ color: '#fff' }}>
1412
- {busy ? 'Sending...' : 'Send Reset Link'}
1413
- </Text>
1414
- </TouchableOpacity>
1415
- </View>
1416
- )}
1417
- </ScrollView>
1418
- );
1419
- }
1420
- ```
1421
-
1422
- #### Complete React Native Register Screen
1423
-
1424
- Create `src/screens/RegisterScreen.js`:
1425
-
1426
- ```jsx
1427
- import React, { useState } from 'react';
1428
- import {
1429
- View,
1430
- Text,
1431
- TextInput,
1432
- TouchableOpacity,
1433
- ScrollView,
1434
- ActivityIndicator,
1435
- Alert,
1436
- } from 'react-native';
1437
- import { useAuth } from '../AuthContext';
1438
-
1439
- export default function RegisterScreen() {
1440
- const { authClient } = useAuth();
1441
-
1442
- const [email, setEmail] = useState('');
1443
- const [password, setPassword] = useState('');
1444
- const [name, setName] = useState('');
1445
- const [username, setUsername] = useState('');
1446
- const [error, setError] = useState(null);
1447
- const [busy, setBusy] = useState(false);
1448
-
1449
- async function handleSubmit() {
1450
- setError(null);
1451
- setBusy(true);
1452
-
1453
- try {
1454
- await authClient.register({
1455
- email,
1456
- username,
1457
- password,
1458
- name,
1459
- });
1460
-
1461
- Alert.alert(
1462
- 'Registration Successful',
1463
- 'Please verify your email, then login.'
1464
- );
1465
-
1466
- setEmail('');
1467
- setPassword('');
1468
- setName('');
1469
- setUsername('');
1470
- } catch (err) {
1471
- setError(err.message || 'Registration failed');
1472
- } finally {
1473
- setBusy(false);
1474
- }
1475
- }
1476
-
1477
- return (
1478
- <ScrollView style={{ flex: 1, padding: 20, backgroundColor: '#fff' }}>
1479
- <Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 24 }}>Sign Up</Text>
1480
-
1481
- <TextInput
1482
- placeholder="Name"
1483
- value={name}
1484
- onChangeText={setName}
1485
- editable={!busy}
1486
- style={{
1487
- borderWidth: 1,
1488
- borderColor: '#ddd',
1489
- padding: 12,
1490
- marginBottom: 12,
1491
- borderRadius: 8,
1492
- }}
1493
- />
1494
-
1495
- <TextInput
1496
- placeholder="Username"
1497
- value={username}
1498
- onChangeText={setUsername}
1499
- editable={!busy}
1500
- style={{
1501
- borderWidth: 1,
1502
- borderColor: '#ddd',
1503
- padding: 12,
1504
- marginBottom: 12,
1505
- borderRadius: 8,
1506
- }}
1507
- />
1508
-
1509
- <TextInput
1510
- placeholder="Email"
1511
- value={email}
1512
- onChangeText={setEmail}
1513
- keyboardType="email-address"
1514
- editable={!busy}
1515
- style={{
1516
- borderWidth: 1,
1517
- borderColor: '#ddd',
1518
- padding: 12,
1519
- marginBottom: 12,
1520
- borderRadius: 8,
1521
- }}
1522
- />
1523
-
1524
- <TextInput
1525
- placeholder="Password"
1526
- value={password}
1527
- onChangeText={setPassword}
1528
- secureTextEntry
1529
- editable={!busy}
1530
- style={{
1531
- borderWidth: 1,
1532
- borderColor: '#ddd',
1533
- padding: 12,
1534
- marginBottom: 20,
1535
- borderRadius: 8,
1536
- }}
1537
- />
1538
-
1539
- <TouchableOpacity
1540
- onPress={handleSubmit}
1541
- disabled={busy}
1542
- style={{
1543
- backgroundColor: '#0066cc',
1544
- padding: 12,
1545
- borderRadius: 8,
1546
- alignItems: 'center',
1547
- }}
1548
- >
1549
- {busy ? (
1550
- <ActivityIndicator color="#fff" />
1551
- ) : (
1552
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Create Account</Text>
1553
- )}
1554
- </TouchableOpacity>
1555
-
1556
- {error && <Text style={{ color: 'red', marginTop: 12 }}>{error}</Text>}
1557
- </ScrollView>
1558
- );
1559
- }
1560
- ```
305
+ router.post('/register', async (req, res) => {
306
+ const { email, username, password, name, ...extra } = req.body;
1561
307
 
1562
- #### Complete React Native Home Screen
1563
-
1564
- Create `src/screens/HomeScreen.js`:
1565
-
1566
- ```jsx
1567
- import React, { useState } from 'react';
1568
- import {
1569
- View,
1570
- Text,
1571
- TouchableOpacity,
1572
- ScrollView,
1573
- ActivityIndicator,
1574
- Alert,
1575
- } from 'react-native';
1576
- import { useAuth } from '../AuthContext';
1577
-
1578
- export default function HomeScreen() {
1579
- const { user, token, logout, refreshProfile, loading, authClient } = useAuth();
1580
-
1581
- const [feedback, setFeedback] = useState(null);
1582
- const [busy, setBusy] = useState(false);
1583
- const [showDeleteForm, setShowDeleteForm] = useState(false);
1584
-
1585
- async function doChangePassword() {
1586
- setBusy(true);
1587
- setFeedback(null);
1588
-
1589
- try {
1590
- await authClient.requestChangePasswordLink({ email: user?.email });
1591
- setFeedback('Password change email sent. Check your inbox.');
1592
- } catch (err) {
1593
- setFeedback(err.message || 'Failed to send password change email');
1594
- } finally {
1595
- setBusy(false);
1596
- }
1597
- }
1598
-
1599
- async function doDeleteAccount() {
1600
- Alert.alert(
1601
- 'Delete Account',
1602
- 'Are you sure? This cannot be undone.',
1603
- [
1604
- { text: 'Cancel', style: 'cancel' },
1605
- {
1606
- text: 'Delete',
1607
- style: 'destructive',
1608
- onPress: async () => {
1609
- setBusy(true);
1610
- try {
1611
- await authClient.deleteAccount({ email: user?.email });
1612
- setFeedback('Account deleted. Logging out...');
1613
- setTimeout(() => logout(), 1500);
1614
- } catch (err) {
1615
- setFeedback(err.message || 'Delete account failed');
1616
- } finally {
1617
- setBusy(false);
1618
- }
1619
- },
1620
- },
1621
- ]
1622
- );
308
+ try {
309
+ const result = await authclient.register({
310
+ email,
311
+ username,
312
+ password,
313
+ name,
314
+ ...extra,
315
+ });
316
+ res.json(result);
317
+ } catch (err) {
318
+ res.status(err.status || 400).json({
319
+ success: false,
320
+ message: err.message || 'Registration failed',
321
+ code: err.code || 'REGISTER_FAILED',
322
+ });
1623
323
  }
324
+ });
1624
325
 
1625
- return (
1626
- <ScrollView style={{ flex: 1, padding: 20, backgroundColor: '#fff' }}>
1627
- <Text style={{ fontSize: 24, fontWeight: 'bold', marginBottom: 24 }}>Welcome</Text>
1628
-
1629
- <View style={{ backgroundColor: '#f5f5f5', padding: 16, borderRadius: 8, marginBottom: 24 }}>
1630
- <Text style={{ marginBottom: 8 }}><Text style={{ fontWeight: 'bold' }}>User ID:</Text> {user?.id || 'n/a'}</Text>
1631
- <Text style={{ marginBottom: 8 }}><Text style={{ fontWeight: 'bold' }}>Email:</Text> {user?.email}</Text>
1632
- <Text style={{ marginBottom: 8 }}><Text style={{ fontWeight: 'bold' }}>Name:</Text> {user?.name || '—'}</Text>
1633
- <Text style={{ marginBottom: 8 }}><Text style={{ fontWeight: 'bold' }}>Login Method:</Text> {user?.login_method || '—'}</Text>
1634
- <Text><Text style={{ fontWeight: 'bold' }}>Token:</Text> {token ? token.slice(0, 18) + '…' : 'none'}</Text>
1635
- </View>
1636
-
1637
- <View style={{ flexDirection: 'row', gap: 12, marginBottom: 24 }}>
1638
- <TouchableOpacity
1639
- onPress={refreshProfile}
1640
- disabled={loading}
1641
- style={{
1642
- flex: 1,
1643
- backgroundColor: '#0066cc',
1644
- padding: 12,
1645
- borderRadius: 8,
1646
- alignItems: 'center',
1647
- }}
1648
- >
1649
- {loading ? (
1650
- <ActivityIndicator color="#fff" />
1651
- ) : (
1652
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Refresh Profile</Text>
1653
- )}
1654
- </TouchableOpacity>
1655
-
1656
- <TouchableOpacity
1657
- onPress={logout}
1658
- style={{
1659
- flex: 1,
1660
- backgroundColor: '#999',
1661
- padding: 12,
1662
- borderRadius: 8,
1663
- alignItems: 'center',
1664
- }}
1665
- >
1666
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Logout</Text>
1667
- </TouchableOpacity>
1668
- </View>
1669
-
1670
- <View style={{ marginBottom: 24 }}>
1671
- <Text style={{ fontSize: 18, fontWeight: 'bold', marginBottom: 12 }}>Change Password</Text>
1672
- <Text style={{ marginBottom: 12 }}>We will email a secure link to {user?.email} to change your password.</Text>
1673
- <TouchableOpacity
1674
- onPress={doChangePassword}
1675
- disabled={busy}
1676
- style={{
1677
- backgroundColor: '#0066cc',
1678
- padding: 12,
1679
- borderRadius: 8,
1680
- alignItems: 'center',
1681
- }}
1682
- >
1683
- {busy ? (
1684
- <ActivityIndicator color="#fff" />
1685
- ) : (
1686
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Send Change Password Email</Text>
1687
- )}
1688
- </TouchableOpacity>
1689
- </View>
1690
-
1691
- {feedback && (
1692
- <Text style={{ color: feedback.includes('successfully') || feedback.includes('sent') ? 'green' : 'red', marginBottom: 12 }}>
1693
- {feedback}
1694
- </Text>
1695
- )}
1696
-
1697
- <TouchableOpacity
1698
- onPress={doDeleteAccount}
1699
- style={{
1700
- backgroundColor: '#d32f2f',
1701
- padding: 12,
1702
- borderRadius: 8,
1703
- alignItems: 'center',
1704
- }}
1705
- >
1706
- <Text style={{ color: '#fff', fontWeight: 'bold' }}>Delete Account</Text>
1707
- </TouchableOpacity>
1708
- </ScrollView>
1709
- );
1710
- }
326
+ export default router;
1711
327
  ```
1712
328
 
1713
- #### React Native Splash Screen
1714
-
1715
- Create `src/screens/SplashScreen.js`:
1716
-
1717
- ```jsx
1718
- import React from 'react';
1719
- import { View, ActivityIndicator, Text } from 'react-native';
329
+ ---
1720
330
 
1721
- export default function SplashScreen() {
1722
- return (
1723
- <View style={{ flex: 1, justifyContent: 'center', alignItems: 'center', backgroundColor: '#fff' }}>
1724
- <ActivityIndicator size="large" color="#0066cc" />
1725
- <Text style={{ marginTop: 12 }}>Loading...</Text>
1726
- </View>
1727
- );
1728
- }
1729
- ```
331
+ ## Error Handling
1730
332
 
1731
- ### 1. Check if User is Authenticated
333
+ All methods throw an `AuthError` when the request fails.
1732
334
 
1733
- ```jsx
1734
- import { useAuth } from './AuthContext';
335
+ Shape (simplified):
1735
336
 
1736
- function MyComponent() {
1737
- const { user, token } = useAuth();
1738
-
1739
- if (!user || !token) {
1740
- return <p>Please login first</p>;
1741
- }
1742
-
1743
- return <p>Welcome, {user.name}!</p>;
337
+ ```ts
338
+ class AuthError extends Error {
339
+ status: number; // HTTP status code
340
+ code: string; // machine-readable code, e.g. 'EMAIL_NOT_VERIFIED'
341
+ data: any; // full JSON response (optional)
1744
342
  }
1745
343
  ```
1746
344
 
1747
- ### 2. Handle Authentication Errors
345
+ Example pattern:
1748
346
 
1749
- ```jsx
347
+ ```js
1750
348
  try {
1751
- await authClient.login({ email, password });
349
+ const res = await authclient.login({ email, password });
1752
350
  } catch (err) {
1753
- // Check error code
1754
351
  if (err.code === 'EMAIL_NOT_VERIFIED') {
1755
- // Show verification resend option
352
+ // Ask user to verify email or call resendVerificationEmail
1756
353
  } else if (err.status === 401) {
1757
354
  // Invalid credentials
1758
355
  } else {
1759
- // Generic error
1760
- console.error(err.message);
1761
- }
1762
- }
1763
- ```
1764
-
1765
- ### 3. Protect Routes/Components
1766
-
1767
- ```jsx
1768
- function ProtectedRoute({ children }) {
1769
- const { user } = useAuth();
1770
-
1771
- if (!user) {
1772
- return <Navigate to="/login" />;
356
+ console.error(err);
1773
357
  }
1774
-
1775
- return children;
1776
358
  }
1777
359
  ```
1778
360
 
1779
- ### 4. Auto-refresh User Profile on Mount
1780
-
1781
- ```jsx
1782
- useEffect(() => {
1783
- if (user && token) {
1784
- refreshProfile();
1785
- }
1786
- }, []);
1787
- ```
1788
-
1789
- ### 5. Handle Token Expiration
1790
-
1791
- ```jsx
1792
- const { refreshProfile } = useAuth();
1793
-
1794
- // Periodically check token validity
1795
- useEffect(() => {
1796
- const interval = setInterval(async () => {
1797
- try {
1798
- await refreshProfile();
1799
- } catch (err) {
1800
- if (err.status === 401) {
1801
- // Token expired, redirect to login
1802
- logout();
1803
- }
1804
- }
1805
- }, 5 * 60 * 1000); // Check every 5 minutes
1806
-
1807
- return () => clearInterval(interval);
1808
- }, [refreshProfile, logout]);
1809
- ```
1810
-
1811
361
  ---
1812
362
 
1813
- ## React Native Specific Patterns
1814
-
1815
- ### 1. Handle Token Persistence (React Native)
1816
-
1817
- ```jsx
1818
- // AuthContext.js already includes this in the bootstrap function
1819
- React.useEffect(() => {
1820
- const bootstrapAsync = async () => {
1821
- try {
1822
- const savedToken = await AsyncStorage.getItem('auth_user_token');
1823
- if (savedToken) {
1824
- authClient.setToken(savedToken);
1825
- setToken(savedToken);
1826
- // Fetch user profile
1827
- const res = await authClient.getProfile();
1828
- setUser(res.data.user);
1829
- }
1830
- } catch (err) {
1831
- console.error('Failed to restore token', err);
1832
- } finally {
1833
- setIsReady(true);
1834
- }
1835
- };
1836
- bootstrapAsync();
1837
- }, []);
1838
- ```
1839
-
1840
- ### 2. Handle Deep Links (React Native)
1841
-
1842
- ```jsx
1843
- import * as Linking from 'expo-linking';
1844
-
1845
- const linking = {
1846
- prefixes: ['myapp://', 'https://myapp.com'],
1847
- config: {
1848
- screens: {
1849
- ResetPassword: 'reset/:token',
1850
- VerifyEmail: 'verify/:token',
1851
- },
1852
- },
1853
- };
1854
-
1855
- export function RootNavigator() {
1856
- const { user } = useAuth();
1857
-
1858
- return (
1859
- <NavigationContainer linking={linking}>
1860
- {/* Navigation config */}
1861
- </NavigationContainer>
1862
- );
1863
- }
1864
- ```
363
+ ## Security Notes
1865
364
 
1866
- ### 3. Handle Keyboard in React Native
1867
-
1868
- ```jsx
1869
- import { KeyboardAvoidingView, Platform } from 'react-native';
1870
-
1871
- export default function LoginScreen() {
1872
- return (
1873
- <KeyboardAvoidingView
1874
- behavior={Platform.OS === 'ios' ? 'padding' : 'height'}
1875
- style={{ flex: 1 }}
1876
- >
1877
- {/* Form content */}
1878
- </KeyboardAvoidingView>
1879
- );
1880
- }
1881
- ```
1882
-
1883
- ### 4. Handle Biometric Authentication (React Native)
1884
-
1885
- ```jsx
1886
- import * as SecureStore from 'expo-secure-store';
1887
- import * as LocalAuthentication from 'expo-local-authentication';
1888
-
1889
- async function handleBiometricLogin() {
1890
- try {
1891
- const compatible = await LocalAuthentication.hasHardwareAsync();
1892
- if (!compatible) return;
1893
-
1894
- const savedEmail = await SecureStore.getItemAsync('saved_email');
1895
- const savedPassword = await SecureStore.getItemAsync('saved_password');
1896
-
1897
- if (savedEmail && savedPassword) {
1898
- const result = await LocalAuthentication.authenticateAsync({
1899
- disableDeviceFallback: false,
1900
- });
1901
-
1902
- if (result.success) {
1903
- await login({ email: savedEmail, password: savedPassword });
1904
- }
1905
- }
1906
- } catch (err) {
1907
- console.error('Biometric login failed', err);
1908
- }
1909
- }
1910
- ```
365
+ - **Backend-only**: Do not expose `apiSecret` in browser or mobile frontend code.
366
+ - Frontend apps should call **your backend**, and your backend uses `@mspkapps/auth-client` to talk to the MSPK Auth Platform.
367
+ - Always keep `apiKey`, `apiSecret`, and `googleClientId` in environment variables in production.
1911
368
 
1912
369
  ---
1913
370
 
1914
- ## Troubleshooting
1915
-
1916
- ### Issue: "Failed to execute 'fetch' on 'Window': Illegal invocation"
371
+ ## Links & Support
1917
372
 
1918
- **Solution:** Make sure you bind fetch correctly in AuthContext:
1919
-
1920
- ```javascript
1921
- fetch: (input, init = {}) => {
1922
- // ...
1923
- return window.fetch(input, { ...init, headers });
1924
- }
1925
- ```
1926
-
1927
- ### Issue: 401 Unauthorized on authenticated requests
1928
-
1929
- **Solution:** Ensure token is being set and sent correctly:
1930
-
1931
- 1. Check if `authClient.setToken()` is called after login
1932
- 2. Verify the Authorization header format matches your server's expectations
1933
- 3. Check if token is expired
1934
-
1935
- ### Issue: Google Sign-In not working
1936
-
1937
- **Solution:**
1938
- 1. Verify Google Client ID is correct
1939
- 2. Make sure `GoogleOAuthProvider` wraps your app
1940
- 3. Check browser console for CORS errors
1941
- 4. Ensure your domain is authorized in Google Cloud Console
1942
-
1943
- ### Issue: Email verification emails not arriving
1944
-
1945
- **Solution:**
1946
- 1. Check spam/junk folder
1947
- 2. Verify email is correct
1948
- 3. Wait a few minutes (email delivery can be delayed)
1949
- 4. Use `resendVerificationEmail()` to request a new one
1950
-
1951
- ### Issue: Password reset link not working
1952
-
1953
- **Solution:**
1954
- 1. Check if link has expired (usually 1 hour)
1955
- 2. Request a new link using `requestPasswordReset()`
1956
- 3. Ensure you're using the correct email address
1957
-
1958
- ---
1959
-
1960
- ## Platform-Specific Considerations
1961
-
1962
- ### React Web vs React Native
1963
-
1964
- | Feature | React Web | React Native |
1965
- |---------|-----------|--------------|
1966
- | **Storage** | localStorage (default) | AsyncStorage |
1967
- | **Navigation** | React Router | React Navigation |
1968
- | **Google Sign-In** | @react-oauth/google | @react-native-google-signin/google-signin |
1969
- | **Token Persistence** | Optional (storage: null) | Recommended (AsyncStorage) |
1970
- | **Network Requests** | window.fetch | Global fetch |
1971
- | **UI Components** | HTML/CSS | React Native components |
1972
- | **Deep Linking** | Browser URL | Expo Linking or Deep Linking |
1973
-
1974
- ### Key Differences in Implementation
1975
-
1976
- #### 1. Token Storage
1977
-
1978
- **React Web:**
1979
- ```javascript
1980
- storage: null // In-memory only
1981
- // or
1982
- storage: window.localStorage // Persistent
1983
- ```
1984
-
1985
- **React Native:**
1986
- ```javascript
1987
- import AsyncStorage from '@react-native-async-storage/async-storage';
1988
- storage: AsyncStorage // Persistent and secure
1989
- ```
1990
-
1991
- #### 2. Navigation
1992
-
1993
- **React Web:**
1994
- ```jsx
1995
- // Uses React Router or conditional rendering
1996
- {user ? <Home /> : <Login />}
1997
- ```
1998
-
1999
- **React Native:**
2000
- ```jsx
2001
- // Uses React Navigation
2002
- {user ? (
2003
- <Stack.Screen name="Home" component={HomeScreen} />
2004
- ) : (
2005
- <Stack.Screen name="Login" component={LoginScreen} />
2006
- )}
2007
- ```
2008
-
2009
- #### 3. Loading States
2010
-
2011
- **React Web:**
2012
- ```jsx
2013
- <button disabled={loading}>
2014
- {loading ? 'Please wait…' : 'Login'}
2015
- </button>
2016
- ```
2017
-
2018
- **React Native:**
2019
- ```jsx
2020
- <TouchableOpacity disabled={loading}>
2021
- {loading ? <ActivityIndicator /> : <Text>Login</Text>}
2022
- </TouchableOpacity>
2023
- ```
2024
-
2025
- #### 4. Styling
2026
-
2027
- **React Web:**
2028
- ```jsx
2029
- <button style={{ color: 'white', backgroundColor: 'blue' }}>
2030
- Login
2031
- </button>
2032
- ```
2033
-
2034
- **React Native:**
2035
- ```jsx
2036
- <TouchableOpacity style={{ backgroundColor: 'blue' }}>
2037
- <Text style={{ color: 'white' }}>Login</Text>
2038
- </TouchableOpacity>
2039
- ```
2040
-
2041
- ---
2042
-
2043
- ## Security Best Practices
2044
-
2045
- ### 1. **Never expose API secrets in production**
2046
-
2047
- In production, move API secret to your backend server:
2048
-
2049
- ```javascript
2050
- // Instead of this (insecure):
2051
- const authClient = AuthClient.create(PUBLIC_KEY, API_SECRET);
2052
-
2053
- // Do this (secure):
2054
- // Have your backend proxy all auth requests
2055
- // Your frontend only sends requests to YOUR server
2056
- // Your server adds the API secret and forwards to auth server
2057
- ```
2058
-
2059
- ### 2. **Use HTTPS in production**
2060
-
2061
- Always serve your app over HTTPS to prevent token interception.
2062
-
2063
- ### 3. **Implement token refresh**
2064
-
2065
- Periodically refresh the user's profile to detect token expiration early.
2066
-
2067
- ### 4. **Clear sensitive data on logout**
2068
-
2069
- ```javascript
2070
- const logout = () => {
2071
- setUser(null);
2072
- setToken(null);
2073
- authClient.token = null;
2074
- // Clear any other sensitive state
2075
- };
2076
- ```
2077
-
2078
- ### 5. **Validate user input**
2079
-
2080
- Always validate email format, password strength, etc. before sending to server.
2081
-
2082
- ---
2083
-
2084
- ## Additional Resources
2085
-
2086
- - **npm Package:** [@mspkapps/auth-client](https://www.npmjs.com/package/@mspkapps/auth-client)
2087
- - **Google OAuth Setup (Web):** [React OAuth Google Docs](https://www.npmjs.com/package/@react-oauth/google)
2088
- - **Google OAuth Setup (React Native):** [@react-native-google-signin](https://www.npmjs.com/package/@react-native-google-signin/google-signin)
2089
- - **React Documentation:** [React Official Docs](https://react.dev)
2090
- - **React Native Documentation:** [React Native Official Docs](https://reactnative.dev)
2091
- - **React Navigation:** [React Navigation Docs](https://reactnavigation.org)
2092
- - **AsyncStorage (React Native):** [AsyncStorage Documentation](https://react-native-async-storage.github.io)
2093
-
2094
- ---
2095
-
2096
- ## Support
2097
-
2098
- For questions or issues:
2099
- 1. Check this documentation first
2100
- 2. Review the demo code in this project
2101
- 3. Check package documentation on npm
2102
- 4. Contact the auth server maintainers
2103
-
2104
- ---
373
+ - npm: `@mspkapps/auth-client`
374
+ - MSPK™ Auth Platform dashboard: (URL you provide in your docs)
2105
375
 
2106
- **Happy Coding! 🚀**
376
+ For issues or feature requests, open an issue in your repository or contact MSPK™ Auth Platform support.